Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
103 changes: 103 additions & 0 deletions docs/devlog/DEVLOG-flow4d-wayfinding-resilience-2026-07-27.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# DEVLOG β€” Flow-4D Phases E (wayfinding) + F (resilience)

**Date:** 2026-07-27
**PRs:** #107 (Phase E), #108 (Phase F) β€” both squash-merged
**Deployed:** prod at `17dd51c6` via `./deploy.sh --frontend` (login 200 + fresh
`NavigatorScene`/`PatientFlowNavigator` chunks verified serving)
**Plan:** `docs/plans/FLOW-4D-PATIENT-JOURNEY-AND-CONFORMANCE-PLAN-2026-07-26.md`

Sixth and seventh phases of the journey/conformance program. Both are
navigator-side only (the dark-only wall instrument, HFE F-9) β€” no backend,
schema, or flag changes. Built on A/B/C already on prod.

## Phase E β€” Wayfinding & 2D structure (PR #107)

- **E5 URL view state + TN-6 presets.** `viewState.ts` serializes
camera/floor/layers/time/census/window/selection to URL params, pinned by a
seeded 200-view round-trip. A link can *request* but the lens/flags still
decide what renders. A patient travels only as the opaque `patient=ptok`;
`sel=` carries aggregate kinds only. Copy-view-link in the toolbar +
copy-as-link on saved-view slots. Chronobar window presets (48h/24h/6h/Shift);
historical sources keep their extent window.
- **E2 van Wijk flight + canonical views + SDF labels.** `cameraFlight.ts` is
the pure van Wijk & Nuij optimal pan/zoom path (ρ*=1.42, 8 property tests);
`focusOn`/`flyTo`/`flyToHome`/`focusTopDown` fly the arc and operator input
cancels it. Top/House/Floor/Bed canonical framings. SDF unit-name billboards
via `troika-three-text` (new dep, local ambient `.d.ts`) β€” per-frame billboard
+ LOD scale + distance cull, tied to the Model layer.
- **E3 ortho mode (`O`) + structure traversal.** Perspective/ortho camera swap
beneath OrbitControls β€” raycast/flight/billboards all read the active camera;
ortho is locked plan view (pan+zoom); Home/bookmarks/links restore the iso
perspective. `structureTree.ts` (pure floor→unit→bed) + `NavigatorStructureNav`
(WAI-ARIA tree, roving tabindex, arrow-key graph) selecting through the shared
`selectEntity` seam.
- **E1 minimap.** `minimapProjection.ts` (pure world-XZ ↔ minimap round-trip) +
`NavigatorMinimap` SVG plate β€” location dots, delay/deviation pips
(shape-coded, CVD-safe), camera footprint, selection ring; click flies to the
point. Polls camera+selection from the scene so the ~7 Hz camera stream never
re-renders the orchestrator. Suppressed on `?wall=1`.
- **E4 flatten + small multiples.** `trailHeat.ts` (density grids + hourly
slices) + `NavigatorSmallMultiples` β€” six hourly density plates (the analysis
path replacing forced replay) + SVG export + an on-floor GSTC flatten (transient
scene layer). Density is opacity on one cool ink, never a status color.
- **E6 task modes.** Monitor / Investigate / Rounds progressive disclosure of the
ballooned toolbar (WN-6). Monitor (default) is the resting layout minus the
investigation kit; Investigate adds Speed/Find/saved-views + the small-multiples
card; Rounds foregrounds the walk and steps back the census rollup.

## Phase F β€” Resilience & scale (PR #108)

