feat(web): adopt TRM design-system tokens (tokens only)

Linked Claude Design project "TRM — Tracking & Race Management" is a
race-timing kit, not a parking design. Adopted its TOKENS only — no TRM
components. Aligned the existing term-* accents onto TRM's exact night/
semantic values (surfaces → night scale; amber→#f2a516, green→#2e8c4a,
red→#e8412b flag, cyan→#2563c8 blue) so the whole booth UI shifts palette
with zero component edits. Exposed TRM's full vocabulary (night/ink/paper
scales, flag/amber/green/blue, viz-1..8, 4px spacing, type scale, square
radii, sharp offset shadows) as Tailwind v4 utilities for new work.

Offline appliance: dropped TRM's Google-Fonts @import (no runtime network);
Goldplay display face not self-hosted yet — falls back to a sans stack.

Web build green; login renders on the new palette.

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
2026-06-18 19:46:56 +02:00
parent dfa76346d6
commit 91cc79b14e
3 changed files with 170 additions and 17 deletions
+6 -1
View File
@@ -24,7 +24,12 @@ The operator UI outgrew "plain React + useState" once it needed live updates and
the latest pushed occupancy. Anything durable is re-fetched via Query.
- **Tailwind v4** with a **"Bloomberg-terminal" theme** (`apps/web/src/index.css`, `@theme`):
near-black surfaces, amber/green/red/cyan status accents, monospace, dense/keyboard-first. **Radix**
primitives (Dialog, etc.) for accessible unstyled components.
primitives (Dialog, etc.) for accessible unstyled components. The token VALUES are adopted from the
**"TRM" design system** (Claude Design project; race-timing kit) — **tokens only**, no TRM
components: the `term-*` accents are aligned onto TRM's `night`/semantic colours and TRM's full
vocabulary (night/ink/paper scales, flag/amber/green/blue, the spacing/type/shadow scales) is
exposed as utilities for new work. Offline appliance ⇒ no webfont `@import`; Goldplay (TRM's display
face) not self-hosted yet — display text falls back to a sans stack.
- **react-i18next** for [[i18n]] (Albanian default).
> This SUPERSEDES the original "plain React, no framework" note on [[react-vite-spa]] — that held
+10
View File
@@ -854,3 +854,13 @@ The version selector picks "latest tariff_version with effectiveFrom ≤ session
The owner asked for "first N hours × X, next N hours × Y, …, 24h cap" — which the stepped-block engine ALREADY does (ordered blocks, per-block rate, rolling-24h cap; computeFee tested). So no new axis: the work was making the model complete + footgun-free. Two changes. (1) **Forbid a bounded last block** — `validateTariffStructure` (shared) now rejects a final block with a non-null `uptoMin`, so the "thereafter" rate is always explicit; previously a bounded tail silently inherited its own rate past its bound (a hidden, never-stated price — e.g. the live ALL tariff's 180-min last block billed hour 4+ at the 3rd-hour rate). `rateAt()` still prices legacy bounded-tail versions gracefully and validation is publish-only, so immutable published versions are unaffected (no migration). (2) **Composer edits bands as a DURATION in hours**, not cumulative minutes — `BlockForm` carries `hours`; `toStructure` accumulates into cumulative `uptoMin` minutes; the last row is a pinned, non-removable, hours-less "thereafter (open-ended)" band; `blocksToForm` round-trips stored minutes back to band hours (legacy bounded tails still load). i18n: replaced `upToMin`/`egExample` with `bandDuration`/`hoursUnit`/`egHours` in sq+en (catalog parity green). Verified: validator rejects bounded-last / accepts open-ended; computeFee correct at 1/2/3/5/6/24h for a 0-2h@200,2-5h@100,5h+@50 + 1000 cap card. Full build green (shared/server/web). Updated [[tariff]]. No migration.
NB considered-and-rejected: time-of-day / weekday wall-clock tiers ("timeframe") were offered but the owner explicitly chose the elapsed-duration ladder only — see [[tariff-time-tiers]] for the deferred wall-clock axis.
## [2026-06-18] feat | Tariff V2 — legacy-parity pricing (time-of-day, category, seasonal, flat)
Brought the legacy ParkSQL2017 pricing BREADTH onto our engine (keeping integer-minor-unit money + immutable signed versions; rejecting legacy float money / mutable rows). `TariffStructure` is now a discriminated union: V1 = the original bare ladder (UNCHANGED, verbatim algorithm — golden-regression-tested against the live production version); V2 = `{version:2, tz, <shared knobs>, defaultCard, windowedCards[]}` where each card is flat OR a block ladder and may be scoped by wall-clock hour window / day-of-week / date range / vehicle category. computeFeeV2 prices by stepping one increment at a time, advancing the ladder by ELAPSED minutes (continuous) while selecting the active card by WALL-CLOCK time in the version's FROZEN tz. Decisions (with user): tz is a per-site setting (site_config.timezone, default Europe/Tirane) stamped server-side into each version on publish — never the host clock (reproducibility); default-card cap governs a mixed day; precedence = specificity (date>dow>hour) → priority → name (total, order-independent), validation rejects ambiguous ties; category = a card FIELD (not tariff scope), frozen in the signed vehicle_entry payload (site_config.default_vehicle_category default), read at both pricing call-sites. Composer: default card front-and-centre (flat/ladder toggle), tiers under an "Advanced" disclosure (window builder), emits BARE V1 when no tiers (back-compat). DB: migrations 0005 (timezone) + 0006 (default_vehicle_category) — applied to the live DB (backed up). Stood up vitest in @parking/shared (was zero tests on the ledger-feeding fee fn); 36 tests incl. golden V1 regression, happy-hour/overnight/dow/flat/category/cap edges, precedence shuffle-invariance, Europe/Tirane DST determinism, validation matrix — all green. Full monorepo build green; V2 publish verified end-to-end via inject (tz stamped from config not client; malformed V2 rejected). Updated [[tariff-time-tiers]] (status open→settled, as-built), [[tariff]], index. No event-chain change.
NB incident: the running tsx-watch dev server (server + vite) crashed mid-edit on a half-saved file + a schema column the live DB lacked; recovered by finishing the edits, applying the migration to the live DB, and restarting both watchers. Live DB backup left at apps/server/parking.sqlite.bak-*.
## [2026-06-18] feat | Booth UI — adopted "TRM" design-system tokens (tokens only)
The owner linked a Claude Design project (`019ddfee-…`, "TRM — Tracking & Race Management") and asked to implement its designs in `apps/web/`. TRM is a RACE-TIMING design system (dashboard/leaderboard/marketing/mobile kits: RaceControl, HeroClock, LiveTable, BibCard, Ticker) — NOT a parking design; literally porting it would have reskinned the booth UI with race components. Flagged the mismatch; owner chose **tokens only**. So: brought TRM's token VOCABULARY into the Tailwind v4 `@theme` (`apps/web/src/index.css`) and ALIGNED the existing `term-*` accents onto TRM's exact values — surfaces → TRM `night` scale (#0b0d10/#14171c/#1e222a/#2a2f38), amber #f5a623→#f2a516, green #2ecc71→#2e8c4a, red #ff4d4f→#e8412b (flag), cyan #38bdf8→#2563c8 (blue). No component touched (170+ `term-*` references resolve unchanged). Also exposed TRM's full vocabulary as utilities for new work: night/ink/paper scales, flag/amber/green/blue + tints, viz-1..8, the 4px spacing scale (s0..s13), type scale (overline..jumbo), square radii, sharp "printed" offset shadows, control/table-row heights. Offline-appliance constraint ⇒ deliberately did NOT keep TRM's Google-Fonts `@import` (no runtime network); Goldplay (TRM display face) left un-self-hosted — display/heading falls back to a sans stack (mono is the booth's primary face anyway), noted for later wiring. Verified: web build green; login renders on the new palette (amber focus ring = #f2a516). Updated [[booth-console]]. No logic/schema/event-chain change.