Codex/ward management design - #2289
Conversation
…nd phase 1 plan Baseline for the Phase 1 model rework. Adds the ward-management surface (command, constellation, network, queue, capacity, movements, exceptions, transport, governance, patient workspace), its synthetic fixtures, the domain glossary and decision records, the metro patient-flow design spec, and the Phase 1 implementation plan. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… eligibility test
…eligibility, shared derivations
The subagent-driven-development ledger lives in git-ignored scratch and does not survive a session. This records what a later session needs: the rulings taken during Phase 1 with what each costs if wrong, the module map, the deferred findings that Phase 4 must not inherit blindly, and the verification lessons from this run. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ules Fixes every finding from the final whole-branch review: gate icons that ignored pass/fail (including MHA authorisation), a five-state bed grid that double-counted held/blocked beds, "open movement" counts that included arrived/closed records, generated movements whose stage contradicted their own fields, negative-instant clock formatting, bed-release blockers that leaked departing-patient detail, a "catchment" label computed from origin ED rather than patient catchment, an "exhausted search" blocker the fixture didn't actually support, a Readiness panel that ticked a breached legal deadline, and an unconditional "eligible candidate" claim. Also cleans up the smaller deferred items: cohort/security gate grammar and pass/fail text, an advertised-but-unrun exception rule, stale glossary prose, and one leftover "AI" label. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The whole-branch review found defects the eight per-task reviews could not see, because each looked at one task's diff. One fix wave closed them. This records what the review caught and why it mattered, the findings parked at the close with the ruling on each, the verification actually run, and the two repo traps that make a green result untrustworthy. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…rief Phase 2 builds the flow coordinator's screen — pressure strip, priority queue, flow diagram, explainable shortlist, exceptions drawer, phone form — and retires Constellation into it. Ten tasks, two of which build the pure derivations the screen reads: the operational score (rebuilt with no urgency component) and per-department pressure. The kickoff brief carries what a cold session needs, including the process calibration for a screen-heavy phase and the three Phase 1 lessons that should shape how it is verified. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
One self-contained file a session can read cold: the problem, the WA clinical and legal grounding, the ten settled scope decisions, the model with every export and identifier, the non-negotiable rules, the repo conventions and the two gates that report success without running, what Phase 1 learned and why it shapes how Phase 2 is verified, and the decisions taken on the owner's behalf. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…nd blocker checks
Review findings against the real fixture:
- Declines detail rendered a self-contradictory fraction ("5 of 3 parallel
referrals declined") — declines.length is cumulative history, the cap
limits simultaneous live referrals, they never shared a denominator.
State only the count.
- Transport delay fired for movements already en route (WF-006, WF-014,
WF-306, WF-313, WF-320, WF-327), contradicting their own blocker text.
Require acceptedAt set and enRouteAt/collectedAt/cancelledAt all unset,
matching buildActionInbox's existing condition, and drop the truthiness
reads on Instant fields in favour of explicit === undefined.
- hasActiveBlocker's "starts with None" match was wide enough to hide a
real blocker like "None of the secure units can take him"; narrowed to
the exact sentinel or "None" + end-of-string/dash/colon.
- queueOrder's exclusion test only asserted a shorter list, which would
still pass if queueOrder dropped everything; pinned to the exact
isOpen-filtered count instead.
- Exported ward-clock's splitDuration (already zero-padding) and reused it
for the wait-time detail instead of a second, non-padded formatter.
- Removed the redundant .slice() after .filter() in queueOrder.
Six new/tightened tests cover the behavioural findings; watched them fail
against the unfixed code before applying each fix.
Adds edPressure(now), sorted worst-first by breaching then longest wait then volume. Reuses ward-clock's clockState for the breach definition instead of re-deriving it inline, and clamps per-movement waits at zero so a movement with a future openedAt can never render a negative wait. The sort test's original array-comparison assertion coerced both sides to strings via `>=`, which would pass a genuinely wrong ordering; it is replaced with an explicit numeric tuple comparison.
…ble tests edPressure(now) imported wardMovements directly, so every assertion was forced to key off the one fixture (every department busy, every wait positive). That let three tests pass under mutations that should have failed them: dropping quiet departments, misattributing counts between departments, and removing the future-openedAt wait clamp. Change the signature to edPressure(now, movements = wardMovements), mirroring queueOrder(movements, now) in ward-priority.ts with now kept first for the existing/planned call sites. Add tests that inject a built movement list to prove: quiet departments still report zeros (never drop, never substitute a different EmergencyDepartment), counts attribute to the correct originEdId rather than a rotated neighbour, and a future-dated openedAt clamps to zero rather than going negative. Also cover the clockState "due exactly now" boundary, and correct the pre-existing longest-wait test's own expectation to mirror the clamp (it previously proved the clamp only by fixture coincidence). Use ward-clock's minutesUntil instead of inline subtraction, matching the existing elapsedLabel pattern. Each of the three structural findings was verified red against its named mutation before being accepted.
…ow, labelling Two Criticals, three Importants, three Minors from the Task 3 review. Criticals: - Wire the unused selectedUnitId setter into a real (if currently unreachable) "Clear unit selection" control in the diagram region instead of discarding it, clearing the npm run lint no-unused-vars failure. - Repointing /ward-management broke five tests in ui-ward-management.spec.ts that still targeted WardManagementConsole. Repaired gotoWardFlow and three specs against the coordinator screen, kept the network-diagram spec untouched, and removed the two specs with no coordinator-screen equivalent yet — replaced by test.fixme placeholders in ui-ward-coordinator.spec.ts naming the tasks that will implement them (5/7 and 8). Importants: - The coordinator spec's overflow assertion measured document.documentElement, which .screen's overflow:hidden makes impossible to fail. Now measures the region grid's own scrollWidth/clientWidth via a testid; proved it goes red against the pre-fix CSS (836px overflow at 320px) before applying the fix. - The region grid had no narrow-layout fallback below its shrink breakpoint and overflowed at 1100/820/390/320. Stacked the three regions into one column below 1440px (min-width: 90rem restores the three-column grid), and separately fixed a blank 72px band at <=640px where the hidden rail's grid track was still reserved. Verified zero overflow at 1600/1280/1100/820/390/320. - "N movements" labelled the open-only queue count as a total; now "N open movements". Minors: - Dropped the inert z-index on the statically-positioned exceptions drawer (its position in .main's row order already pins it) and the now-unused --co-z-drawer token, plus two other declared-but-unused tokens. --co-space-4, --co-space-16. - Added a visually-hidden <h1> naming the screen. Also fixed a wrapping regression the open-movements label change introduced in the 14rem queue column header, caught by re-inspecting the recaptured screenshot. RAG impact: no retrieval behaviour change — this only touches the Ward Flow coordinator screen shell (src/components/ward-management/coordinator/**) and its tests; no file under src/lib/rag/**, retrieval-selection, ranking-config, answer-ranking, the eval harness, or the golden fixture was touched. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…, selection visibility, empty state - Tests could not detect a wrong card order or the queue filtering to the wrong department; both are now pinned by data attributes (data-breaching, data-longest-minutes, data-waiting, data-origin-ed) and asserted directly. - aria-label on each pressure card replaced its whole accessible name, hiding every figure from assistive technology; the name is now composed from the hospital name plus the visible figures. - Selected state was invisible on breaching cards (border/background were overridden by the danger tint); a card outline now survives that and is pinned to Highlight under forced-colors. - A quiet department rendered "0 waiting · longest 0m"; it now shows an explicit "No patients waiting" state, covered by a new dom test. - Removed the click-retry loop around Playwright interactions in favour of a networkidle wait in gotoCoordinator, matching the sibling ward-management spec's helper. - Added the ordering rule to the strip header and cleared the department filter automatically if it ever names a department that cannot be resolved.
…roof, now-prop, origin label, ED lookup hoist Three mutations survived the appended ordering test with the suite green: removing data-tier still passed (empty-array vacuity), inverting the within-tier tiebreak still passed (no machine-readable score on the row), and both a constant score and a deleted breach line still passed. Fixes: - Add a count floor before the tier-sortedness assertion so absence can't satisfy it. - Add data-score to each row and assert non-increasing score within a tier (the property, not the fixture). - Assert the visible score text matches the row's own data-score, and that the breach line is present on a breached-deadline row and absent on a non-breaching one. Also: PriorityQueue now takes now: Instant as a prop (was reading NOW_ANCHOR directly, unlike its now-prop sibling PressureStrip); origin department renders "from JHC" instead of the unlabelled "JHC", which read as a destination next to the patient's required security level; and allEmergencyDepartments() is looked up once per render via a Map instead of once per row.
… as a routed destination Critical 1: eligibleCandidates sorts eligible-first but never filters, so the diagram was drawing solid accent routes and "N shortlisted units" for candidates that had already declined the movement or failed a hard gate (e.g. WF-009: zero of its three nearest candidates were actually eligible). Every shortlisted node now carries data-eligible plus its own candidateReason() text, ineligible routes render dashed and danger-toned, and the hub states the true eligible count instead of the shortlist size. Important 2: destinationUnit() conflated an accepted bed with an outstanding referral into one badge and only looked at referredUnitIds[0]. Split into distinct Accepted destination / Outstanding referral badges, checked directly against the movement's own fields, so every parallel referral is visible. Important 3/4: the routed-set and route-connector assertions now hold for a second movement with a different shortlist (WF-009), and route connectors are marked data-connector-kind so they're provably distinct from demand connectors, closing the gaps a single-movement proof and an unmarked connector left open. Important 5: routed state now reaches the accessible name as real text (candidateReason), not just an outline colour behind an aria-hidden svg. Minor 6/7: a unit that fails its service-group lookup renders as an explicit anomaly instead of vanishing; the window resize listener now attaches unconditionally rather than being gated behind ResizeObserver's own availability check. Controller finding 8: the region grid's row-sizing squeezed the diagram to 208px of 1080px content at 1280x900. `.regionGrid` now sizes to its own content (align-self: start) only in the stacked (<90rem) layout, `.diagramRegion` gets a real min-height floor, and `.queueRegion` gets a matching cap so it doesn't balloon to all 41 unclamped rows — the >=90rem three-column layout is untouched (restored to align-self: stretch there), matching its original, already-working behaviour exactly.
…ates
Task 7: builds ShortlistPanel, the surface Phase 1's whole-branch review found
rendering a green tick beside "is not authorised under the Mental Health Act".
Every gate row now states its own verdict in text ("Met"/"Not met"), all eight
gates render every time (never .slice()'d), the icon reads directly off
gate.pass, and an ineligible candidate is marked with a dashed danger border
(forced-colors safe) and never styled as a recommendation. Confirm/override are
the only way to place a patient — nothing auto-allocates.
Task 8: one inpatient unit's own view at /ward-management/ward/[unitId] (WardScreen) - the ward answering what the coordinator refers, closing the loop the coordinator screen opens. Five regions derived live from useWardFlow(): unit identity, the five-state bed grid plus a CONFIRM_CAPACITY form scoped to the unit's own id, incoming referrals (accept in principle / hold a bed / decline with one of the seven DECLINE_REASONS) each carrying restrictionNotice where it applies, accepted/held/en-route movements, and withdrawn referrals with reason. An unresolved unitId renders an explicit empty state naming the id, never a substituted unit. Every accept/decline/hold control mirrors wardFlowReducer's own preconditions so it can never advertise an action the reducer would refuse. Registers tests/ui-ward-roles.spec.ts in both playwright.config.ts matchers, adds the route/component to the design-system adoption contract (regenerated ADOPTION.md/adoption-manifest.json), links the route from ClinicalRail's rail (literal href, route-reachability-safe), and regenerates docs/site-map.md (was stale without this run).
Task 9: /ward-management/transport/officer shows every transport job not yet arrived (the model records a provider organisation, never an officer identity, so filtering to "my jobs" isn't possible). A coordinator-style queue-plus-pinned-bar pattern lets one job be "active" at a time, its four actions (accepted, en route, collected, arrived) pinned to the viewport bottom and each gated by a function that mirrors the reducer's own preconditions exactly, including the receiving unit's live empty-bed floor guard on Arrived. Wires the route into ClinicalRail with a literal <Link> (RailLink's prop-passed href is invisible to route-reachability's AST scan) and adds ward-sites.ts's edById alongside unitById. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e implies Six "moving" transport jobs in the ward-movements fixture (WF-006, WF-014, WF-306, WF-313, WF-320, WF-327) carried no transport.collectedAt, a state PATIENT_COLLECTED (the only reducer transition producing stage "moving") could never leave behind, since it always sets collectedAt in the same update. That made PATIENT_ARRIVED refuse on all six, leaving the transport officer's phone screen with four dead controls on six of its eight jobs. Gives each of the two hand-authored records (WF-006, WF-014) its own plausible collectedAt derived from its own enRouteAt, and fixes the generator's "moving" case (stageFields in ward-movements.ts) so every generated in-transit record gets an index-varied, NOW_ANCHOR-clamped collectedAt instead of omitting it — the root cause behind the other four. Adds a fixture-coherence describe block to ward-flow-contracts.test.ts that walks the whole wardMovements array directly (not through the reducer) and asserts stage/stamp coherence read off ward-flow-reducer.ts: "moving" implies collectedAt, "arrived" implies arrivedAt where a transport job exists, transport stamps only fill in the order the reducer allows, and no stamp exceeds NOW_ANCHOR. Each invariant counts the records it actually matched, not loop iterations, so a future fixture edit that hollows the condition out goes red instead of passing vacuously.
Task 10: /ward-management/transport now renders LiveTracker, the coordinator's view of every vehicle in transit — patient, leg, and how long since its last recorded stamp. Every row is a movement that carries a real transport job (re-measured at this branch's head: 8 of 41 open movements); the other 33 are named explicitly in an on-screen banner rather than rendered as fabricated-leg rows, mirroring the officer screen's own honesty discipline. - tracker/tracker-derivations.ts: trackerRowState delegates leg precedence to transportLeg (never re-derives it) and maps the leg to its one real stamp; stampAgeText renders "<duration> ago" or an explicit no-timestamp sentence for the Requested leg, which the model never stamps at all. - Strengthens the brief's own Playwright assertion (which the task-10 preflight flags as unable to distinguish five correctly-rendered legs from two): adds a pinned exact-row-count test, plus a node-environment unit suite covering all five legs, Cancelled, and the no-transport absence — none of which the seed fixture exercises end to end. - The tracker screen's root also carries data-testid="ward-mode-transport" so the existing cross-mode navigation test keeps working now that this route no longer mounts WardModeWorkspace.
…lings R54-R62 Verification of every commit from 171adb6 to b2e0a92, measured in this session rather than read from any implementer report. The substantive finding is a fixture defect, not a code one. Six movements sat at stage 'moving' with no collectedAt - a state PATIENT_COLLECTED is the only producer of and always sets, so the reducer could never have reached it, and a patient cannot be in a vehicle that never picked them up. The consequence landed on the transport officer's phone: six of eight jobs had four dead controls each, because PATIENT_ARRIVED requires both. Fixed at 1349c21 with a contract invariant derived from the reducer rather than only correcting the six records. Counted from the live DOM afterwards: 8 of 8 jobs now have at least one available action, up from 2. R60 caught the phase's signature defect inside the guard written to prevent it: the vacuity tripwire counted loop iterations rather than matches, so its companion arrived-branch assertion ran on zero records and passed regardless. Corrected to count matches and to assert the honest zero rather than inventing a fixture record to satisfy a positive one. R55 records that no Ward Flow browser spec has ever run in CI's Production UI lane - scripts/playwright-pr-shards.mjs holds its own copy of the spec pattern and it has no ward alternation at all. Pre-existing on main, surfaced not fixed: correcting it needs hosted timing measurements this machine cannot take. R61 is a new environment trap that cost twenty minutes: the dev server is reaped when the shell that launched it exits, and the symptom is a Playwright identity guard failing with a Node connection error while curl to the same URL still succeeds - which reads exactly like an IPv6 mismatch and is not one.
Task 11: /ward-management/ed/[edId] shows one department's own patients, the department clock (from openedAt) and the legal clock (from formedAt where earlier), the four-hour departmental access target — labelled and computed so it can never be mistaken for a legal deadline and never touches a LegalForm/dueAt — a police-attendance flag, and each movement's single outstanding item. Adds a raise-referral form (RAISE_REFERRAL) and a record-examination form (RECORD_EXAMINATION), plus a mark-handover-ready control (HANDOVER_READY) — the missing control that lets a movement ever reach the transport officer's screen. Statewide capacity is shown read-only. Registers the route in the nav rail, the adoption contract, the codebase index, and the Playwright roles spec.
… that never accepted them HANDOVER_READY is the only reducer transition that produces stage "handover_ready", and it always creates the movement's transport job in the same update; bed_held (its only precondition) is itself only reachable after ACCEPT_IN_PRINCIPLE sets acceptedUnitId. Five fixture records claimed "handover_ready" without a transport job, and four of those five without an accepted unit either. WF-008 (hand-authored) had an accepted unit but no bedHeldUntil or transport, so its honest stage is accepted_awaiting_bed. WF-305, WF-312, WF-319 and WF-326 are generated records: routineMovements() assigns stage from index % MOVEMENT_STAGES.length with no case in stageFields() for "handover_ready" (correctly, since equipping one would mean fabricating the fields), so any index landing there produced an incoherent record. Fixed structurally by remapping that one stage to placement_requested at the point of assignment, closing the defect for every future index the generator can produce, not just today's four. Extends the existing fixture stage/stamp coherence block in ward-flow-contracts.test.ts with the reducer's full stage-producing table and four new invariants derived from it (direct and transitive), including the accepted-unit persistence chain that the original defect exploited. Full derivation, gate evidence and mutation-kill proof for every new assertion in the task report. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The product owner superseded the four-hour departmental access target figure for this prototype on 2026-08-22, in response to a direct clinical question: "change the 4 hour limit to 24 for patients in ED." ED_ACCESS_TARGET_MINUTES moves from 240 to 1440. Every safeguard Task 6A and Task 11 built around this constant is untouched: it is still counted UP from movement.openedAt, never a legal deadline, never attached to a LegalForm or dueAt, and never feeds a legal-breach count or eligibility gate. Updated the constant's doc comment, its pinning test, the ED screen's banner copy, the single-source guard's explanatory comment, and the phase 3 spec (with a dated supersession note so spec and code cannot drift apart again).
…dy has assessed The clinician, asked whether being examined should confer priority of its own: 'the reality is in ED that a patient needs review before they are referred for a bed as they may not need a bed.' operationalScore gains one factor, 'Bed need confirmed', worth 25 points when a movement carries an examination whose outcome is inpatient_order - the model's only operational evidence that a bed is genuinely needed rather than sought before anyone looked. 25 sits deliberately between the statutory-timing tiers of 20 (critical) and 30 (breached): a stronger signal than an unbreached legal timer, but not inflated to read like a deadline. Wording is operational throughout - never severity, acuity or risk, and the score still orders only within an urgency tier. Measured effect on the coordinator queue, tier 1: before after WF-303 61 not examined WF-009 78 inpatient_order WF-009 53 inpatient_order WF-017 66 inpatient_order WF-312 50 not examined WF-303 61 not examined Examined patients now lead, which is the clinician's rule reaching the surface. It also retires a separate concern raised earlier in this phase: the demo no longer opens on WF-303, a generated movement whose breach came from an index-derived formula rather than from anything anyone authored. This deliberately does NOT gate REFER_TO_UNITS on examination. Measured: only 2 of 17 open movements at a referable stage carry an inpatient_order examination, and 23 further open movements already past that stage carry none, so gating would make most of the fixture unreachable. That larger reading is with the product owner. KNOWN GAP, documented in the code rather than papered over: 21 of 41 open movements are voluntary and carry no legal form, so they never receive a Mental Health Act examination and can never earn this factor - even though a voluntary patient is just as much reviewed before a bed is sought. No proxy was invented. Covering them needs a general notion of review the model does not yet have.
…63-R67 Brings the committed ledger level with the live workspace after an integrity check prompted by a reported tree deletion. No loss found: all 15 session commits present and ancestors of HEAD, git fsck clean, node_modules intact at 523, 59 workspace files, 14 phase-3 screenshots. Substantive content: Task 11's emergency department screen with both clocks; the clinician's two answers implemented as separate revertible commits (the access target raised 4h to 24h, and a 'Bed need confirmed' factor so a patient confirmed to need a bed outranks one nobody has assessed); and R63/R64, a second and larger instance of the stage-incoherence defect found by looking at a screenshot rather than by running anything - five patients recorded ready to hand over, four of them to a ward that never accepted them.
The last task of Phase 3. One patient, WF-315, walked from an emergency department to an inpatient bed through all four roles in a single browser window, never reloading the page. Eleven steps: ED records the examination (outcome inpatient_order); the coordinator refers to all three eligible wards; RPH Adult Secure accepts in principle and holds a bed; the ED marks handover ready, which creates the transport job; the officer marks accepted, en route, collected, arrived; and the coordinator confirms WF-315 has left the queue entirely. The journey opens with the examination because the clinician ruled that a patient is reviewed before a bed is sought (R67). WF-315 carries a Form 1A awaiting examination, so recording it flips the form to 3B and earns the 'Bed need confirmed' factor - the patient's queue position visibly improves as a direct consequence of being reviewed. The switcher infers where you stand from the selected patient only when exactly one destination is implied, and otherwise offers a picker. After a three-way parallel referral nothing can be inferred, and silently taking the first referral would be a ?? array[0] in interaction form (R52). The coordinator is statewide and has no place; the switcher shows that asymmetry rather than inventing a location. A shared focusMovementId in the provider carries the selection across role switches, since each screen is a route segment that remounts. Reviewing the journey's own final screenshot caught a real bug in it: restoring the selection unconditionally re-selected WF-315 after arrival and left the shortlist showing 'Currently at ARM - waiting in the emergency department' for a patient who had already left. Now gated on isOpen, so the restore cannot invent a selection a live click could never produce. Mutation-proved, by the implementer and independently by me: replacing one click-based role switch with a page.goto() to the identical URL KILLS the test. The provider re-seeds and ward-incoming-WF-315 never appears. The journey therefore proves that state survives navigation across four roles, not merely that eleven screens each work in isolation - which is a different and far weaker claim under the same name.
…cing record Every judgement made on the product owner's behalf across three sessions, in one place, each with what it costs if wrong. The ledger holds the full text; this is the map. Organised so a clinician can find what matters: the eleven rulings that changed what the software says to a clinician, the eight spent on this project's one recurring defect (a check that claims more than it delivers), the six unmeasured fixture claims - three of them the controller's own - the process and environment calls, and the four questions still open with the owner.
|
Important Review skippedToo many files! This PR contains 151 files, which is 1 over the limit of 150. To get a review, reduce the PR to 150 files or fewer by splitting it into smaller PRs or changing its base branch. Upgrade to Pro+ to raise the limit. Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab. ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (151)
You can disable this status message by setting the Comment |
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1813124690
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2c9c56e244
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
- ward-flow-reducer.ts: closing an examination as community_order/ revoked now cancels in-flight transport and releases any bed already held at the accepted unit, and every downstream transition (referral, acceptance, hold, decline, handover, transport, arrival, escalation) now rejects once a movement carries a `closure`. Reproduces and fixes the reported defect where TRANSPORT_EN_ROUTE still succeeded on a movement already recorded as revoked, with its bed still reserved. - ward-screen.tsx: resolves the unit from the provider's live `units` instead of the frozen `unitById()` fixture, so CONFIRM_CAPACITY/ HOLD_BED updates are reflected on this screen instead of the seeded count. - ward-flow-provider.tsx: elapsed time is now accumulated across 30s checkpoints instead of compared against the original mount instant, so a session open past 24h (or any multiple of it) keeps counting forward instead of resetting to zero on an exact-24h wall-clock reading, which previously moved every deadline/wait/hold on every screen backward by up to a day. Each fix carries a regression test; the provider one is confirmed to fail against the pre-fix code (702 vs expected 2142). Addresses the three unresolved P1 review threads on PR #2289 as part of a Run PR sweep.
Summary
Verification
npm run verify:pr-localDuring development, use
npm run verify:cheapas the faster iteration gate before the final PR-local preflight.npm run verify:uiwhen UI, routing, styling, browser behavior, reduced-motion, or forced-colors behavior changednpm run verify:releasebefore release or handoff confidence claimsFor retrieval, ranking, selection, chunking, source/citation rendering, or answer-contract changes,
verify:pr-localrunseval:rag:offlineautomatically. Run the offline command directly during iteration before spending a live eval.npm run eval:retrieval:quality(must stay 36/36) when retrieval, ranking, selection, chunking, or scoring behavior changed — CI cannot run it (needs live keys), so run it locally and paste the summary. A metadata/governance-weighting change once buried correct docs (recall 1.0→0.76) and only this eval caught it.npm run eval:rag -- --limit 15+npm run eval:quality -- --rag-onlywhen answer generation, the synthesis prompt, or answer post-processing changed (grounded-supported must not drop; citation-failure 0)npm run check:production-readinesswhen clinical workflow, privacy, environment, Supabase, source governance, or deployment behavior changednpm run check:deployment-readinesswhen deployment startup, hosting, or rollout behavior changedRisk and rollout
Complete this section for clinical, data, API, auth/privacy, workflow, dependency, build, or deployment changes.
src/lib/rag/, retrieval RPCs, golden fixtures, ranking tests; seedocs/rag-behaviour/safeguards.md). Must use one of:RAG impact: no retrieval behaviour change — <reason>RAG impact: behaviour change — canary pair <baseline run> -> <post run>RAG impact: noneor omit for non-RAG PRs)Clinical Governance Preflight
Complete this section when the change touches ingestion, answer generation, search/ranking, source rendering, document access, privacy, production env, or clinical output.
Clinical KB Database(sjrfecxgysukkwxsowpy)Notes