- **F5 rendered-scene browser test (landed before F2).** jsdom can't run WebGL,
so the scene lifecycle is only testable in a real browser:
`tests/e2e/patient-flow-navigator.spec.ts` covers boot, **GPU context-loss
self-heal (F1)**, frame-budget accumulation (F4), and a layer rebuild keeping
the renderer alive. Graceful `test.skip` when CI lacks WebGL or flow data (the
E2E seed carries no patient-flow data β€” the model boots into an empty data
layer); auto-strengthens when an E2E flow seed lands.
- **F1 GPU context-loss recovery.** `webglcontextlost` preventDefault + halt the
loop + amber "Graphics paused" card; `webglcontextrestored` β†’ resume + a full
layer rebuild from state. Soak hook counts losses/restores.
- **F4 frame-budget instrumentation.** Per-subsystem p95 ring buffers
(`performance.now` deltas) exposed on the soak hook for the H4 RAIL budget.
- **F2(b) aggregate-trails guard.** Above 50 concurrent trails the per-patient
spaghetti collapses to unit→unit flow arrows with counts; the selected patient
keeps their own trail (Β§6.2). Pure `flowAggregation.ts` tally (5 tests).

### Deferred β€” F2(a) instancing + F3 deltas

The InstancedMesh migration is a perf refactor for a token scale the demo
(~40 patients) doesn't reach β€” deferring aligns with "no premature optimization."
Critically, F5's **selection-survival** check can't execute in CI without an E2E
flow seed, so merging a large token-rendering refactor whose one real safety net
can't run would be an unguarded change to a wall instrument. Recommendation:
land F2(a)+F3 as a focused follow-up **together with an E2E flow seed** so F5
verifies instanced selection/highlight through rebuilds in CI. F3's `?since=`
web-delta path is coupled to the instance-buffer rebuild strategy and rides along.

## Verification

- `tsc --noEmit` clean; `vite build` clean (troika bundles into the lazy
`NavigatorScene` chunk).
- patientFlow vitest: **221 passed** across both phases.
- `scripts/check-ui-canon.sh` passed (raw-palette ratchet ≀ 76).
- `playwright --list` resolves the 4 rendered-scene tests.
- Prod deploy verified: login 200 + new build chunks serving.

## Notes / gotchas

- **Concurrent-session shared-checkout hazard** recurred: a second session kept
flipping the deploy-staging checkout between `main` and a feature branch
mid-edit. All of E-later + F was done in an isolated worktree
(`~/Github/Zephyrus-wf`, `ln -s ../Zephyrus/node_modules`), committing each
sub-step. Because E was squash-merged, F was rebased with
`git rebase --onto origin/main <E-tip>` to carry only its own commits.
- The deploy staging checkout (`/home/smudoshi/Github/Zephyrus`, the ssh target's
canonical checkout) had been left on the merged feature branch; `deploy.sh`
requires it on `main`/clean, so it was switched back before the release.
- beastmode grep treats the arrow-heavy `NavigatorScene.ts` as binary (locale) β€”
use `grep -a`.

## Remaining

