docs(wiki): record session findings — snapshot fix, booth rework, db reset
- 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:
+46
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user