docs(wiki): record session findings — snapshot fix, booth rework, db reset
Build desktop / desktop (push) Successful in 4m34s
Build & push images / images (push) Successful in 3m1s
CI / check (push) Successful in 40s

- entry-exit-points.md: the snapshot content-type bug + serve-side cleanType
  fix (Hikvision image/jpeg; charset="UTF-8" broke every legacy render).
- booth-exit-flow.md: the Active-Sessions/modal rework — inline barrier button
  removed -> modal; closed-within-grace view; live grace countdown; actual paid
  amount; read-only snapshot review in the closed-session view.
- local-dev-workflow.md: the gated `pnpm db:reset` training tool + flag table +
  the booth (docker exec, no pnpm) note.
- appliance-provisioning.md: new §7d — reset on the booth via docker exec into
  the server container (script ships in the deploy bundle; DATABASE_URL=
  /data/parking.sqlite), ledger-truncation warning + the two safety gates.
- index.md catalog line; log.md entries.

Claude-Session: https://claude.ai/code/session_01Xcm6ikLgGoCxxHrxtjkk5V
This commit is contained in:
2026-06-30 17:58:43 +02:00
parent d92b8d1e6a
commit 266e9b0027
6 changed files with 158 additions and 4 deletions
+46
View File
@@ -2028,3 +2028,49 @@ Boot Deployed, GRUB edit-lock, unprivileged-operator) + the firmware/dbx lockdow
with its load-bearing nuance, plus the standing caveat that this is the SECONDARY control —
reconciliation over the signed chain is the main anti-fraud event. Commands stay in appliance-provisioning
(the how); this page points there. Updated the index.md line accordingly.
## [2026-06-30] fix | Snapshot content-type bug — every legacy image rendered blank
Symptom: no snapshot showed in the booth modal. Root cause: Hikvision-style cameras return
`Content-Type: image/jpeg; charset="UTF-8"` (a charset param on a binary body = malformed; browsers
refuse to decode an <img> declared that way). Old capture code persisted that raw header into
snapshots.content_type (100 of 101 dev-DB rows); the serve route GET /api/snapshots/:id re-emitted it
verbatim → broken render for every legacy row. Capture was already hardened (encodeForStorage →
clean image/jpeg, fail-soft cleanType), but the serve route trusted the stored value. Fix: route now
runs cleanType(row.contentType) on the way OUT too → bare image/jpeg, un-breaks all legacy rows with
NO data migration. Verified via Playwright: a previously-unrenderable 2560×1440 row now decodes
in-browser; clean + malformed rows both load. Exported cleanType from snapshot.ts + unit tests.
Recorded in entry-exit-points.md. Lesson: normalize a device-supplied content-type on capture AND on
serve (a stored value from an untrusted camera is itself input).
## [2026-06-30] feat | Booth Active-Sessions + pay/exit modal rework
(1) The inline "Open barrier" button on paid-in-grace Active-Session ROWS was removed; the audited
re-pulse now lives only in the modal. Reason: a paid+exited session is open=false, so clicking its
row dead-ended on "already closed" — useless for the exact case (paid, barrier unconfirmed) that
needs a re-pulse. The modal now recognizes closed-within-grace (found && !open && withinGrace) and
shows the session view + Open barrier. Server reopenBarrier guard unchanged (already handled the
closed-in-grace case — the T-397815c0 fix). (2) Active-Sessions rows show a LIVE grace-remaining
countdown badge (exited · M:SS, 1s tick off graceExpiresAt) instead of a static label. (3) Settled
sessions show the ACTUAL sum paid (new SessionLookup.paidMinor, summed across payment events) not a
flat "PAID". (4) A fully-closed (grace-expired) session's modal is no longer a dead-end: it shows a
read-only review view (figures + paid amount + entry/exit snapshot strip) for dispute/audit review,
with no pay/exit/open controls. i18n sq+en parity kept; web build/tests green. Recorded in
booth-exit-flow.md.
## [2026-06-30] feat | DB reset CLI for training/demo (packages/db/scripts/reset-db.mjs)
A site is sometimes run live to train operators/admins; afterwards the demo data must go WITHOUT an
obvious self-serve button (operator must not wipe history). So: a CLI script `pnpm db:reset`, not UI.
Category flags grounded in the table map — --financial (ledger/telemetry/snapshots/subscription
instances/blocklist; keeps users/devices/config/tariffs/plans), --config, --users, --all. Because
shifts/cash/payments all live as event types INSIDE the hash-chained ledger_events, "financial" =
truncate the whole signed ledger back to empty (re-seed starts a new chain under the SAME
EVENT_SIGNING_KEY — key untouched). Two safety gates (decided with user): RESET_ALLOWED=1 env (real
booths never set it) + typed DB-filename confirmation (--yes skips for CI). Single txn + VACUUM;
re-seed admin after --users/--all. On the BOOTH there is no pnpm — only containers — so it runs via
`docker exec` into the server container (node node_modules/@parking/db/scripts/reset-db.mjs,
DATABASE_URL=/data/parking.sqlite); the script ships in the deploy bundle next to the boot migrator
(@parking/db has no `files` allowlist → whole pkg copied). Verified on throwaway dev-DB copies (both
gates refuse correctly; each flag wipes/keeps the right tables; real dev DB never touched). Recorded
in local-dev-workflow.md + appliance-provisioning.md §7d.