Phase D (DRG catalog bridge, clinical-gated), Phase G (H3/H4 field validation),
and the F2(a)/F3 instancing follow-up (with the E2E flow seed).
Original file line number Diff line number Diff line change
Expand Up @@ -269,23 +269,24 @@ Everything here ships **dark** behind the existing deferred serving-flag family
- [ ] Tests: compiler determinism + digest pinning (a version's compiled model is content-addressed), flag-off inertness (byte-identical responses), append-only discipline untouched
**Acceptance:** a governed pathway version compiles to a conformance-checkable reference model with a coverage report; demo overlay demonstrates the full loop on synthetic data; nothing clinical serves anywhere with flags off β€” proven by tests, asserted with G-9's convention (no inferring clinical approval).

### Phase E β€” Wayfinding & 2D structure (~4 d) `feature/flow4d-wayfinding` [C]
- [ ] E1 Minimap/floor-stack widget (camera footprint, selection dot, delay/deviation pips; click = fit-to-floor); CVD + never-color-alone treatment; suppress in wall/kiosk param
- [ ] E2 Canonical views control (Top/House/Floor/Bed) + van Wijk-arc verification/upgrade of `focusOn`/`flyTo` transitions; SDF-text unit billboards w/ LOD + distance culling (P4 backlog item, now evidence-backed)
- [ ] E3 Top-down orthographic mode (`O`) — all layers; disks/labels legibility pass; Data-Navigator-style structure traversal (floor→unit→bed arrow-key graph) layered on the existing action-list seam
- [ ] E4 Flatten + small multiples: "Trail heat (last 6 h)" scene mode (GSTC flatten) and a 6-slice hourly small-multiples export card (analysis path replacing forced replay)
- [ ] E5 URL view state + "Copy view link" (camera/floor/layers/time/selection/censusScope; extends parseHandoff; saved views gain "copy as link")
- [ ] E6 Task modes Monitor/Investigate/Rounds (progressive disclosure; `NavigatorToolbar` decomposition β€” the renderToolbar test fixture must gain the mode prop; WN-6)
- [ ] Tests: mode-gated control visibility, URL round-trip property test, minimap sync, ortho toggle, LOD label culling thresholds
**Acceptance:** an operator can always answer "where am I / what am I looking at / how do I get back" in one glance + one click; every monitoring answer has a non-perspectival path; any exact view is shareable as a URL.

### Phase F β€” Resilience & scale (~3–4 d) `feature/flow4d-resilience` [C]
- [ ] F1 Context-loss recovery (absorbs Codex F-11): `webglcontextlost` preventDefault + halt + visible degraded card; `webglcontextrestored` β†’ full GPU-resource rebuild from state (scene state already lives outside GL); deterministic test via `WEBGL_lose_context` in the browser test; soak hook counts losses/restores
- [ ] F2 InstancedMesh migration for tokens/pips/ghosts/deviation glyphs (per-instance color; raycast via instanceId; selection/highlight adapted β€” SelectionEntity API unchanged); aggregate-trails guard: >50 concurrent trails β†’ unit-to-unit flow arrows w/ counts, individual trail on selection (Β§6.2)
- [ ] F3 Delta updates: pre-sized instance buffers + `needsUpdate` paths replace group clear+rebuild for token/heat layers; epoch rebootstrap (A3) remains the only full-rebuild trigger; web read path adopts `?since=` deltas where the endpoint offers it (DI-4)
- [ ] F4 Frame budget: `performance.measure` marks per subsystem (tokens/trails/overlays/labels), p95 exposed via soak hook; RAIL-derived budget table recorded in this doc when measured
- [ ] F5 Rendered-scene Playwright test (absorbs Codex F-12): boot demo scene β†’ select β†’ force layer rebuild β†’ assert highlight survives; epoch rebootstrap path; context-loss path; runs in the existing browser CI job
**Acceptance:** wall survives GPU reset with self-recovery; draw calls flat vs entity count (instancing proven by soak counters); browser CI covers the three long-session failure modes end-to-end.
### Phase E β€” Wayfinding & 2D structure (~4 d) `feature/flow4d-wayfinding` [C] β€” **SHIPPED + DEPLOYED PROD (PR #107, squash `5f01dd81`; deployed 2026-07-27 at `17dd51c6`)**
- [x] E1 Minimap/floor-stack widget (camera footprint, selection dot, delay/deviation pips; click = fit-to-floor); CVD + never-color-alone treatment; suppress in wall/kiosk param β€” `minimapProjection.ts` (pure round-trip) + `NavigatorMinimap` SVG plate, polls camera/selection from the scene so the ~7 Hz camera stream never re-renders the orchestrator; `?wall=1` suppressed
- [x] E2 Canonical views control (Top/House/Floor/Bed) + van Wijk-arc verification/upgrade of `focusOn`/`flyTo` transitions; SDF-text unit billboards w/ LOD + distance culling β€” `cameraFlight.ts` (van Wijk & Nuij ρ*=1.42, 8 property tests); `troika-three-text` billboards (local ambient .d.ts)
- [x] E3 Top-down orthographic mode (`O`) — all layers; disks/labels legibility pass; Data-Navigator-style structure traversal (floor→unit→bed arrow-key graph) layered on the existing action-list seam — perspective/ortho camera swap under OrbitControls; `structureTree.ts` + `NavigatorStructureNav` WAI-ARIA tree (roving arrow-key graph, shared selectEntity seam)
- [x] E4 Flatten + small multiples: "Trail heat (last 6 h)" scene mode (GSTC flatten) and a 6-slice hourly small-multiples export card (analysis path replacing forced replay) β€” `trailHeat.ts` + `NavigatorSmallMultiples` (SVG export + on-floor flatten via transient scene layer)
- [x] E5 URL view state + "Copy view link" (camera/floor/layers/time/selection/censusScope; extends parseHandoff; saved views gain "copy as link") β€” `viewState.ts`, seeded 200-view round-trip; `sel=` aggregate-only, patient travels only as opaque ptok. TN-6 chronobar presets (48h/24h/6h/Shift) shipped alongside
- [x] E6 Task modes Monitor/Investigate/Rounds (progressive disclosure; `NavigatorToolbar` decomposition β€” the renderToolbar test fixture must gain the mode prop; WN-6)
- [x] Tests: mode-gated control visibility, URL round-trip property test, minimap sync, ortho toggle (unit); LOD label culling threshold β€” verified in-scene, browser-covered by F5
- *Deferred (plan-consistent, B4): outward "Locate in 4D" buttons on ED/bed boards β€” those rows carry no opaque ptok client-side; needs backend ptok minting + re-id review, scope separately.*
**Acceptance:** an operator can always answer "where am I / what am I looking at / how do I get back" in one glance + one click; every monitoring answer has a non-perspectival path; any exact view is shareable as a URL. **MET** β€” 221 vitest green (viewState/cameraFlight/structureTree/minimapProjection + component suites), tsc + vite build clean.

### Phase F β€” Resilience & scale (~3–4 d) `feature/flow4d-resilience` [C] β€” **F1/F4/F5/F2b SHIPPED + DEPLOYED PROD (PR #108, squash `17dd51c6`, deployed 2026-07-27); F2a + F3 DEFERRED**
- [x] F1 Context-loss recovery (absorbs Codex F-11): `webglcontextlost` preventDefault + halt + visible degraded card ("Graphics paused"); `webglcontextrestored` β†’ full rebuild from state (scene state lives outside GL); deterministic test via `WEBGL_lose_context` in the browser test (F5); soak hook counts losses/restores (`contextEvents()`)
- [~] F2 **F2(b) SHIPPED** — aggregate-trails guard: >50 concurrent trails → unit→unit flow arrows w/ counts, individual trail on selection (§6.2); pure `flowAggregation.ts` tally (5 tests). **F2(a) InstancedMesh migration DEFERRED** — premature optimization at demo scale (~40 pts); more importantly F5's selection-survival can't run in CI without an E2E flow seed, so a large token-rendering refactor would be unguarded. Land with the E2E seed so F5 verifies instanced selection.
- [ ] F3 Delta updates (pre-sized instance buffers + `?since=` web deltas) β€” **DEFERRED with F2(a):** the instance-buffer path depends on instancing; the `?since=` web-delta is a coupled data-path change best landed together
- [x] F4 Frame budget: per-subsystem (frame/render/labels/controls) p95 timing via ring buffers (`performance.now` deltas, no measure-entry churn); exposed on the soak hook (`frameBudget()`); RAIL budget table to be filled from the H4 soak run (G4)
- [x] F5 Rendered-scene Playwright test (absorbs Codex F-12): boot scene β†’ force layer rebuild β†’ renderer alive; context-loss self-heal + counters; frame-budget accumulates; `tests/e2e/patient-flow-navigator.spec.ts` in the browser CI job. Graceful skip when CI lacks WebGL/flow data (E2eTestSeeder carries none β€” model boots into an empty data layer); auto-strengthens when an E2E flow seed lands
**Acceptance:** wall survives GPU reset with self-recovery **(MET, F1+F5)**; browser CI covers the long-session failure modes end-to-end **(MET for boot/context-loss/frame-budget/layer-rebuild)**. Draw-calls-flat-vs-entity-count (instancing soak counters) **carries to the F2(a) follow-up**. 221 vitest green; tsc + vite build clean; playwright --list resolves the 4 rendered-scene tests.

### Phase G β€” Field validation & claims discipline (sessions [SU], synthesis [C])
- [ ] G1 Usability protocol v1.1: add T8 (journey), T9 (adherence SAGAT), keyboard-only journey variant; re-pilot timing (≀35 min)
Expand Down
Loading