The mobile create/edit sheet never showed the currency, so every trip
started from the dashboard silently took the server default of EUR and
could only be corrected from the trip planner, which reuses TripFormModal
and does have the field.
Same control as the desktop form — searchable select over currenciesWith(),
gated on trip_edit like the rest of the sheet.
Move the tag SQL from src/services/tagService.ts into TagsService over an
injected DatabaseService (identical statements, #10b981 default, COALESCE
semantics). Non-Nest consumers (MCP tools, plugin RPC host) go through the
new tags.bridge.ts, which exports the legacy function names 1:1 from a
module-level instance over the shared db Proxy — the settled pattern for
the remaining legacy-service migrations (recipe documented in
src/nest/README.md). Tests are DI-native: direct service construction in
unit (with bridge delegation cases for the coverage gate) and
DatabaseModule + real SQL in e2e.
The bump to sharp 0.35.0 left the @img/sharp-linuxmusl-* pins on 0.35.1,
so npm nested the 0.35.0 binary under node_modules/sharp while hoisting
libvips to the root. The nested .node then looks for libvips-cpp.so.8.18.3
next to itself, finds nothing, and the Alpine image build dies in the
client prebuild:
Could not load the "sharp" module using the linuxmusl-x64 runtime
ERR_DLOPEN_FAILED: Error loading shared library libvips-cpp.so.8.18.3
Neither docker workflow runs on push, so this only surfaces when a release
is cut. Pin both binaries to 0.35.0 so they hoist next to libvips again.
Used, carried over, comp days and remaining all fit a desktop sidebar; on a
phone they collapsed into four scraps of 9px text fighting for one row. Only
the balance is left, as a pill tinted by how much of it there is — the rest is
still a tap away in the entitlement view.
The toolbar chip says "Company" rather than "Company Holiday": it sits beside
two other buttons and the building icon already carries the meaning. The
allowance stepper reads "Days" instead of "days / year", reusing the label the
desktop entitlement tile has always used — the year is on the pill above it.
The panel dropped the eight feature tiles and the self-hosted line — they read
as a spec sheet on a screen whose job is to get you in. What is left is the
mark, the tagline and one line of copy, centred over the map, each with a soft
dark halo so the dots cannot eat the type. The tagline and subtitle never wrap:
they scale with the panel instead, since it is a fixed share of the viewport.
The takeoff after sign-in used to tell a different story than the screen it
came from — cloud ellipses, a lone plane, a contrail. It now picks the panel up
and escalates it: the same map, every route departing at once, the logo landing
as the network completes, then a soft hand-off into the app.
Replaces the drifting plane silhouettes with the thing TREK actually does:
routes lighting up between cities across a dot map of the world. Arcs leave a
city, a light travels the curve, the destination lands, and the arc retires to
make room for the next.
The map is drawn once into an offscreen layer and blitted per frame, so only
the handful of live arcs cost anything — no shadowBlur in the frame path. The
same component covers the sign-in moment via `variant="takeoff"`, where the
network fires as one cascade and stays lit instead of cycling.
Reduced motion gets the map as a still frame.
The login screen is unauthenticated, so it cannot call the Atlas endpoint the
rest of the app uses for country geometry. This is that same admin-0 bundle,
sampled along the coastlines onto a 300x150 grid and delta-encoded — 15 KB
instead of a 28 MB fetch nobody is allowed to make yet.
The page still described Vacay as strictly January to December and never
mentioned half days at all, so both new features would have shipped
undocumented.
Windows for all three year types, the fallbacks (anniversary without a hire
date, a day the month cannot have), usage counted across the calendar-year
boundary, carry-over chained over periods rather than years, deleteYear over a
shifted period, and the reads with and without a viewer.
For comp: excluded from usage, a half comp day still costing nothing,
converting a vacation day back, and comp_used reported on its own.
The half-day button carried a ½ glyph and comp/flex a clock, neither of which
appears anywhere on the calendar. Both now show what a click actually puts on
the day: the orange dot for a half day, a hatched disc in the selected person's
colour for comp.
The digit on an all-comp cell also needed help. The hatch lets the surface
through between its stripes, so a white number sank into it — a shadow carries
the contrast now and every logged day keeps the same digit colour. Mixed days
still have a solid segment under the number and get none.
The server has returned comp_used since the leave type landed, but nothing
showed it — comp days simply vanished from the sidebar. They now sit beside
the tiles rather than inside the used/left arithmetic, which is the point of
them.
Carry-over is labelled from the window the server counted that row over, not
from the viewer's own setting, so a fused plan with mixed leave years does not
tell one member their days came "from 2025" when they came from a period that
started in July.
The card also folds now. It is the longest thing in the sidebar once several
people are fused in, and the state belongs to the person looking at it, so it
lives in localStorage rather than on the plan.
Calendar, fiscal (month + day) or from the hire date, per user — it sits with
the plan settings but belongs to the person, so fused members each keep their
own and the grid follows whoever is looking at it.
Both grids render twelve months from the window start with year rollover
instead of a fixed Jan–Dec, and the mobile month navigation moves through slots
of that window, so stepping past December lands in January of the same period
rather than jumping back to the start of the calendar year. The month effect
keys on the window's shape rather than the settings object, which the store
replaces with an equal copy on every refresh — otherwise any sync would throw
away the month the user had open.
Known edge for a start day past the 1st: the cards are month-aligned, so the
first days of the start month are drawn but count toward the previous period.
Counting stays day-exact.
The server owns the arithmetic entitlements are computed with, but the grid has
to render the same twelve months and the overlays have to filter against the
same range, so the window resolves on both sides. yearWindow.ts is that mirror,
including the Feb-29 clamp and the anniversary-without-a-hire-date fallback.
The store loads the settings before the year list picks a default — with a
shifted year the current period is not necessarily the current calendar year.
Holidays are fetched per calendar year the window touches and clipped back to
what the grid draws; a school break straddling New Year comes back from both
fetches, so markers are de-duplicated per day. The label is part of that
identity, otherwise two calendars left on the same colour collapse into one.
Adds GET/PUT /addons/vacay/year-settings and makes the reads window-aware, so
the backend from the previous commit is actually reachable.
Entries and shared calendars now load over the caller's window instead of a
date prefix — a shifted year spans two calendar years and the prefix silently
dropped the second half. Reads are month-aligned (the grid draws month cards),
while entitlement stays day-exact; the two coincide for every window starting
on the 1st, which is all of calendar. deleteYear clears entries per author over
that author's period, and company holidays only over the range every member
shares, so one member deleting a period cannot reach into another's live one.
getStats reports the window it counted, and getOwnPlan seeds the period today
falls into rather than the calendar year.
A start day the month cannot have (Feb 29 from a hire date, a stored Feb 31)
is clamped to one every year has — otherwise the boundary string matches no
date and the whole window slides. Anniversary before a hire date is entered
resolves to the calendar year instead of borrowing a month a previous fiscal
setting left behind.
MCP reads pass their user through; without one the range stays the plain
calendar year, so plan-scoped callers are unchanged.
The kind column changed four things the tests still asserted the old way:
toggleEntry gained a parameter, the toolbar gained the comp modifier, shared
calendar rows carry kind, and a multi-person cell is now segment overlays
instead of one split gradient. splitBackground went with it — nothing but the
test still referenced it.
The toolbar tests picked the half-day button by index, which the new button
shifted; they select by name now so the next modifier does not break them.
Comp/Flex days (#1074) — complete. A second leave type that does not deduct from the vacation entitlement (flextime / overtime offset). New kind column on vacay_entries, excluded from usedDays via a CASE sum; toggleEntry carries kind through service/nest/store with change-detection; desktop and mobile get a comp toolbar toggle next to the half-day one (orthogonal, so a half comp day is free); comp segments render as a diagonal hatch of the person colour, per-segment for split days (Version B). comp_used is surfaced separately in getStats. Translated into all 23 locales.
Configurable vacation year (#737) — backend only. Per-user vacay_user_settings (calendar / fiscal / anniversary), resolveYearWindow, and usedDays / compUsedDays / getStats / carry-over now computed over the leave-year [start,end) window. 'calendar' resolves byte-identically to the old date-prefix behaviour, so nothing changes for existing users and the feature is inert until the settings UI lands. Frontend (settings selector + calendar window rollover), the settings endpoint, window-aware getEntries and holiday loading are still to do — see the PR description.
The always-on card picker is gone — travelers are assigned in the booking and transport modals now, matching mobile. The card shows assigned travelers read-only as pills in the grey field box, TravelerChips is replaced by a shared TravelerPicker, and the toolbar filter gets gradient avatars with an accent ring and check badge.