Commit Graph

808 Commits

Author SHA1 Message Date
Maurice a3e2ff9fa5 fix(mobile): offer the currency on the dashboard trip sheet
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.
2026-07-25 22:04:03 +02:00
jubnl 8a46c56a96 test(client): dismiss the CTA-style support modal in e2e setup 2026-07-25 16:30:33 +02:00
Maurice 39d4dc2675 feat(vacay): thin the mobile person card down to the number you act on
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.
2026-07-25 14:49:38 +02:00
Maurice 8b6a80fce0 feat(login): rebuild the branding panel and the sign-in moment around 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.
2026-07-25 14:49:38 +02:00
Maurice 45ce365920 feat(login): a living world map behind the sign-in panel
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.
2026-07-25 14:49:38 +02:00
Maurice 0d9032224d feat(login): bake TREK's coastlines into a dot grid for the sign-in screen
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.
2026-07-25 14:49:38 +02:00
dependabot[bot] b955998905 chore(deps-dev): bump sharp from 0.33.5 to 0.35.0 (#1690)
* chore(deps): bump dompurify from 3.4.11 to 3.4.12 (#1681)

Bumps [dompurify](https://github.com/cure53/DOMPurify) from 3.4.11 to 3.4.12.
- [Release notes](https://github.com/cure53/DOMPurify/releases)
- [Commits](https://github.com/cure53/DOMPurify/compare/3.4.11...3.4.12)

---
updated-dependencies:
- dependency-name: dompurify
  dependency-version: 3.4.12
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* chore(deps-dev): bump sharp from 0.33.5 to 0.35.0

Bumps [sharp](https://github.com/lovell/sharp) from 0.33.5 to 0.35.0.
- [Release notes](https://github.com/lovell/sharp/releases)
- [Commits](https://github.com/lovell/sharp/compare/v0.33.5...v0.35.0)

---
updated-dependencies:
- dependency-name: sharp
  dependency-version: 0.35.0
  dependency-type: direct:development
...

Signed-off-by: dependabot[bot] <support@github.com>

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-07-25 13:14:41 +02:00
Maurice b37996ec39 feat(vacay): make the toolbar show the marker it places (#1074)
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.
2026-07-25 12:02:16 +02:00
Maurice f1d520bbc2 feat(vacay): show comp days in the stats and fold the card away (#1074)
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.
2026-07-25 12:02:16 +02:00
Maurice d984e09473 feat(vacay): pick a leave year and roll the grid over with it (#737)
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.
2026-07-25 12:02:16 +02:00
Maurice e1c5daeccb feat(vacay): mirror the leave-year window in the client (#737)
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.
2026-07-25 12:02:16 +02:00
Maurice 2672a7a741 test(vacay): catch the suites up with the comp/flex column
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.
2026-07-25 12:02:16 +02:00
Maurice c475e5b04c feat(vacay): comp/flex leave type (#1074) + configurable-year backend (#737)
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.
2026-07-25 12:02:16 +02:00
Konstantinos Thermos c61203e607 fix(reservations): restrict all booking dates to the trip span 2026-07-24 19:16:55 +02:00
Maurice 8769ed084e refactor(reservations): move desktop traveler assignment into the edit/create modals
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.
2026-07-24 16:50:41 +02:00
Maurice 2a5c54c025 feat(reservations): traveler filter on the mobile bookings and transports tabs 2026-07-24 16:50:41 +02:00
Maurice bc06125d6e test(reservations): server + client coverage for traveler assignment 2026-07-24 16:50:41 +02:00
Maurice e89dae5528 feat(reservations): traveler filter + transit-card badge (desktop) and full mobile traveler UI (cards + sheet pickers) 2026-07-24 16:50:41 +02:00
Maurice 781c693434 feat(reservations): traveler badge and inline member/guest picker on booking cards (desktop) 2026-07-24 16:50:41 +02:00
Maurice 791468e5b3 fix(airtrail): also treat an endpoint-count > 2 booking as a local multi-leg shape, mirroring the server so a locally-grown chain is not mislabelled removed 2026-07-24 12:28:49 +02:00
fbnlrz 3dc9ac6ac6 fix(airtrail): stop labelling a merged multi-leg import "removed in AirTrail"
A multi-leg flight imported "as one flight with a layover" is detached
from sync by design — AirTrail has no single flight to round-trip a
layover chain to, so it is created with sync_enabled=0 (#1535). The
reservations badge collapsed every sync_enabled=0 AirTrail row into "Not
synced" with the tooltip "This flight was removed in AirTrail", which is
false for a merged booking that was never removed (#1646).

Give the multi-leg case (metadata.legs.length > 1) its own state: show
the normal AirTrail badge with a truthful tooltip explaining it stays a
one-time import. A genuinely removed single-leg flight still shows "Not
synced". Adds reservations.airtrail.layoverHint across all locales.
2026-07-24 12:28:49 +02:00
yu 617ffae351 feat(journey): add mobile quick capture with location and weather 2026-07-24 12:06:29 +02:00
Maurice 60abcf6598 feat(dashboard): drop the play circle from the beacon, keep the copy and the badge 2026-07-24 11:49:11 +02:00
Maurice 51f8afdb60 feat(dashboard): retire the 4.0.0 moment after Aug 23 — viewable-until badge + a permanent dismiss X 2026-07-24 11:49:11 +02:00
Maurice 2cec06161d fix(dashboard): drop the frame shake — the boom and the shatter carry the climax on their own 2026-07-24 11:49:11 +02:00
Maurice 0834282bfd feat(dashboard): five signature text moments + widget-matched corners
- The finale condenses out of light: ~1000 particles rise from the world and
  gather into the anthem's letterforms before the crisp DOM text takes over;
  the language cascade waits for the word to settle.
- Replaced fear-act lines decay letter by letter — the words of fear fall
  apart instead of vanishing.
- The climax boom rocks the whole frame for half a second, in sync with the
  sub impact and the border shatter.
- Hope-act lines reveal sentence by sentence, in speech rhythm.
- The standing anthem breathes — a barely-there scale and glow pulse.
- Beacon corners now use the dashboard tool widgets' --r-xl radius.
2026-07-24 11:49:11 +02:00
Maurice ffda4de77d feat(dashboard): the anthem IS the ending + beacon final polish + navbar fix
The show now ends standing: KEINE ANGST with the full 23-language cascade
stays up over the lit world, music carrying on until the traveler closes it —
no credits hand-off. Skip jumps straight to that standing frame.

Beacon: dismiss × removed, copy block centered on the canvas, the title gets
its quiet jewelry treatment — ivory-to-gold fill, a traveling sheen and a slow
breathing glow.

The overlay now portals to document.body: the sticky dashboard sidebar forms
its own stacking context, so the fixed navbar (z-200 at root) used to float
above the show regardless of the overlay's z-index.
2026-07-24 11:49:11 +02:00
Maurice 1cf1a9ca42 fix(dashboard): end screen drops the inspiration credit and the close button — anthem, three lines, done 2026-07-24 11:49:11 +02:00
Maurice 406229e701 fix(dashboard): flash words become stamped title cards — chromatic fringes, micro-jitter, hard cut, no zoom 2026-07-24 11:49:11 +02:00
Maurice d2d8b65827 fix(dashboard): soften the pivot into 'but you have traveled' + audio review fixes
The transition out of the fear act was too brutal: the silence now arrives as
a fast pull-away with the hall tail ringing out instead of a 100ms kill, the
pivot line trades its impact boom for a slow warm A1 swell (the first calm
breath), and the line itself breathes in over 3.4s.

Audio review fixes: the wind's swell LFO moves to a series tremolo stage so
the release can actually silence it before the cut (it used to warble through
the silence and click on stop); the heartbeat's noise layer becomes the
intended high-passed click instead of a dull lowpassed thud; dispose() pulls
the master to zero before the deferred context close so leaving the show
cannot pop.
2026-07-24 11:49:11 +02:00
Maurice ba2582f9c8 feat(dashboard): beacon goes cinema-poster — centered copy, iconic play circle
Copy sits centered on the night-world canvas over a corner-pooling scrim; the
title grows monumental tracking, a gold sheen and a soft glow; the text CTA
becomes a slowly pulsing golden play circle that fills on hover. The version
eyebrow and the red live dot are gone — title, one line, play. Nothing else.
2026-07-24 11:49:11 +02:00
Maurice 920628d63c feat(dashboard): show polish — readable lines, gliding arcs, a real hall
- Lines get a layered soft glow plus a radial pool of darkness under the text
  zone, so they hold up over the burning border map without a hard box.
- Arc heads now interpolate fractionally along the curve with ease-out growth
  (the whole-segment quantization read as stutter), in the show and the beacon
  teaser alike.
- The score gains a convolution reverb with a procedurally generated impulse
  response; the fear act trades raw saws for an organic bed (sine sub,
  drifting fifth, breathing noise rumble, a semitone grind in the dread act),
  the shimmer runs through a feedback echo, impact tails bloom in the hall,
  the heartbeat stays close and dry.
2026-07-24 11:49:11 +02:00
Maurice 10bfad97da feat(dashboard): cinematic beacon — living arc teaser, sheen title, scrim
The beacon is now a dark cinema card: a small canvas paints a breathing
night-world with golden travel arcs on loop, colored glows drift over it, the
title carries a gold sheen sweep and the copy sits on a bottom scrim. Core
colors ride inline so no dashboard widget CSS can wash the card out again.
2026-07-24 11:49:11 +02:00
Maurice c541c158df fix(dashboard): keep the generic line up through the stats slot for travelers with no places yet 2026-07-24 11:49:11 +02:00
Maurice aec541c573 feat(dashboard): make the KEINE ANGST show hit harder
Visual: city lights now come alive in the opening and die one by one while the
borders burn; three staccato words strobe with the heartbeat before the cut;
at the climax the borders no longer fade — they shatter into drifting sparks.
The traveler's own places ignite gold in the release act, with their real
numbers in the line ('{p} places. {c} countries. And not once did the world
hurt you.'). All heavy geometry is decimated to a vertex budget and pre-baked
into offscreen layers, so the frame path is a handful of drawImage calls — the
per-frame border stroke that dropped the show to ~1 fps is gone.

Sound: heartbeat with a click transient, wind textures, a doubled noise riser,
cue-driven sub-bass impacts and a gliding D–A–f#m–E progression with a sub
root under the anthem instead of the static pad.

Also: dashboard FAB hidden behind the show, suspended-AudioContext recovery
(Safari), tab-hidden pause for clock and audio, aborted geo fetch on close,
valid beacon markup (dismiss as sibling button), RTL-aware beacon, real
reduced-motion static frame, and native-speaker fixes for nl/sv/br/fr/it/es/
tr/gr copy.
2026-07-24 11:49:11 +02:00
Maurice c6d96872ae feat(dashboard): 4.0.0 release moment — KEINE ANGST
A one-release statement against racism, hatred and fascism, told the way TREK
knows the world: through travel. A pulsing beacon in the dashboard sidebar
(desktop only, dismissible) opens a ~80s full-screen show — the world as a map
of dots whose borders burn red and then dissolve under a growing web of travel
connections, driven by a fully procedural Web Audio score (heartbeat, drone,
A-major release; no audio assets, no copyrighted material) and hard-cut lines
in all 23 TREK languages, closing on the anthem cascade and a credit to the
song that sparked it (Danger Dan — Keine Angst).

Self-contained in components/FourZero (own copy dictionary, no shared i18n
churn); wired into the dashboard with two marked lines. Removing the folder
plus those two lines retires the moment after the release.
2026-07-24 11:49:11 +02:00
Konstantinos Thermos e100c52676 fix(maps): localize map POI pin titles to the user's language 2026-07-24 11:36:30 +02:00
Konstantinos Thermos fce26618a1 fix(transport): guard non-finite dates in trip-map duration 2026-07-24 11:36:24 +02:00
Elmer 7bd9e08016 feat(vacay): add school holiday overlays (#1633)
* feat: add vacay school holiday overlays

* fix(vacay): remove SOH control character corruption in locale files

13 of 22 vacay locale files contained a 0x01 (SOH) control character
that corrupted the closing }; token. This broke the shared package
build (tsdown/rolldown PARSE_ERROR: Invalid Character) and cascaded
to all client tests failing to resolve @trek/shared.

Affected locales: ar, br, ca, cs, es, fr, gr, hu, id, it, ja, ko,
nl, pl, ru, sv, tr, uk, vi, zh, zh-TW

* fix(vacay): school-holiday migration order, i18n dupes, settings layout

- Move the school-holiday columns migration to the end of the migrations
  array. It sat before the #1435/#1281 migrations, which every existing DB
  had already run, so the runner skipped it and school_holidays_enabled /
  vacay_holiday_calendars.type were never created — PUT /addons/vacay/plan
  then 500'd with "no such column".
- Remove duplicate i18n keys in 13 locale vacay files (TS1117) that failed
  the shared + client typechecks.
- Move the school-holidays block into the right column, directly under
  public holidays, instead of full-width below everything.
- Add the school-holidays section to the mobile Vacay settings sheet.

* feat(vacay): modern school-holiday day styling + combined hover tooltip

- Replace the stacked 2px underlines with a single rounded accent band and a
  soft background wash on plain school-break days (desktop + mobile — mobile
  did not render school holidays at all before).
- Fold school holidays into the custom day tooltip under a divider instead of a
  separate native title, and show the tooltip on every set day (whole vacation
  days too), not only half days / shared calendars.

---------

Co-authored-by: Otto <uif86164@automotive-wan.com>
Co-authored-by: Maurice <mauriceboe@icloud.com>
2026-07-23 22:48:42 +02:00
SimMesg20 4d3d16389e Feat/journey external picture provider rework (#1632)
* feat(journey): add contextual external photo browser

* fix(Journey) added missing language keys for new Journey feature

* fix(Journey) fixing missing gps requests aswell as added protection against duplicate entries
2026-07-23 21:35:12 +02:00
Maurice 8855b5581d feat(places): planned pool filter + hotel-transfer day routing (#1666)
* fix(dayplan): expose route tools on a hotel-to-hotel transfer day (#1297)

* feat(places): add a Planned filter to the places pool (sidebar, map, mobile)

* fix(places): stretch the mobile pool filter chips to fill the row
2026-07-23 21:28:00 +02:00
Maurice c5360be242 feat(dayplan): choose the travel mode per segment (#1281) (#1651)
* feat(dayplan): persist per-leg and per-day travel mode (#1281)

The day-plan route used one transport mode for the whole day, held only in
ephemeral state. Store it instead: leg_transport_mode on an assignment is
the mode of the leg leaving that stop (null = inherit the day default), and
days gains a default_transport_mode. Both nullable, so existing itineraries
are unchanged.

Adds PUT /assignments/:id/transport and PUT /days/:id/transport — their own
endpoints so setting the day default can't wipe notes/title the way the
general day update would — mirroring the assignment_time path.

* feat(dayplan): choose the travel mode per segment (#1281)

Each leg between two stops now routes with its own mode, so a day can mix
walking, driving and plugin-routed legs. Tap the connector between two
places to change that leg's mode (desktop popup / mobile sheet); the
whole-day Foot/Car picker sets the persisted day default and leaves
explicitly-set legs alone. The connector and map redraw in the chosen mode,
and each route segment is tagged with the mode it was drawn in.

New strings across all 23 locales.
2026-07-21 23:36:53 +02:00
Maurice ac723744ab feat(collections): add places by coordinates + sort A–Z (#1647)
* feat(collections): add places by coordinates and sort the list

Raised in #1640: collections behaved differently from trips — you
couldn't add a place by typing coordinates, and the list had no order
control.

The add-place modal and the place detail sheet now take an editable
address plus latitude/longitude, with a "lat, lng" paste shortcut, so a
place can be saved by GPS without searching. The place update path
learned to persist coordinates, so an existing one can be corrected
later.

A sort control in the filter row toggles between the saved order and
alphabetical by name. The add-place button moved into that row to match
the other controls, and the add-label button dropped its dashed one-off
styling. New strings land across all 23 locales.

* feat(collections): make the add-place button icon-only

Keep the filter row compact — the button carries just the plus icon (with
an aria-label/title), squared off to match the dropdown height.
2026-07-21 22:10:41 +02:00
Maurice 399e3573c1 feat(places): collaborative place ratings + collections MCP (#1435) (#1644)
Trip members can rate a place 1-5 stars; the place shows the average,
with a who-voted custom tooltip and a star-sort toggle in the places
sidebar. Saved collection places are ratable too (plus a min-rating
filter), and ratings travel with a place copied between a trip and a
collection — carrying only the votes of users who belong to both sides.

Adds a full Collections MCP surface (list/get, CRUD lists + places,
status, rate_collection_place, labels, sharing, copy-to-trip) under a
new collections:read/collections:write scope, plus rate_place for trip
places; rating aggregates flow through the existing place outputs.

Mobile trip + collection place sheets gain the rating row. New UI
strings translated across all 22 locales.
2026-07-21 21:00:26 +02:00
Konstantinos Thermos 88cb7f22dd fix(budget): match the add-expense input decimal separator to the list (#1645) 2026-07-21 20:36:47 +02:00
Maurice b8728078c4 feat(reservations): add a parking booking type (#1444) (#1642)
* feat(reservations): add a parking booking type (#1444)

* feat(reservations): parking type option, icon and color across the UI

* test(reservations): cover the parking booking type

* docs(reservations): document the parking booking type
2026-07-21 16:02:53 +02:00
Maurice 3678761a2d feat(places): custom place image (#1136) (#1641)
* feat(places): upload a custom place image on the server (#1136)

* i18n(places): custom place image labels

* feat(places): click a place thumbnail to set a custom image

* feat(places): custom place image on mobile place and collection sheets

* test(places): cover custom place and collection image upload

* docs(places): document custom place images

* fix(places): carry osm_id on the embedded place so day-plan thumbnails auto-fetch (#1136)

* feat(places): custom tooltip on the place image controls

* fix(places): show a custom place image on map markers, skip the redundant photo fetch (#1136)
2026-07-21 15:33:31 +02:00
Maurice 31f6ec1f47 feat(vacay): read-only calendar sharing (#444, #667) (#1637)
* feat(vacay): read-only calendar shares on the server (#444, #667)

* feat(vacay): shared calendars card and ring overlay on desktop

* feat(vacay): share sheet and calendar rings on mobile

* test(vacay): cover the calendar share flows

* docs(vacay): document read-only calendar sharing
2026-07-21 13:46:38 +02:00
Maurice 7528706d00 feat(vacay): half vacation days (#552) (#1631)
* feat(vacay): support half vacation days (#552)

Log a vacation day as a half day (0.5) as well as a full day and count it
as such against the entitlement. A "Half day" mode joins the calendar
toolbar on desktop and mobile; half days render as a diagonal half-fill in
the day cell, and the used/remaining totals carry the fraction.

Closes #552

* refactor(vacay): half-day as a per-person toggle with a corner badge

The diagonal half-fill collided with the two-person split — one person's
half day looked like two people off. Drop the diagonal: the cell fill keeps
showing who is off, a half day gets a small corner ½ badge, and a hover
tooltip lists who is off on that date and how much (only when a half day is
present). Logging moves from a third mode to a Half-day toggle that applies
to the selected person, on desktop and mobile.

* feat(vacay): mark half days with an orange corner dot

Swap the ½ badge for a small orange dot in the bottom-right corner, mirroring
the blue trip dot — quieter in the grid, and the hover tooltip still spells
out who is on a half day.

* fix(vacay): make the fraction migration idempotent

The crosswalk test replays the migration tail (rewind + re-run), which expects
every later migration to be idempotent. Guard the vacay_entries ADD COLUMN
behind a pragma_table_info check so the second run is a no-op. Also only set
dayVisual's `half` flag when true, so full days keep their exact
{ background, numColor } shape for the mobile day-model unit test.

* style(vacay): nudge the mobile day dots slightly inward

The blue trip dot and orange half-day dot sat right in the cell corner on
mobile; move both a touch toward the centre so they read cleaner.
2026-07-20 23:38:47 +02:00
Konstantinos Thermos a3cb783df7 fix(settings): retry settings load on reconnect so region prefs stop resetting (#1626) 2026-07-20 21:02:19 +02:00