Rework paid-but-grace-expired sessions and add booth filters.
Overstay (was "stuck"):
- Stop silently aging out a paid transient whose walk-back grace lapsed with no
signed exit. Keep it listed with an OVERSTAY badge — a new parking period began
(re-parked) or the car is faulty/abandoned; it is not a system fault.
- No free exit: reopenBarrier refuses server-side once a transient's payment grace
has expired (allow only subscription OR paid-and-within-grace); the UI hides the
Open-barrier button on overstay rows and routes to the pay/exit modal. Closes a
hole where a stale payment authorized a free multi-day exit (operator-as-adversary).
- Price the overstay as a NEW period from grace-expiry -> now with its own daily-cap
ladder, NOT "full stay minus paid" (which a daily cap collapsed to 0 — ticket
1245791632490 owed ALL 0; now owes its real overstay). quote() gains periodStart +
overstay; SessionLookup/ActiveSession gain `overstay`. handlePayAndExit charges
whenever the session is payable (was: only if !alreadyPaid, skipping the overstay).
Filters (new ui/FilterBar): Active Sessions — search + status
(unpaid/paid/exiting/overstay) + transient-vs-subscriber. Live feed — search +
event (entry/exit/pay/void/anomaly) + direction + source (booth=manual vs reader).
All client-side over already-fetched data; matched/total count shown.
i18n parity (sq+en). Wiki: booth-exit-flow updated (overstay model, naming history,
no-free-exit security fix, new-period pricing; open question on grace-renewal noted).
Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
The ANPR plate was saved (device_events kind:"read") but had no UI. Extend
GET /api/snapshots/by-identity/:identity to also return plates[] (plate, confidence,
region, direction, snapshotId, at) for that session, and render each as a cyan
"Plate: AA558EE 100%" chip in the SnapshotStrip — so it shows in both the booth
event-detail modal and the pay modal, beside the evidence photo, no separate screen.
Deduped by plate+direction; session:read gated; i18n sq+en.
Verified: by-identity returns plates[] for a seeded read (200, AA558EE 0.999 Albania
entry). Build + lint green.
Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
Make the vision service genuinely configurable (was env-only).
- SetupWizard: an "ANPR" checkbox on the camera form (writes config.anpr; persisted
only when on; sq+en) — opt-in is no longer raw JSON.
- DeviceMonitor optionally takes the VisionClient and probes /health each tick, emitting
a "vision" pseudo-device → a Vision chip (ready/degraded/offline + recognizer) in the
booth footer when VISION_ENABLED, no chip when off. Widened the DeviceStatus category
union (server + web) + footer maps + devices.catVision. Verified: ready/fast_alpr when
up, 0 chips when disabled.
- apps/vision/.env.example (Python service) + a VISION_* block in apps/server/.env.example
(Node side) + a Configuration section in opencv-anpr-service.md covering all four
layers and the caveats: the two processes share the VISION_ prefix but need SEPARATE
.env files; bind /analyze to 127.0.0.1; cache model weights at deploy; an unbound anpr
camera recognizes but every read is refused.
Build + lint green.
Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
The live activity feed flagged anomalies with no explanation and showed
opaque session keys. Make events self-describing and clickable.
- Clickable feed rows → read-only event-detail modal: humanized fields,
entry/exit snapshots, and signed-chain provenance collapsed behind an
audit disclosure (operator sees the story, auditor expands for crypto).
- Localized reason codes (backend i18n): the signed ledger now carries a
stable REASON_CODE + params (+ English fallback) instead of free-text
English. The UI translates via reason.<code> catalogs in sq/en, so an
Albanian operator reads Albanian — from the same immutable event. Adding
a language is a catalog change, no re-signing. (@parking/shared
REASON_CODES, reasonPayload; entry/exit/subscription flows emit codes.)
- Subscriber-name resolution: a SUBSESS-… occurrence now shows the
subscription holder's name (fallback "Abonent"/"Subscriber"). Resolved
read-time server-side (events API + WS push) as a non-signed
subscriberLabel; cached with invalidation on subscription edit/delete.
- Failed-snapshot visibility: a camera that was attempted but unreachable
now shows a "⚠ camera unreachable" tile instead of a silent gap. The
snapshots API returns failures[] from telemetry, filtered so a recovered
capture shows no stale warning.
Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
Add a reusable ui/Modal (Radix Dialog + terminal chrome) and move the
add/edit forms in the Devices setup, Subscriptions and Roles screens into it,
leaving each list in the page behind the modal. The Devices wizard's per-
category device form is also fully translated (setup.* i18n keys).
Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
Generalise printer-only monitoring to every configured device. New
DeviceMonitor polls all enabled devices each tick (default 8s): printers
via rich readStatus(), relays/readers/cameras via the generic healthCheck()
reachability probe, flattened to one traffic-light (ready/degraded/offline)
+ detail, deduped (emit on change only), fail-toward-offline.
- device-status bus event + GET /api/devices/status snapshot.
- Pushed over the existing /api/ws (hello carries the initial set;
device-status frame per change).
- Web: live-store devices map, WS handler, DeviceFooter chip-per-device
(role label not vendor; click a degraded/offline chip for an issues panel).
Verified roleKind resolution + change-only emit on a fresh DB.
Note: the footer's UI surface (api type, router mount, i18n devices) rides
in the subsequent subscription commit due to shared-file overlap.
Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
Add react-i18next with two key-parity-checked catalogs (sq default/fallback, en).
Active language driven by the logged-in user's stored preference (applied after
/me resolves); SQ/EN toggle in the header persists via PUT /api/auth/language.
Translate the booth (screen, pay/exit modal, active sessions, snapshots, status),
Login, ShiftControl, SiteSettings, PermitManager, TariffComposer.
SetupWizard deferred (its content is server-provided; needs backend catalog i18n).
Add tailwindcss (Bloomberg-terminal theme in index.css), @tanstack/react-query +
react-router, zustand, and Radix primitives. Router with role-guarded routes;
QueryClient wrapping the existing apiFetch; a small Zustand live store fed by a
/api/ws client that invalidates Query caches. Booth screen: live occupancy gauge
+ streaming entry/exit/payment feed. Vite proxies the WS upgrade.
Note: BoothScreen references the pay/exit modal + active-sessions panel added in
following commits; final HEAD builds.