Ward Flow Phase 5 — bed availability becomes a number you can plan against - #2390
Conversation
Product-owner instruction given during implementation ("keep it simple and
end the day at 2400"). D5 originally argued against midnight on the grounds
that nobody hands over at a calendar boundary; that reasoning is kept in
place as superseded rather than deleted, because it is the argument a future
reader will want to weigh before changing it back.
The behavioural consequence is one-directional and stated in the spec: a
release expected at, say, 23:30 now counts inside "tonight" rather than
being reported as excluded.
D5 anticipated this exact amendment — it recorded the boundary "so it can be
changed in one place". The code keeps that property: one named constant,
END_OF_DAY_MINUTES.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WHpxMM4ct1fF72RUmsZSFq
This reverts commit ce4d4f3.
… freshness stamp Two independent Phase 5 foundations. They are committed together because this repository's pre-commit documentation-sync hook refuses to regenerate while any source file is untracked, so splitting them was not available; neither depends on the other and each can be reverted by its own files. Capacity derivations (spec D5, D6) — src/components/ward-management/ward-bed-availability.ts: The rule this module exists to enforce is that nothing predicted, confirmed-but-unreleased, or on leave is ever added into `availableNow`. A coordinator must be able to point at that number and say "that is a bed I can fill this minute", and it must never have been softened by an expectation. `availableNow` and `held` are copied verbatim from `unitCapacity` rather than re-derived, so the one number a coordinator acts on cannot drift. Bands compare the raw instant against named minute constants rather than a wrapped time of day: an `Instant` is minutes since midnight on the synthetic operating day, so an instant past the evening-shift end is beyond today even when its clock time is not. Anything beyond tonight is excluded from every count AND counted, so a board can say how many it left out — silent truncation reads as "we counted everything" when we did not. Freshness stamp (spec D7) — src/components/ward-management/ward-freshness.tsx: Today only the capacity board states when its data was last true; every other board looks authoritative and cannot say how old it is, which invites the reader to assume it is current. Three renderings and no fourth: a confirmed time with the confirming role, the clock time a derived figure was computed, or "Never confirmed". Never a blank and never a dash — both are claims this component must not make. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WHpxMM4ct1fF72RUmsZSFq
…ly ask
Two items. They are committed together because this repository's pre-commit
documentation-sync hook refuses to regenerate while any tracked file is
unstaged; neither depends on the other.
1. The lifecycle transitions (spec D2, D3, D12)
src/components/ward-management/ward-flow-{events,reducer,provider}.tsx,
tests/ward-bed-release-lifecycle.test.ts
Six new events. CONFIRM_BED_RELEASE, BLOCK_BED_RELEASE, RELEASE_BED,
RECORD_LEAVE_BED and END_LEAVE_BED are ward-only; only
REQUEST_CAPACITY_REFRESH belongs to the coordinator.
The point of D2 is that the ward owns its own beds. A hub that lets a
central coordinator edit a ward's bed state is a hub wards will stop
trusting, so a coordinator attempting any transition gets a visible
Rejection rather than a silent no-op, and the one thing it may do changes
no number at all — it records that somebody asked, with the time and the
requesting role. Nothing leaves the sandbox and no message is sent.
Legal transitions and nothing else: predicted to confirmed or blocked;
confirmed to released or blocked; blocked to confirmed or released;
released is terminal. Each refusal names both states. BLOCK_BED_RELEASE
sets confidence null and the given blocker; CONFIRM_BED_RELEASE and
RELEASE_BED set both to null — so D3's invariant (a blocker exists exactly
when blocked, a confidence exactly when predicted) holds by construction
rather than by check.
RELEASE_BED is the only one of the six that moves a real bed count. A
released bed is available by D1, and capacityBreakdown's availableNow is
deliberately blind to bed releases, so the unit's own fields are the only
route — which is what keeps a merely predicted or confirmed bed from ever
reaching that number.
The provider gains leaveBeds and refreshRequests, including in its memo
dependencies, so the boards reading them cannot go stale.
2. Two test repairs found by review
tests/ward-freshness.dom.test.tsx, tests/ward-bed-availability.test.ts
The freshness stamp's "Confirmed" test asserted the confirming role but
never the time, so rendering the current clock in place of the confirmed
time would have survived it — a stamp silently claiming a stale figure was
confirmed just now, which is the failure D7 exists to prevent. The
assertion now pins the exact time, and both remaining branches were
mutation-tested.
Adds the missing case for a blocked release expected beyond tonight: it
counts as excluded and in no other figure.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WHpxMM4ct1fF72RUmsZSFq
…acity figures Committed together because this repository's pre-commit documentation-sync hook refuses to regenerate while any source file is unstaged. The three are independent; each is described separately below. 1. The ward's controls (spec D10, D12) src/components/ward-management/ward/ward-screen.tsx, ward.module.css The existing flagging panel gains the rest of the lifecycle rather than a second panel beside it. Each bed release row offers only the transitions legal from its current state, so a released bed offers no controls at all. Blocking asks for a reason from the fixed list and refuses without one. Leave beds now have the two-state life the spec describes — recorded, then ended on return — rendered as rows with an Ended control. Without it END_LEAVE_BED existed in the reducer and was reachable from no screen, which is both an unfinished feature and dead code. A coordinator's refresh request appears here as a visible mark naming the time and the asking role. It changes no number; it records that somebody asked, which is the phone call that says "is that still right?". 2. The discharge and egress board (spec D9) src/components/ward-management/discharges/*, its route, and its nav entry Grouped Blocked first, then Confirmed, Predicted, Released today — blocked first because those are the rows somebody must act on. On a phone it is cards, not a squeezed table: Ward Flow's tables are right at a desk and wrong in a corridor. The count of releases falling beyond tonight sits at the foot of the board and is stated even when it is zero. Silent truncation reads as "we counted everything" when we did not. 3. Capacity becomes five numbers, and none of them is a guess added to a fact src/components/ward-management/ward-management-modes.tsx, its stylesheet Available now, Confirmed today, Predicted today, Held, Leave (usable) — five separate figures, never a sum. Available now keeps its meaning exactly: a coordinator must be able to point at it and say "that is a bed I can fill this minute", and it must never have been softened by an expectation. This is the one existing rendered figure the phase changes. unitCapacity().potential — a raw count of every release regardless of state or timing — is superseded here by the breakdown. It is left in place with a comment for its three other call sites, which are outside this phase's scope. The coordinator's refresh control is the only thing a coordinator may do to a ward's bed data. Nothing leaves the sandbox and no message is sent. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WHpxMM4ct1fF72RUmsZSFq
Found by capturing the discharge board, the capacity board and the ward screen at 390, 820 and 1440 pixels and looking at them, after the first visible change rather than at the end. All three were structurally invisible. 1. The discharge board printed the confirming role twice on every row, in both the desktop table and the phone card: a bare "NUM FSH Adult Secure" line immediately above "Confirmed 10:07 · NUM FSH Adult Secure". WardFreshness already carries the role, so spec D9's "confirming role, and its freshness stamp" is satisfied by the stamp alone. Same class as the Phase 4 sidebar that duplicated its own title. 2. The capacity board contradicted its own headline. The headline separates confirmed from predicted and excludes anything beyond tonight; the per-unit row three columns away still rendered unitCapacity().potential — a raw count of every release regardless of state or timing, including the ones the headline deliberately excludes. One screen, two incompatible answers, and a coordinator reading "Potential 1" could not tell which it was. The row now shows Confirmed and Predicted from the same breakdown the headline uses. Now, Held, Blocked and Occupied are untouched: those four sum to the unit's bed total and that identity is asserted elsewhere. unitCapacity() itself is unchanged, as are its other call sites. 3. The ward screen rendered a raw union value as user-facing text, so a release read "confirmed" in lowercase where every other status on that screen is sentence case. Now goes through a label map beside stageCopy, the convention this file already uses. Each defect gains an assertion that would catch its return, and the first two were mutation-tested: reintroducing the duplicate line and restoring the Potential chip each failed the new test before being reverted. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WHpxMM4ct1fF72RUmsZSFq
A fourth defect, visible only once the third was fixed. The capacity board's per-unit row now reads Confirmed and Predicted; the ward screen for the same unit still read "Potential 1" — a raw count of every release regardless of state or timing — directly above an itemised list showing that very release as Confirmed. One prototype, two vocabularies for the same beds. The chip row now sources Confirmed, Predicted and Leave from the same capacityBreakdown the capacity board uses. Ready, held, blocked and occupied are untouched: those four sum to the unit's bed total and that identity is asserted elsewhere. The explanatory sentence still states plainly that confirmed, predicted and leave beds are never counted into them — that is the phase's central promise and it stays explicit. unitCapacity() is unchanged, as are its two remaining call sites on the network and flow diagrams, which this phase does not touch. Also formats five files that no earlier gate could have caught: formatting is in none of the test run, the type check or the lint run, so nothing verified this session would have gone red until CI. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WHpxMM4ct1fF72RUmsZSFq
The type check refused to build, which is this guard working rather than a nuisance. candidateEvents' switch must be exhaustive over every event type, so adding six Phase 5 events without extending this file broke compilation instead of letting six actions pass through the sweep unchecked. tests/ward-legal-figure-guard.test.ts walks every reachable state and checks that nothing rendered anywhere carries a figure or requirement that could have come from the Mental Health Act. It is the test that keeps this prototype's hardest promise, and until now confirming, blocking, releasing, recording a leave bed, ending one, and requesting a refresh were all outside it. Follows the file's own precedent of one candidate per real domain value rather than one hard-coded choice: every one of the seven blockers, and both usable and unusable leave beds. No default case and no cast — the exhaustiveness requirement is the mechanism that caught this, so it stays. Proven non-vacuous rather than assumed: emptying one new event's candidate list turns the traversal assertion red, naming the event that stopped being reached. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WHpxMM4ct1fF72RUmsZSFq
…d Phase 5 Test migrations — none weakened, each still guards what it guarded: ward-management.test.ts counted 15 column headers; the capacity table gained a Coordinator action column and its "FIVE BED STATES" header became "BED STATES" because the row no longer shows five. Now 16, and all 16 still carry scope="col" — checked rather than assumed. ward-bed-release.dom.test.tsx asserted "Potential 1" and "0Potential". Both re-pointed at the figures that replaced them. The second one's real subject is scoping — that a flag raised for one ward cannot move another ward's numbers, asserted against the live capacity row so a reducer bug that never reaches the fixture is still caught. That property survived the migration and was proved by making FLAG_BED_RELEASE write to every unit and watching it go red. Both tests originally chose a blocker before submitting. A blocked release counts in neither Confirmed nor Predicted, so leaving that in would have made both assertions vacuous against the new figures. They now submit with no blocker, which is a plain prediction and does move the numbers under test. Documentation, travelling with the code it describes rather than in its own pull request: The roadmap records Phase 5 as built, and the two things owed before Phase 6 builds on it — that spec D14's four-state model has still never been checked by a ward clinician, and that Phases 6 and 7 are worth designing in one conversation since Phase 6 is small and already largely determined by these numbers. The project ledger records what was built, the 22:00 to 24:00 and back round trip, the four defects the screenshots caught that ten thousand passing tests did not, the two gates that hide from every local check, and the three pre-existing failures on main that are not this branch's. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WHpxMM4ct1fF72RUmsZSFq
formatInstant became unused when the discharge board stopped printing the confirming role separately from its freshness stamp, which renders its own time. Lint runs with --max-warnings 0, so this was a build failure rather than a tidy-up. Found only after clearing the ESLint cache: the cache is per-file, so a warning caused by a different file's change stays invisible locally and goes red in CI. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WHpxMM4ct1fF72RUmsZSFq
Spec Verification: a ward flags a release, confirms it, blocks it with a reason from the fixed list, then releases it — and the coordinator's capacity board reflects every step without a reload. The no-reload part is the point; a journey that reloads between steps proves nothing about the shared derivation. Registered in both regexes in playwright.config.ts. Only one of them is the project pattern; the other is the top-level testMatch, and a spec file matching neither is a file tests/playwright-project-isolation.test.ts fails on. That test passes, so the registration is real rather than assumed. Proven load-bearing rather than merely green. Making CONFIRM_BED_RELEASE coordinator-only turns the journey red with the release stuck showing "Predicted" — the ward can no longer confirm, and the board correctly refuses to move. Reverting the role table exactly returns it to passing. Evidence, read from the "N passed" line and never the exit code, since run-playwright.mjs exits 0 on failure and on refusal alike: 1 passed (3.6s) on correct code 1 failed with the role gate mutated 1 passed (3.6s) after reverting Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WHpxMM4ct1fF72RUmsZSFq
|
Warning Review limit reachedNext included review available in 3 minutes. View limit detailsLimit details: You’ve used the included review currently available. Your 93 included PR review attempts over the past 7 days set your current allowance at 1 review per hour. Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab. Review configuration: ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: ⛔ Files ignored due to path filters (7)
📒 Files selected for processing (47)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
Static PR checks went red on mockup-spec-parity, which is a third registration
point the phase missed. playwright.config.ts decides which project runs a spec;
scripts/ci-change-scope.mjs decides whether the advisory lane starts at all. A
spec matching the first but not the second would be listed as advisory and then
never triggered by its own edit — the journey would sit there looking covered
and run in neither lane.
The guard states that consequence outright rather than reporting a mismatch, so
the failure named the fix.
Reproduced and proved both ways before pushing: with the pattern the self-test
passes ("Mockup spec parity: 13 advisory specs all match mockupPatterns"), and
reverting it reproduces exactly the CI error, naming
tests/ui-ward-discharges.spec.ts.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01WHpxMM4ct1fF72RUmsZSFq
Static PR checks went red on check:repo-awareness-snapshot: the committed snapshot still described 192 pages and 137 mockup pages, and knew nothing of /mockups/ward-flow/discharges. Regenerated with the repo's own tooling (npm run snapshot:repo-awareness), never by hand. The diff is exactly the new route plus the two counts it moves, and the commit stamp — nothing else. This is the fourth place a new route has to be registered this phase, after the nav entry, the two route-contract maps in tests, and CI's change-scope map. Each one failed closed rather than letting a half-wired route through, which is the design working. Verified before pushing: check:repo-awareness-snapshot now reports "in step with data/repo-awareness-snapshot.json (193 pages, 441 documents, 2618 reviews)". Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WHpxMM4ct1fF72RUmsZSFq
…e-5-p8rwcm # Conflicts: # data/repo-awareness-snapshot.json
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 9a840fc761
ℹ️ 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".
Three defects raised by automated review on PR #2390, each fixed with a mutation-tested regression test; the fourth finding is rejected with its reasoning recorded rather than silently dropped. FLAG_BED_RELEASE carried no expected time, so the reducer stamped expectedAt with the moment the ward reported the release. releaseBand() then classified it "now" every time, because expectedAt <= now is trivially true when the two are equal — the four planning bands worked only for the hand-authored fixture and never for anything a ward actually flagged. The ward's flag form now collects an "Expected free" time, parsed by the same helper the leave-bed form already uses. confirmedAt stays the moment of the report: they are different facts, and conflating them was the bug. Neither says anything about the departing patient; expectedAt is an operational estimate about the bed. Leave-bed identifiers were derived from the array's length while ending a leave bed removes an entry, so a third record could be handed the first's identifier and the removal filter would then delete both. Replaced with a monotonic sequence. Bed releases were checked for the same flaw and do not have it — nothing ever removes a release — and that reasoning is now a comment rather than something to re-derive. Confirming, blocking and releasing all kept the original confirmedAt, so a row went on reporting when it was first flagged rather than when its current state became true, defeating the freshness contract. All three now restate it. confirmedBy is deliberately untouched: each case already refuses any acting unit other than the release's own ward. The fourth finding wanted a released bed to stop banding "now" past the day boundary. That value feeds the discharge board's excluded count, whose footer reads "expected beyond tonight" — and a released bed is not expected at any future time. Applying it would have traded a cosmetic ordering nit for a board stating something false about a real record. Also records, with its measurements, that the intermittent role-screen strict-mode violation in tests/ui-ward-roles.spec.ts reproduces on clean origin/main on a screen this work never touches, so it is not this branch's. Nothing was quarantined, skipped or loosened. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WHpxMM4ct1fF72RUmsZSFq
The whole-branch review of Phase 5 raised seven defects, none critical and all of them a screen or a guard claiming something it could not back up. All seven are fixed, each with a test that was mutation-tested. The network view and the coordinator flow diagram still showed a "Potential" chip counting every bed release regardless of state or timing, so a bed that had already been released was advertised as one that may become available — no clicks, on seeded data. Both now read the same Confirmed and Predicted breakdown the capacity board and the ward screen already use. unitCapacity()'s arithmetic is untouched. The ward screen's chip said "Leave" while counting only usable leave beds, so on one seeded unit it read "Leave 0" seven lines above "1 bed currently on leave". It now says "Leave (usable)", as the spec names it and as the capacity board already said. Releasing a bed lowered derived occupancy without touching the sex-mix figure that every other occupancy-changing event maintains, so the capacity board's own row contradicted itself three clicks in. Sex is not added to a bed release and no sex is guessed at: the model cannot know who left, so the cell now says so in visible words when the two figures no longer agree. The blocker field was still an unconstrained string at every layer meant to constrain it, and the reducer only checked that it was non-empty; both are now real membership checks against the fixed list. The capacity board asserted a confirming ward unconditionally, so the "Never confirmed" floor was unreachable and a ward could be credited with a figure it never confirmed; it now carries the guarded props the ward screen already had. Two tests could not fail for what they claimed. A leave bed's only structural privacy guard inspected one hand-authored fixture object, so adding an optional sex field would have passed the whole suite; it now checks every seeded record and one the reducer produced. A freshness test's pattern accepted both possible outputs; it is pinned to the exact string. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WHpxMM4ct1fF72RUmsZSFq
The immutable review record for the whole-branch review, appended with the repository's own tool rather than written by hand, so the review throttle can find it by SHA on a later session. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WHpxMM4ct1fF72RUmsZSFq
Two conflicts, both from files that grow by accretion rather than by anyone disagreeing. The Playwright spec patterns are resolved as a union: main's Care Plan mockup journey and this branch's discharges journey are both kept, in both patterns. The generated repository-awareness snapshot is not resolved by hand — main's version is taken and the repository's own tool regenerates it, which is the only way that file is ever correct. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WHpxMM4ct1fF72RUmsZSFq
The generated snapshot counts pending inbox requests, and this branch added two. Regenerated with the repository's own tool rather than edited, which is the only way that file is ever correct. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WHpxMM4ct1fF72RUmsZSFq
CI triageCI failed on this PR. Automated classification of the 2 failed job(s):
Compared with main CI run #14008 (failure). Classification is evidence routing, not permission to ignore a failure. Exact quarantined Playwright identities remain governed by the flake ledger. |
Records what was built, what is proven and by what evidence, what is still red, and the one decision waiting for a human, so a session with no memory of this work can resume it exactly. Deliberately blunt about the unfinished parts: the mockups bundle budget is 0.7 points over its ceiling and needs an owner decision, and the intermittent duplicate-testid failure in the roles journey is characterised as an unproven hypothesis rather than a diagnosis. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WHpxMM4ct1fF72RUmsZSFq
Session handover — Ward Flow Phase 5The full handover is committed to this branch as Resume in sixty secondsgit fetch origin claude/ward-flow-phase-5-p8rwcm
git checkout claude/ward-flow-phase-5-p8rwcm
What was builtA bed release gained a lifecycle The rule the phase exists to hold: nothing predicted, confirmed-but-unreleased, or on leave is ever added into "available now" — and it holds structurally, not by assertion. The one thing still broken
There is no user-facing regression — the production bucket is within tolerance. The failing bucket is chunks reachable only from This is already recorded as outstanding issue
Verification, stated honestly
One known unknown
Owed before Phase 6
A note on review coverageCodeRabbit hit its hourly allowance repeatedly and its one real attempt aborted when the head changed mid-review. The Cursor agent approved twice, both times stating it skipped the bug-finding signal — that is a policy approval, not a defect review. The four findings that were fixed came from the Codex connector. The deterministic gates carried the load; none was weakened to compensate. Generated by Claude Code |
Clean origin/main already sits at +24.7% against the mockups ceiling of 25%, inside the check's own drift-warning band. This branch adds one percentage point, so ~96% of the overage predates it. Records the numbers and the apportionment rather than leaving an instruction to go and find them. Also records that --update rewrites every baseline, production included, so clearing this the easy way would re-baseline production's own drift as the new normal. Recommends refreshing the mockups number alone, and leaves the decision with the owner. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WHpxMM4ct1fF72RUmsZSFq
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01WHpxMM4ct1fF72RUmsZSFq
|
| Ref | mockups bucket | vs baseline 487.6 KiB |
|---|---|---|
clean origin/main |
608.1 KiB | +24.7% |
this branch (400cedd9) |
613.1 KiB | +25.7% |
| ceiling | — | 25% |
main is already at +24.7% — three-tenths of a point under the ceiling, and already inside the check's own 15% drift-warning band. Phase 5 adds 5.0 KiB, one percentage point, and is simply the change that tips it over. This is outstanding issue #QSHHGK happening word for word: "accumulated growth fails whichever unrelated PR lands last."
Production is unaffected in any meaningful sense: main 1660.7 KiB, this branch 1661.2 KiB — Phase 5 adds 0.5 KiB, 0.03%. Both sit just above the 1656.0 KiB production baseline, so main carries a little production drift of its own too.
Why I have not just cleared it
Pruning is not available to this branch — the growth is main's, and pruning other people's mockups is well outside this change.
That leaves the sanctioned refresh, and it has a trap worth naming: npm run check:bundle-budget -- --update rewrites every baseline — production, per-route and mockups alike (scripts/check-bundle-budget.mjs: "refresh every baseline"). Running it here would silently re-baseline production's own accumulated drift as the new normal, which is exactly the hiding-a-regression failure that the production bucket's "do not refresh the baseline to clear it" rule exists to prevent.
So the conservative fix is to refresh only mockups.gzipBytes and leave production and per-route baselines untouched — changing precisely the number the evidence justifies and nothing else.
Either way this is a repo-wide config change governing every future pull request, so it is the owner's call rather than an agent's. No measurement makes that automatic. Flagging it and holding rather than deciding it unilaterally.
Full detail, including how to reproduce both numbers, is in docs/ward-flow-phase-5-handover.md §6 on this branch.
Generated by Claude Code
…efresh only the mockups bundle baseline. A released record was always banded as now, so advancing the clock by a day still listed it under Released today. The Build failure is the mockups ceiling at +25.7 percent against a 25 percent tolerance; production and per-route baselines stay put, and mockups.gzipBytes moves to the measured 613.1 KiB (627814 bytes). Co-authored-by: Cursor <cursoragent@cursor.com>
Keep main's mockups gzip baseline from #2390 (627814). The PR-local 625306 refresh was only needed against the older 499284 ceiling. Co-authored-by: Cursor <cursoragent@cursor.com>
The merge brought in new review records from #2390, so the committed snapshot lagged review_state and would fail Static PR. Co-authored-by: Cursor <cursoragent@cursor.com>
The merge brought in new review records from #2390, so the committed snapshot lagged review_state and would fail Static PR. Co-authored-by: Cursor <cursoragent@cursor.com>


