feat(canary): manage dossier page + /m/:manageId route + 404 fallback
Manage route renders the per-token dossier — the "specimen catalog entry"
twin of the intake page. Same vector-minimalism mind: archive strip,
big-blocky headline, hairline-rule cards, mono labels, alarm-red only
when the trap is alive (eventsTotal > 0) or disabled.
Page composition:
- Top Strip (FIELD STATION / SECTION / SPECIES pill)
- Hero block — headline state-shifts: QUIET (no events, ink), TRAP TRIPPED
(alarm red), TRAP MUTED (ink-mute). Memo rendered as printer-block quote.
- Halftone separator
- SpecimenCard with armed status / trigger count / silenced (15m window) /
last seen / created / alert channel / filename — plus Trigger URL CopyField
- EventLogSection — cursor-paginated list of EventRow cards (timestamp +
source_ip + geo + ASN + UA + referer + notify_status pill + expandable
fingerprint extra). "Load older →" + "← back to most recent" controls.
- DeleteSection — two-step arm/confirm pattern (no native confirm() dialog),
alarm-tone Button on confirm, success → toast + navigate('/')
- Bottom Strip with "new specimen ↗" link
States: useManageToken loading | ApiError(NOT_FOUND) → ManageNotFound |
other error → ManageError with retry | success → ManageView.
Pagination policy per plan anti-relit: singular useQuery, NO useInfiniteQuery.
Each cursor change replaces the visible page (operator-friendly default).
Auto-refresh every 5s via QUERY_STRATEGIES.frequent (Phase 14 contract).
Routes:
- / → landing
- /m/:manageId → manage (new)
- * → notfound (new — replaces previous fallback-to-landing)
config.ts: ROUTES.MANAGE added; manageRoute(id) helper centralizes the
URI-encoded path so result.tsx and any future caller cannot drift.
notfound page: same ethos in miniature. 404 strip pill, big OFF-ROUTE
headline, link back to intake.
Gates green: typecheck + biome + stylelint + build (414ms, manage chunk
10.9KB / 3.7KB gzip, notfound chunk 1.0KB / 0.5KB gzip).