Summary
predicted → confirmed → blocked → released, and only a ward may move it.confidencenarrows tolikely | possibleand now means something only while a release is predicted; a confirmed release has no confidence because it is no longer a belief.blockerbecomes state-dependent and typed — legal only while blocked. The invariant holds by construction: each transition builds its own object literal, so no code path can produce both fields non-null.LeaveBedcarries nothing about the person on leave, and a usable leave bed is never merged into availability — it is its own figure. It has the two-state life the spec describes: recorded, then ended on return.Available now · Confirmed today · Predicted today · Held · Leave (usable).availableNowis computed before any release or leave bed is examined, so a prediction is structurally incapable of inflating it — not merely tested against, unable to happen.availableNowandheldare copied verbatim fromunitCapacityso the number a coordinator acts on cannot drift./mockups/ward-flow/discharges. Blocked rows first because those are the ones somebody must act on; cards rather than a squeezed table below 40rem; the beyond-tonight exclusion count stated at the foot even when it is zero.Never confirmed. Never a blank and never a dash.docs/ward-flow-complete-ledger.md§5d records what was built, what the screenshots caught, and what is still open.Binding spec:
docs/superpowers/specs/2026-08-26-ward-flow-phase-5-bed-availability-design.md. It is unchanged by this branch.Verification
npm run test—Test Files 3 failed | 873 passed (876)/Tests 3 failed | 10559 passed | 1 skipped (10563)npm run typecheck— cleannpm run lint— clean, run withnode_modules/.cache/eslintcleared first (the cache is per-file, so a warning caused by another file's change stays invisible otherwise; doing this surfaced a real--max-warnings 0failure)npx prettier --checkon every changed file — clean. Formatting is in none of test, typecheck or lint; five files were unformatted after all three passedtests/ui-ward-discharges.spec.ts—1 passed (3.6s), and proven able to fail: mutatingCONFIRM_BED_RELEASEto coordinator-only turns it red with the release stuck at "Predicted", then green again on revertVerification not run:
npm run verify:pr-local— its constituent gates were run individually and quoted above; the wrapper itself was not invoked.UI verification not run:
npm run verify:uiwas not invoked as a wrapper. The new Chromium journey was run directly and is quoted above, and the three changed screens were captured and inspected at 390 / 820 / 1440. CI's own Production UI lane is the authoritative full-matrix run.No provider-backed gate was run: no
verify:release, noeval:*, nocheck:supabase-project, notest:live, and nothing touching OpenAI, Supabase or a live database.The three failing unit tests are pre-existing and not this branch's.
clinical-hazard-controls,privacy-readiness-contractandrag-plan-package-parityfail identically on cleanorigin/main, verified by running them in a worktree at that ref. They are untouched here.Every new test was mutation-tested — the thing it guards was deliberately broken, the test watched to go red, then restored. Where a mutation would have survived, the assertion was strengthened rather than the result accepted.
Risk and rollout
/mockups/ward-flow, an administrator-gated developer-hub prototype holding only synthetic data and reachable from no production route. The one existing rendered figure this phase changes isunitCapacity().potentialon the capacity board and the ward screen, replaced by the state-aware breakdown;unitCapacity()itself is unchanged and its remaining call sites on the network and flow diagrams keep their current behaviour. The only file outside that prototype is the generateddata/repo-awareness-snapshot.json, regenerated by the repo's own tooling to add the new route.supabase/changed.src/lib/rag/, no retrieval RPC, no ranking surface and no golden fixture is touched, andclassifyPullRequestFilesreportsragRanking: false.Clinical Governance Preflight
Clinical KB Database(sjrfecxgysukkwxsowpy)Evidence for each of the seven, in order:
BedReleaseandLeaveBedboth remain free of anything describing the departing or absent patient — not an identifier, not a reason, not a destination, and per spec D11 not even sex, the one otherwise-permitted attribute. This is asserted structurally against each type's own field set rather than against fixture content, so a future field namedpatientIdwould fail it.supabase/is in this diff.Rejectionrather than a silent no-op; a release expected beyond tonight is reported as excluded rather than dropped; and a unit with no confirmed capacity readsNever confirmedrather than zero, because zero is a claim.No Mental Health Act figure was invented, cited, paraphrased or inferred anywhere.
tests/ward-legal-figure-guard.test.tssweeps every reachable state for exactly that, and it failed the build when the six new events were added without extending it — its switch must be exhaustive, so the gap could not pass silently. It now covers all six, including every one of the seven blockers and both usable and unusable leave beds, and was proven non-vacuous by emptying one candidate list and watching the traversal assertion name the event that stopped being reached.Notes
Four defects were found by looking at screenshots that every one of 10,000+ passing tests missed — the project's recorded recurring failure, caught mid-phase this time rather than after later work was built on top:
confirmedin lowercase.Each fix carries an assertion that would catch its return.
One decision reversed and reverted during the session: the day-end boundary went 22:00 → 24:00 → 22:00 at the owner's direction. The spec is byte-identical to
main. Worth recording because D5 had written the boundary down "so it can be changed in one place", and that is exactly how it behaved — one named constant, one assertion, and no code shipped at the wrong value.Automated code review did not run on this PR. CodeRabbit reported its hourly limit and posted no findings; the Cursor approval agent approved on policy grounds and stated that it skipped the bug-finding signal because that bot had not reported when it polled. Neither is a defect review. The deterministic gates carried the load instead, per the accepted owner decision recorded in AGENTS.md.
Two things are owed before Phase 6 builds on this, both recorded in the roadmap:
predicted → confirmed → blocked → releasedis a software model of how a bed comes free. A bed may be confirmed and blocked at once in reality, and "predicted" may compress several distinct real states. It is cheap to change while everything is synthetic, and Phase 6 is built entirely from these numbers — so the cost of it being wrong rises the moment Phase 6 lands. Nothing else in this phase depends on there being exactly four states.