Ward Flow — synthetic bed-flow prototype (DRAFT, not for merge) - #2466
Ward Flow — synthetic bed-flow prototype (DRAFT, not for merge)#2466BigSimmo wants to merge 299 commits into
Conversation
Implements "The three lists (OWNER-APPROVED, 2026-08-28)" from
docs/ward-flow-phase-6-7-decisions.md. Every entry ships verbatim: nothing
was tidied, shortened, re-cased, reordered, added or removed.
List 1 - BED_RELEASE_BLOCKERS gains "Awaiting family or carer arrangement",
overturning a principled Phase 5 exclusion. The reasoning is recorded in the
code: excluding family availability never stopped the delay happening, it just
made the ward record a different reason that was wrong. Guardianship and
financial arrangements stay excluded.
List 2 - the Q1 axis change. BED_RELEASE_CONFIDENCE_LEVELS (likely/possible)
is replaced by BED_RELEASE_WAITING_ON, and BedRelease.confidence becomes
BedRelease.waitingOn. A predicted discharge now states a fact two wards mean
the same thing by, not a probability estimate they do not. All three places
that picked a confidence moved with it: the flag form, the revert form, and
REVERT_BED_RELEASE's own event field. "Nothing outstanding" is load-bearing -
without it the list forces a ward to name an obstacle that does not exist.
List 3 - BED_PREPARATION_NOTES was an empty array, so BedPreparationNote
resolved to never and the field could only be null. Filling it turns on both
the field and a picker: a new "Beds being made ready" section on the ward
screen. A bed carrying a preparation note is STILL OFFERED, still counts in
availableNow, and still appears in every figure - guarded now in three places
(the pure function on the boolean, the pure function sweeping every specific
note, and the rendering layer through a real dispatch).
Verification, decisive lines quoted:
check-ward-suite.sh (58 files, discovered from disk)
Test Files 58 passed (58)
Tests 752 passed (752) EXIT=0 (baseline was 746)
Chromium, all six ui-ward-*.spec.ts (--project=chromium-mockups)
52 passed (1.6m) PW_EXIT=0
[32/52] ... the real PDF is exactly one A4 page
typecheck recorded a pass for "typecheck:internal" (5643 input files)
eslint exit 0 on all 18 changed files, cache cleared first
design-sync contract checked: 55 components and 7 guidelines
prettier All matched files use Prettier code style!
Six mutations, each watched red then restored byte-identically. Dropping one
entry from each of the three lists goes red on an exact-members assertion, not
a length check. Making the preparation note gate allocation goes red in both
the pure function and the rendering layer.
One thing needs the owner's word: the decisions document says "the six Phase 5
reasons are kept unchanged" but BED_RELEASE_BLOCKERS has always had SEVEN. Its
transcription omits "Awaiting receiving-service acceptance". I kept all seven
and added the eighth rather than silently delete a shipped entry on the
strength of a miscount. Detail in the task report.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This document said 'the six Phase 5 reasons' and listed six. There were seven: the transcription dropped 'Awaiting receiving-service acceptance', which shipped in Phase 5 and is named in its own test's title. The implementing agent noticed, kept all seven, added the approved eighth, and flagged the discrepancy rather than deleting a live entry from a working picker on the strength of a slip in this document. That was the right call and it is recorded here so the correction travels with the decision. Verified after the correction: Test Files 58 passed (58), Tests 752 passed (752). Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…into Phase 8's Task 10 Both items were flagged honestly by the tasks that built them rather than found later, and both are the class of defect that has reached a rendered screen in every phase of this project while every test stayed green: the 'beds being made ready' section has been proven by jsdom and typecheck only and seen at no breakpoint, and the print ink on the new bed-model and list copy was never measured (only the page count, which is one A4 page). The three wording observations are recorded so nobody silently 'fixes' them without knowing the rule that an agent never tidies a clinical list. Two are deliberate leaves with their reasoning attached. The third - whether 'Awaiting receiving-service acceptance' and 'Awaiting a community team to accept' name the same thing - is with the owner and must not be resolved by guessing. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…t follow-up rulings The product owner - a psychiatrist - was asked directly on 2026-08-29 whether 'Awaiting receiving-service acceptance' and 'Awaiting a community team to accept' name the same thing. His answer: 'they are different things. So separate them.' So both stay, and List 2's entry is renamed to 'Awaiting community team acceptance' so the two no longer look like the same phrase in two registers. This is the one entry in these lists carrying his own CLINICAL confirmation rather than his approval of a session's proposal, and the record says so - the distinction matters if a later reader notices the two look similar and wonders whether to merge them. They look similar because both describe somebody accepting a patient; they differ in who, and in whether a bed is involved. Also records eight follow-up rulings, six of them deliberate LEAVES with their reasoning attached so nobody 'fixes' them later: - 'Awaiting accommodation' in both lists stays - the column disambiguates, and a second phrase for one real-world thing would be worse. - Mixed registers in List 1 stay - tidiness is not a reason to touch clinical wording. - Releasing straight from 'predicted' stays allowed - the old model permitted the same by another route, and recording a fiction to satisfy a state machine is worse than a loose state machine. - The six morning figures deliberately do not sum, because blockedToday overlaps confirmed and predicted. Forcing them disjoint would reintroduce the exact defect the rework removed: a blocked bed vanishing from the confirmed count, so a ward's figures improve at the moment it is stuck. Verified: Test Files 58 passed (58), Tests 752 passed (752); typecheck 5643 input files. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Phase 8 Task 1. A band is a fact about a pair — a person's home region and a hospital site — so it belongs to neither the hospital nor the ward and lives in its own module with one entry point. Every screen that shows a band asks travelBand()/unitTravelBand() rather than working one out, which is what stops two screens disagreeing. The band is measured from homeRegion to the candidate unit's own site, never from referral.originSiteCode: that is the hospital the referral came from, not where the person lives, and measuring from it would call a city bed close for someone driven into a city emergency department from a long way away. Nothing stores a band on a record. An unrecorded pair reads as undefined — never a default, never the nearest band, never 'unknown means far'. The out-of-area threshold is a list of band names, not a number, so it can never be read as a measured figure. SYNTHETIC_TRAVEL_BANDS is invented. No map, atlas, search or recollection of Western Australian geography was consulted. Which pairs are recorded was chosen for coverage; which band each got was then decided mechanically by list position, so neither step could carry a judgement about distance. The table is deliberately sparse and a missing pair is a first-class answer. Six tests, each mutation-tested. None asserts a band for a named place — that would turn a placeholder into a pinned expectation and make the owner's future correction a test failure. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Phase 8 puts honest travel bands on the coordinator's screen. An unchecked superlative sitting beside a checked band reads as though it had been checked too, so both claims go before anything else touches those screens. (a) REFERRAL_DECLINE_REASONS: out_of_catchment -> belongs_to_another_service, labelled "Belongs to another service". Nothing in this model holds a catchment for anybody, so the old label implied a check the system never performed. homeRegion cannot supply one either: a catchment is a service's boundary, a home region is where a person lives, and ten WA regions do not map onto five health services. The reason is renamed, not removed -- "this request belongs to another service" is a real administrative answer a coordinator can give and can know, and it stays distinct from referred_elsewhere. The movement-side DECLINE_REASONS list keeps its own out_of_catchment member deliberately: different list, different event, different decision (Phase 7 D8). tests/ward-model-phase3.test.ts is untouched and still green, which is the proof the two lists were not collapsed. (b) originServiceFit: "Best" -> "Same health service", "Escalation" -> "Different health service". The function compares two health service names; "Best" read as the system's opinion about which bed this person should have. Tones are unchanged -- a colour is not a claim in the way a word is. Tests: the decline-reason array pin, a new distinctness guard for the two administrative reasons, and a new comparative-word guard over originServiceFit across the whole fixture. All four mutation-tested red. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fix round 1. Test-only: neither module changed. An assertion that could not fail is worse than no assertion, because it reads as coverage. Five gaps closed. - Drop the tautological site-code check, which looped over pairs built from the very array it compared against. The real guard — every site code the fixture KEYS ON exists in wardSites — now stands alone with a mutation behind it. - Split the single coverage test into one test per property. Two of the four properties had no mutation because a mutation to either was masked by an earlier assertion failing first in a shared body. All four are now evidenced by an isolated mutation. - Pin both mandated notices as whole strings against an independent copy. Truncation is the failure mode that matters: half of 'no distance shown here should be relied on' still reads like a caveat while having dropped the part that does the work. - Replace the near-vacuous sparseness floor (fewer than 170 recorded pairs, when there are 15) with claims that can fail: at least one pair genuinely unrecorded, and no home region recorded at every site. The second is strict on purpose — completing a region deserves a deliberate decision, not a silent green run — and the reasoning is written on the test. - Say non-emptiness where the label check meant non-emptiness, not truthiness. The comparative-word denylist is left as it is: every denylist is survivable by rephrasing, and widening it would buy a false sense of coverage. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e to fail Two review findings on ca558dc. 1. The doc comment added to originServiceFit said "Nothing here sorts, ranks or hides a candidate". That is true of the function and false of the file: candidatesFor cuts the shortlist to three via eligibleCandidatesAmong, which sorts twice, and the compare table renders a positional rank beside each column heading. In a phase whose binding rule is "nothing sorts, ranks, truncates, gates or hides a candidate", a comment asserting that of the wrong scope is the same failure mode that let an invented Mental Health Act figure into this codebase. The sentence is now scoped to the function and names the surrounding sort, rank and cut explicitly as deliberate and out of scope. No behaviour changed and the pre-existing shortlist ordering is untouched. 2. expect(labels.length).toBeGreaterThan(0) was a floor nothing realistic could make fail. It now asserts the sweep covers every movement crossed with every unit, which is what it was there to mean. Mutation-tested: narrowing the sweep to one movement fails it at 23 of 1104 while the label-set assertion below still passes, so this is genuinely the half that pins the coverage. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…flag Fix round 2. The completeness guard was the only thing closing 'the table is deliberately incomplete' — its companion claim is mathematically 155 > 0 — so it could not just be dropped. But as written it broke a settled rule: the owner's future correction of the invented placeholders must never arrive as a test failure. Completing a region with measured times IS that correction, and nothing about the table's shape distinguishes an invented fill-in from a measured one. The decisive problem was the incentive, not the red. The failure lands on a test named for leaving the table incomplete, so the natural way back to green is to not record the real data. A test that pressures someone into withholding checked data is worse than the fill-in it guards against. So the fixture now exports TRAVEL_BANDS_ARE_INVENTED, a boolean and nothing else, whose doc comment says what flipping it means and what it turns off. The guard runs only while it is true, and the test's title carries the reason either way, so a skipped run says so in the reporter instead of vanishing quietly. The eventual correction costs a deliberate flag flip in the same change that replaces the values — a conscious governance act rather than a mystifying red. Also split the near-vacuous companion claim into its own test so it cannot mask the strict one, the shape the earlier split was meant to remove. It keeps honest work there: it proves the loop that checks all 155 gaps through travelBand actually iterates. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…d search
Phase 8 Task 2. The out-of-area measure counts elapsed time from ARRIVAL, and
nothing in this model recorded a referral arriving anywhere: a Referral carried
a home region and never arrived, a Movement arrived and carried no home region.
This is the cheapest closure of that gap — one optional field, one event, one
role gate — and it adds no new fact about a person, because an arrival time is
operational, in the same family as raisedAt and decidedAt.
Referral.arrivedAt?: Instant
Referral.localBedSought?: { at: Instant; by: string } // by is a ROLE
REFERRAL_ARRIVED (coordinator|ward) stamps an accepted referral. It is ONE
TIMESTAMP and must stay one: it creates no Movement, sets no location, no legal
status and no stage, so Phase 7 D14 — that an accepted referral is deliberately
not turned into a movement — is exactly where D14 left it. That absence is
asserted field by field, not assumed, so a later extension argues with a test.
RECORD_LOCAL_BED_SOUGHT (coordinator) answers D8-6: nobody knows whether country
services look for a local bed first, so the step is something a coordinator MAY
record if it happened, never a stage the pathway requires and never a gate on
acceptance. No note, reason or outcome field; `by` comes from the raising role.
Seeds: RF-002 arrived with a band the table does not record, RF-003 arrived with
an in-area band (the case the ledger must exclude), RF-006 and RF-007 accepted
with no arrival. No existing accepted referral could be out of area, so RF-008
was ADDED using a (home region, site) pair the band table already records as far.
The band table itself is untouched — it is owner-ruled placeholder data, and
editing it to make a case fit would defeat the whole arrangement. Every band is
looked up through ward-distance.ts; none is stored on any record.
Verified: 124 passed across the four affected test files, typecheck and lint
clean, and sixteen mutations each watched RED at the predicted test and restored
byte-identically. Full evidence in the (gitignored) task-2 report.
Thirty commits landed between writing this design and revising it. Five changed things it depends on, and every one is now reflected: - The bed model became THREE stages plus a flag. `blocked` is no longer a state; it is a flag on a predicted or confirmed release, and a blocked-but-confirmed bed keeps counting as confirmed. D4's mapping table is rewritten and Task 4 now mutation-tests the cross-cut, because reintroducing the subtraction is the likeliest silent regression here. - `BedRelease.confidence` became `waitingOn`. Every stale reference is gone. - The blocked-discharge reason list is ANSWERED. The owner approved it separately and it shipped as the eight-entry BED_RELEASE_BLOCKERS, so D9's ten-item draft is withdrawn and this board defines no list of its own - a second vocabulary for one fact is the defect class this repo produces most reliably. - BED_PREPARATION_NOTES is filled, which adds a tile caption and a trap: a bed being made ready is STILL available and still in every figure. - Phase 8 landed travel bands, so the discharge arrows may carry one - under Phase 8's own rules: bands group, they never sort, and no word implying proximity ships without a fact behind it. The design goal held. The bed model changed underneath this work and it cost one derivation module on paper, nothing else - which is what D4 was for. One owner-pending item remains: the receiving-time options at the pull. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…aced far from home Phase 8 Task 3. Two derivations, both in `ward-referrals.ts` deliberately: that file is already an entry point of the transitive import-graph contract test, so the "no Phase 8 derivation reads a bed release" rule covers them automatically. A tidier-looking module outside that graph would have left the claim vacuous. `groupCandidatesByTravelBand` is a pure rearrangement of a list somebody else computed. Always exactly five groups in `TRAVEL_BANDS` order followed by not_recorded, empty ones included — "there is nothing within an hour" is the answer a coordinator came for, and a group that vanishes when empty cannot give it. Every candidate lands in exactly one group (the key expression is total by construction, so there is no branch to fall out of), and order inside a group is the order the candidates arrived in. Distance never gates: a bed three hours away that accepts this referral still says so and still carries its Accept control. `outOfAreaLedger` counts accepted arrivals whose band is a member of `OUT_OF_AREA_BANDS`, with the clock running from `arrivedAt` and never from `decidedAt`. An arrival the fixture records no band for increments a separate `notBanded` rather than being counted as far or silently dropped; an accepted referral with no arrival is in neither number and is not reported as missing anything; an unresolvable unit is skipped rather than banded against a guess. The two counts share no denominator and the return carries no total, so no screen can read a proportion out of it. No band is stored anywhere. Bands are looked up through `ward-distance.ts` per call, so replacing the placeholder values in `ward-travel-bands.ts` stays the whole change. Two contract tests the earlier reviews asked for: - `ward-distance.ts` and `ward-travel-bands.ts` are asserted to be MEMBERS of the collected import graph. They join it automatically, but a coverage claim you cannot see is not a coverage claim — a refactor that moved a derivation out of the graph now fails here. - No module under `src/` may import `ward-travel-bands.ts` except `ward-distance.ts`. Scoped to `src/` on purpose: `tests/ward-travel-bands.test.ts` imports the fixture legitimately, and checked against import statements rather than file bodies, because `ward-movements.ts` and `ward-model.ts` both name the fixture in prose explaining why they do not store a band. Eleven mutations watched red, each restore verified by git blob id. Three tests were strengthened after a mutation survived or landed weakly: two fixture searches ran on the output of the function under test and were steered by the defect they were looking for; the "distance does not gate" test searched for an out-of-area bed that accepts a referral, so under a distance gate it simply found a different pair the mutant allowed, and is now stated as verdict invariance under home region — which no distance gate survives under any name. The ledger ordering test ran against a seed that yields one entry, where no sort is observable, and now uses a two-entry fixture. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Branch cut from Phase 8's tip. Six tasks run now - all creating files that do not exist on Phase 8's branch - and the rest waits. The rule that makes it safe: never edit a file that already exists over there. Task 1 therefore puts the occupancy record in its own new file rather than in ward-model.ts. Four owner decisions, 2026-08-29: - ONE record, not two. The occupancy record serves the ward board AND Phase 8's out-of-area ledger. Phase 8's Referral.arrivedAt is superseded. - The ten WA regions stay. - sex goes on the occupancy record - confirmed, not merely unopposed. - Parallel work approved, logic layer only. Three lessons taken from Phase 8's ledger, all binding here: - mutate.sh cannot fail. It copies the backup over the source then diffs the backup against that copy. Every mutation here uses blob ids plus an empty git status instead. This plan previously told implementers to use it, in two tasks. - An assertion that searches for a satisfying example is not an invariant. Phase 8's most important test survived a real defect because it searched the fixture and found a pair the mutant still allowed. The prep-note, blocked-cross-cut and sex-constraint tests are restated as invariance. - One exported function, never two components agreeing on a verdict. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fix round 1, coordinator ruling on concern 1. Band group headings carry two figures — how
many units are in the band, and how many of those accept this referral — and leaving both
to the screens was not safe. Two surfaces will show band groups, the match view and later
the network diagram, and "how many of these accept this referral" is a verdict rather than
arithmetic. Two components each deciding what "accepts" means is how this project ended up
with three screens holding their own copy of one label, two of which disagreed. The lesson
written into this codebase is one exported function, not two files agreeing.
`TravelBandGroup` stays exactly as the brief pins it: `{ band, candidates }`.
`travelBandGroupCounts` takes the GROUP, not `(referral, units, now)`, and that is the
structural guarantee rather than a convention. Taking the group means the only thing it can
count is the very `ReferralCandidate` objects the rows beneath the heading render, reading
the verdict already computed for each — there is no second verdict for a heading to
disagree with. A signature taking `now` would permit exactly that divergence, because
`referralEligibility`'s `capacity_freshness` gate is time-dependent, so a heading recomputed
a moment after the rows could report a different number for the same beds with nothing
looking wrong in either place.
`candidateAccepts` is now the one spelling of "this bed accepts this referral". The match
view was routed through it, so the row's styling, the row's "Accepts this referral" wording
and any heading count all ask the same function; `verdict.eligible` no longer appears in
that component at all, and a test pins that.
The counts report two present facts and nothing about what is missing — no completeness
figure, no tally of what the fixture failed to record, nothing that reads as a shortfall. An
absence is shown by the not-recorded group being present and populated. An empty group
returns zeroes and callers still render it, because "none within an hour" is the answer a
coordinator came for. The two ledger counts are untouched and still share no denominator.
Four mutations watched red: the accepting count made to diverge from the rows' own verdict
(caught by inverting every verdict before grouping, so a recomputation reports the true
eligibility while the rows show the opposite); an empty group made to return one rather than
zero; a `notRecorded` key added to the return; and the match view made to re-spell
`verdict.eligible` itself.
The mutation harness now also compares `git status --porcelain` across each mutation and
prints the full listing after every restore, so a sed that reaches beyond the file it was
aimed at is visible rather than invisible to a single-file comparison.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fix round 2. One Important finding and five Minors from review, all in the test file.
The ledger ordering test was weak about ordering, which is this phase's whole hazard. Its
two arrivals shared a unit and a band and were given newest-first, so "most recently arrived
first" — the exact idiom `recentlyDecidedReferrals` uses in the same source file, and so the
one most likely to be written by somebody being helpful — was a no-op against it. So were
"shortest wait first", "by band" and "by unit". The test's own comment claimed all four were
caught; one was.
It now uses three arrivals given in middle/oldest/newest order across two out-of-area sites
carrying different bands, everything located by search rather than by index. It also asserts
that the given order differs from every plausible sorted order, each with its own message —
that self-check is what makes the ordering assertion a guard rather than a coincidence, and
its absence is why the previous comment was wrong.
The invariance sweep now varies `originSiteCode` alongside `homeRegion`. Measuring from the
hospital a referral came from rather than from where the person lives is this phase's
founding defect — it called a city bed close for someone driven into a city emergency
department from a long way away — and it is the most likely wrong implementation anyone
would write here. Nothing caught it before: the same gate that home-region invariance
catches under an innocuous name survived when keyed on origin site instead.
The invariance proof also rested on fixture variance it never asserted. If the fixture lost
its spread of band values, a band-reading gate would stop varying with home region and the
sweep would stay green while proving nothing. Both facts it depends on are now asserted
without naming a site or a region: one site alone separates four distinct band values, and
between them the sites expose every band plus the unrecorded case.
"Exactly five groups" was derived on both sides — the expected list and the grouping both
come from `TRAVEL_BANDS`, so adding a band moved both and the length check could not fail.
The count is now pinned as a literal, so changing the band list is a decision made in a test
rather than a silent consequence.
The fixture-boundary guard could be defeated by a comment. Matching `import ... ;` in raw
source is non-greedy and stops at the first semicolon, so a semicolon inside a comment in the
middle of an import truncates the statement before the specifier: the old matcher captured
`import { /* note;` and reported no violation. Comments are now stripped first, with a
string-aware stripper that is itself tested against that exact shape. Stripping also lets the
guard check the whole remaining source rather than only import statements, which closes the
half of the rule a statement-only check could never see — the fixture must not be indexed
inline either. The fixture file declaring its own contents is excluded by exact path.
The accepting unit the ledger tests are built on is asserted rather than carried on a bare
non-null assertion, and the boundary failure now names the offending modules.
Nine mutations watched red, each restore verified by git blob id and by an unchanged
`git status --porcelain` snapshot: four comparators against the new three-arrival fixture
including the previously no-op most-recent-first; a gate keyed on origin site; a fixture
change that collapses the band spread; a sixth band added to the band list; and an import
hidden behind a block comment. `mutate3.sh` gains `pipefail`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three modules built in parallel, committed as one wave because the
pre-commit hook refuses a commit while other new files sit untracked
under src/components or tests - which is precisely what concurrent
implementers produce.
ward-discharge-dates.ts the ward's date drives the bed release
ward-teams.ts a synthetic community team per WA region
ward-statistics.ts the six ward-level flow figures
67 tests across the four modules now on this branch, green together.
Five mutations, each watched red and each file restored to a matching
blob id. mutate.sh was not used - it diffs the backup against a copy of
itself, so its 'restore verified' line only ever proved cp succeeded.
TWO GAPS FOUND BY REFUSING TO FAKE THEM. Both are real holes in the
Admission record, both flagged rather than papered over, and both need
an owner decision before the wiring task:
1. No waitlist-start time. 'How long do people wait on a ward's list'
returns no-data rather than a fabricated number. Fix is either a
new field or sourcing it from the referral's raised-at.
2. No 'the ward has decided this discharge is happening' flag, so a
CONFIRMED release cannot be derived - only predicted and released.
An implementer declined to invent a time-based proxy, which would
have rendered a decision nobody made. Correct call.
Also unreviewed: every derived prediction currently reports its
waiting-on as 'Nothing outstanding', the most conservative value
available, but nobody has approved it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Both came from implementers refusing to fake something.
DB-1 The waiting clock starts at the referral's raised-at, not at ward
acceptance. No new field - but ward-statistics.ts must widen to take
referrals, and the figure is the patient's wait, never the ward's score.
DB-2 Confirming a discharge is a separate act from setting a date. The
record gains dischargeConfirmedAt and dischargeConfirmedBy (a role).
This unblocks the confirmed stage, which an implementer found was
unreachable and correctly refused to fake with a date-window proxy.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fix round 3, on the coordinator's ruling. The traversal that proves matching never reaches
the bed-release model extracted import statements from raw source with a non-greedy match,
so it stopped at the first semicolon it met. A semicolon inside a comment in the middle of
an import truncated the statement before the module specifier was reached.
That defeated this contract two ways at once, which is why it mattered here more than
anywhere else the same shape appears: a bed-release import could hide from the identifier
check, and the traversal would also stop following it, so the module graph narrowed silently
while the test still reported green.
Measured with the real helper rather than a reimplementation. On an import carrying a block
comment, the old extractor captured `import { /* note;` and reported no bed-release mention;
the new one captures the whole statement and reports it. Demonstrated at contract level too:
with a genuine bed-release import added to a file in the graph, the old extractor passed
33 of 33 while the new one fails.
Comments are now stripped before extraction, with the same string-aware stripper already
proven in the travel-band boundary guard, so a specifier that merely contains a comment
marker is never mistaken for one. Stripping can only reveal more imports, never fewer, so it
strictly strengthens every assertion built on it.
Strengthened only, as instructed. Exactly one line was removed — the old extractor body. The
identifier list, the entry points, the non-vacuity floor and every assertion are untouched,
and the traversal still reaches the same seven files, so it widened rather than shifted.
Nothing that was green went red.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…sentence
91 tests across five modules on this branch, green together.
Two mutations, both watched red, both restored to a matching blob id:
- the accepts rule replaced by an equality: 7 tests red, decisively on
'counts a man and a woman identically when every bed is undesignated'.
That invariance form is what a search-for-an-example test could not
have caught.
- constraintSentence re-counting independently instead of calling
acceptingBedCounts: 4 tests red. The one-function property is pinned.
THE SPEC IS WRONG AND BUILDING IT PROVED IT. D11's headline sentence reads
'Only 1 will take a man'. That cannot occur. Unit.sexDesignation is ONE
value for the whole unit, so sex acceptance is all-or-nothing and the real
output is 'None will take a man'. A ward with 3 free beds of which 1 sits
in a male bay is NOT EXPRESSIBLE in the current model - there is no
per-bed designation. This needs an owner ruling, because on a real ward
the mixed case is the common one and it is exactly what the line existed
to surface. Counts are typed and written as counts, not booleans, so only
the inside of acceptingBedCounts changes if per-bed designation lands.
Four smaller gaps flagged rather than papered over:
- sexDesignationAccepts is module-private in ward-eligibility.ts, so
this module reaches the one verdict through referralEligibility and a
probe referral. Exporting it would remove the probe; that file belongs
to the other session, so it was left alone.
- the sex_mix gate is deliberately NOT folded into the counts; folding
it would make 'how many beds accept a man' depend on the free-bed
total through a threshold. Reasoning written out in the file.
- 'dates moved since yesterday' counts admissions, not revisions: the
record holds a lifetime total and the current set-at, so a second move
the same day is invisible. Needs a per-move history.
- ARROW_HORIZON_DAYS = 7 is a display choice made here, not a supplied
figure. Named, exported and pinned by a test so it is visible to
replace. It carries no clinical or legal meaning.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Fix round 4. Round 3 gave this contract a comment stripper that nothing tested. The copy of that test in the travel-band grouping suite protects that file's stripper, not this one, so anyone later simplifying this one, tidying it, or "fixing" what looks like an over-complicated regex would narrow the contract silently while it still reported green. That is the same failure round 3 demonstrated at 33 of 33, one level up: a guard that is itself unguarded. The test pins what the contract depends on rather than an implementation detail — it asserts through the real importsMention and the real identifier list, for an import hidden behind a line comment and one hidden behind a block comment. It also pins both directions on plain, comment-free imports, because a stripper that discarded everything would otherwise satisfy the hidden-import assertions by leaving nothing to disagree with, and it pins that a string literal which merely looks like a comment survives untouched, since corrupting specifiers would drop files from the traversal instead of adding them. Three mutations watched red, each reaching disk under its own blob id: the stripper tidied out of the extractor, its block-comment branch disabled, and it returning its input unchanged. Assertion messages were added first so each failure names the sample that broke; without them all three reported the same bare line, which is indistinguishable from a contaminated run. With them, the block-comment mutation is discriminated from the other two, which disable stripping entirely and so legitimately fail at the same first assertion. Purely additive: 61 insertions, no deletions, and no change to the identifier list, the entry points, the non-vacuity floor or any existing assertion. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The spec's headline sentence was written against a model that cannot produce it. A unit has ONE sex designation, so 'only 1 of these 3 will take a man' - the case the line existed for - was not expressible. Owner chose the ward-stated daily number over per-bed designations: the charge nurse knows it and the system never will, and a stale per-bed designation is worse than none. Two counts, deliberately overlapping, in a new daily-return record - NOT on Unit, which belongs to the other session. That record also gives the daily-confirm freshness a home it did not have. Honest cost recorded: it is a typed number in a design that has been removing typed numbers. Guarded by validation against the derived free count, and by going stale like everything else on the sheet.
… testable 267 admissions across 23 units - 259 holding beds, 5 departed, 3 waitlisted. 109 tests across six modules on this branch, green together. The binding constraint: the derived sex mix equals every unit's recorded sexMix exactly, for both sexes, across the whole network. The fixture never reads sexMix back to top itself up - every occupant's sex is written out - so a flipped sex cannot be silently absorbed. That is what makes the mutation bite. Coverage authored deliberately rather than incidentally: all four stay bands, a pulled-but-not-arrived bed, one past its own date, one blocked, one with no date, one departure to another psychiatric ward (the only destination that is not a statewide release - without it the netting rule is untestable in seeded data), plus the three cases the out-of-area ledger needs, including one who has LEFT an out-of-area bed. CONTROLLER ADDITION - a duplicated constant, and the guard it needed. The implementer had to copy NOW_ANCHOR rather than import it, because a single-source test restricts reads under src/ to three files and editing that test was forbidden while another session shares this worktree. A duplicated anchor that drifts would shift every seeded stay length and re-bucket every band with nothing going red, because every test reads the same stale copy. The restriction does not cover tests/, so the drift is catchable from outside src at the cost of one assertion and no edit to any existing file. Added, and watched red on a one-minute mutation (expected 643 to be 642). That mutation also caught a real failure in my own restore: git checkout cannot revert an UNTRACKED file, so the standard restore silently left the mutation in place and only the blob-id comparison noticed. Restored by hand and re-verified against the original blob. Flagged, not fixed: dischargeDateSetBy still has no fixed vocabulary, unlike every other category on the record; seeded referral ids join to nothing today; and the state-coherence test cannot currently fail, since every record is built by a coherent builder - reported rather than left to imply otherwise. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The out-of-area ledger read an `arrivedAt` stamp added to `Referral`, and it had no exit. A referral never stops being accepted, so somebody discharged weeks ago stayed on the ledger with their elapsed time still climbing. That is a defect, not a preference. `Admission` records the same fact with both ends attached — `state: "left"` and `leftAt` — so the ledger now reads it, and `Referral.arrivedAt` and the `REFERRAL_ARRIVED` event are gone rather than kept alongside. One fact, one record. `localBedSought` and `RECORD_LOCAL_BED_SOUGHT` answer a different question and are untouched. The clock still runs from the arrival, not the pull: this measures how long somebody has been away from home, and the bed has been gone since the pull. The pull-to-arrival gap is a real figure and is deliberately not surfaced here. The two counts still share no denominator and there is still no total. That matters more now than it looks: the band table classifies only some pairs, so `notBanded` will normally dwarf the out-of-area count, and any key permitting a ratio would turn an honest gap into an apparent shortfall. Nothing is seeded. No `Admission` fixture exists on this branch; the ledger is exercised by test-local admissions only, and the seeded demonstration is owed. Removed with their subject rather than weakened: the `REFERRAL_ARRIVED` reducer tests, and the seed-shape tests that asserted three arrival shapes on referrals. The referral privacy allowlist NARROWS by one, which the `Required<Referral>` equality check pins.
… testable 267 admissions across 23 units - 259 holding beds, 5 departed, 3 waitlisted. 109 tests across six modules on this branch, green together. The binding constraint: the derived sex mix equals every unit's recorded sexMix exactly, for both sexes, across the whole network. The fixture never reads sexMix back to top itself up - every occupant's sex is written out - so a flipped sex cannot be silently absorbed. That is what makes the mutation bite. Coverage authored deliberately rather than incidentally: all four stay bands, a pulled-but-not-arrived bed, one past its own date, one blocked, one with no date, one departure to another psychiatric ward (the only destination that is not a statewide release - without it the netting rule is untestable in seeded data), plus the three cases the out-of-area ledger needs, including one who has LEFT an out-of-area bed. CONTROLLER ADDITION - a duplicated constant, and the guard it needed. The implementer had to copy NOW_ANCHOR rather than import it, because a single-source test restricts reads under src/ to three files and editing that test was forbidden while another session shares this worktree. A duplicated anchor that drifts would shift every seeded stay length and re-bucket every band with nothing going red, because every test reads the same stale copy. The restriction does not cover tests/, so the drift is catchable from outside src at the cost of one assertion and no edit to any existing file. Added, and watched red on a one-minute mutation (expected 643 to be 642). That mutation also caught a real failure in my own restore: git checkout cannot revert an UNTRACKED file, so the standard restore silently left the mutation in place and only the blob-id comparison noticed. Restored by hand and re-verified against the original blob. Flagged, not fixed: dischargeDateSetBy still has no fixed vocabulary, unlike every other category on the record; seeded referral ids join to nothing today; and the state-coherence test cannot currently fail, since every record is built by a coherent builder - reported rather than left to imply otherwise. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…cope
DB-4 Screens: something ugly on screen in the first hour; the daily sheet
built FIRST and timed with a stopwatch, not left in the middle; print
designed alongside the screen. Blocked-discharge on the morning page
DECLINED for now - that page is fixed and has already gained a figure.
DB-5 'What moved since I last looked', the coordinator's cross-ward view.
Constrained by there being no user model: scope it to the session or a
chosen point, never invent a stored per-user timestamp.
DB-6 The transport officer screen answers ONE question - which job can I
start now, and if not why not. Two windows, pickup and drop-off, from
opposite ends; startable when both are open.
An earlier recommendation put empty-bed time on this screen as a
prompt. WITHDRAWN. It is a task surface, not an information surface;
an officer with a van does not need a management figure. A 'not yet'
row must say which window is shut and until when, or the officer rings
the ward - the call the screen exists to remove.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…emonstration Fix round 5. Three items. The comment scanner did not track regex literals, so a regex containing a quote character flipped it into string mode and it never recovered. Reproduced rather than taken on trust: with both protections disabled, nine files under src end the scan out of step, named in the failure. Imports sit above the offending regex in all nine, so no import was lost and the bed-release contract was sound in practice — but sound by accident of file layout rather than by construction. The comment claiming stripping "can only ever reveal MORE imports, never fewer" was therefore false, and is corrected in place rather than quietly dropped. A scanner that loses its place reveals fewer, not more. A false reassurance in a comment is how this codebase acquired an invented Mental Health Act figure. Both copies now track regex literals, resynchronise at a newline inside a quoted string or a regex since neither may contain one, and report whether the scan finished balanced. Every scanned file must end balanced, so a future blind spot fails loudly instead of reporting clean over a partial read. Two of those guards were unfalsifiable when first written, and both are now pinned directly rather than left as untested code: regex tracking makes the resynchronisation unnecessary on today's files, and no file in the referral-matching graph desynchronises at all, so the balance sweep there could not fail. Each is proved on a synthetic input instead. The invariance sweep closed uniform refusal but not uniform acceptance — everything accepting everyone is perfectly invariant and made both existing floors more satisfied, not less. A third floor now requires at least one refusal, and the comment records why an invariance test needs a companion that pins an absolute in a case where the answers should differ. The seeded demonstration is wired: 267 admissions across 23 units, read with the seed's own time anchor. Occupied out-of-area beds, admissions whose band cannot be resolved, and departures from out-of-area beds all have real content. The exit path carries a self-check — putting the same person back in the same bed must produce an entry — so the exclusion cannot pass because of a band rather than because they left. The seed's duplicated time anchor is guarded here because the copy shipped with the fixture does not currently load. No total is pinned and no denominator is formed. On this seed the unclassified count exceeds the out-of-area count by more than an order of magnitude, and at that ratio any construction implying one is a shortfall of the other would be the dominant reading of the screen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…ard speaks Folds in three owner rulings. 116 tests across six modules, green together. RULING 1 - Admission gains dischargeConfirmedAt and dischargeConfirmedBy (a role). A date is a PLAN; confirming is a DECISION. An implementer found the confirmed stage unreachable and declined to invent a date-window proxy because it would render a decision nobody made; these fields are the fix. The permitted-field list widens deliberately, and the structural test says so rather than merely allowing it. RULING 2 - a predicted release nobody has spoken about now carries NO waitingOn. Silence means nobody looked; 'Nothing outstanding' means a ward checked and there is nothing in the way. The invented default collapsed those two into the optimistic one, applied to every discharge nobody had examined. The owner-approved list is UNCHANGED - the entry stays, and a test now pins its membership against a future tidy-up. RULING 3 - confirmed derives from that field and nothing else. A blocked release still keeps its stage; blocked stays a cross-cut, never a bucket subtracted. CONTROLLER WORK on top of the implementer's: Fixed the compile break the two new required fields caused in two Admission factories the implementer was forbidden to touch. Vitest does not typecheck, so both suites were green while tsc would have failed - flagged by the implementer rather than discovered later. Decoupled the seed test from ward-board-derivations. The sister session cherry-picked the seed, its import could not resolve, and the whole test file silently never loaded - including this branch's anchor-drift guard, which everyone then believed was protecting a copied constant it had never run against once. The sex-mix sum is a claim about the FIXTURE, so it is computed in the test rather than reached for across a layer that also pulls in capacityBreakdown, a forbidden identifier inside their matching firewall. Mutation-tested the replacement to prove it is as strong as what it replaced. WORTH RECORDING: the first attempt did NOT go red, and the cause was a bad mutation rather than a fake test - it flipped the sex of somebody who had already LEFT, and a departed person holds no bed, so the mix correctly did not move. Retried against an occupied record: red on the right test, restored byte-identical. A mutation that fails to bite is a question, not an answer. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The out-of-area ledger asked "has this person left?" — a denylist, and it fails open. A waitlisted admission carrying an arrival time counted, and a fifth AdmissionState added later would fall through as occupied by default, onto a ledger a coordinator reads as fact. An unrecognised state must be excluded, not counted. Now `if (!bedIsOccupied(admission)) continue;` — the record's own predicate, so a state added to ward-admissions.ts is classified in one place. Strict tightening: no test changed, no module graph change. Pinned by a new test that a waitlisted admission WITH an arrival is not counted, with an occupied control identical but for the state. Mutating back to the denylist reddens that test alone; deleting the check reddens five. The plan still told a future agent to build the event this task removed. Its 32 task boxes are all unchecked, and in this repository an unchecked box is a live to-do, not history — including one instructing a mutation test of the REFERRAL_ARRIVED role gate. Superseded notes now head Task 2 and the ledger half of Task 3. The original text stays: it is the record of what was planned, and deleting it loses the reasoning. The design specification is untouched — it carries no instruction boxes. Decisions register gains D8-9, with the provenance stated exactly: decided by the product owner on 2026-08-29, relayed by the parallel ward-board session, and confirmed with him directly before any code changed, because it contradicted an answer he had given earlier the same day. It records what it supersedes — including the mandated screen sentence that is now false — and what reversing it costs. tests/ward-admissions-seed.test.ts is deleted, and NOT because it was unused. It imports ward-board-derivations, which lives only on the ward-board branch, so on this branch it has never loaded and has never run: vitest reports "Tests no tests" against it. Deleting it removes a false signal, not coverage — anyone reading this branch would reasonably believe those assertions were protecting the seed fixture, and they are not. Its subject is that fixture's own internal consistency, which is its own branch's concern. It returns at the fold in that branch's version — 21 tests, including the sex-mix consistency claim and the confirmed-admission coverage assertions — with its dependencies arriving in the same merge. The one guard asked to travel with the seed, protecting the copied time anchor from drifting, is already live here in a separate copy that has actually been executing (tests/ward-travel-grouping.test.ts). The fixture itself, ward-admissions-seed.ts, stays — it is what the ledger reads. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Writing "85 initialNow= call sites" into a docblock made it 86, because the sentence contains the token being counted. A third session re-measured before accepting my correction, got one more than me on both totals with the file counts matching exactly, and traced the consistent off-by-one to its cause rather than dismissing it. Verified at both trees rather than taken: 699cc35 gives 85 call sites and 109 occurrences; HEAD gives 86 and 110. The file counts stayed at 38 and 42 because this file already held the token — so the drift shows on one axis and not the other, which is how it survives a sanity check. One is the dangerous size. Off by fifty reads as a bug and gets investigated; off by one reads as carelessness and quietly discredits a record that was exact. Two repairs were offered and neither is taken. Saying the docblock counts itself leaves a number any later edit still invalidates. Excluding this file from the pattern tunes the measurement so the record fits, which is the wrong direction — the measurement is not the thing that should move. The figures are now stamped with the tree they were taken on, which is what an observation with a shelf life actually needs: a SHA-stamped number cannot be contradicted by a later edit at all. Also corrected while measuring: my "109 mentions" was an occurrence count, and `git grep -c` returns lines-containing-a-match. Reading one as the other gave 107 for the same tree. Both numbers are now stated as what they are. Gates: focused run on the touched file, 3 passed. prettier clean. Nothing else in this change is executable.
Measured across all three trees under both units, after a peer reported it and
before accepting it:
matches (-o) lines with a match (-c) files
699cc35 109 107 42
416fb1e 110 108 42
d8cc6c6 111 109 42
Run `git grep -c` today and it returns 109 — the exact occurrence count pinned
in this file from 699cc35. So a reader verifying that number right now
confirms it while being wrong about both the tree and the unit, with the two
errors cancelling to precisely the right figure. Nothing about that check feels
weak from the inside.
It is structural, not a fluke. The tree drifts by one per commit that mentions
the token and the two units differ by a small constant, so the quantities cross
regularly, and will cross again on any long-lived count.
Which makes both halves of the previous commit load-bearing, and the case for
the pair is stronger than the case made for either. The SHA stamp kills the
wrong-tree half; naming the unit kills the wrong-measure half; either alone
still admits the cancelling pair. They were argued for separately and should
not have been.
Found by the session that re-measured a correction it had already been told was
exact — which is the only way this was ever going to surface.
Gates: focused run on the touched file, 3 passed. prettier clean. Nothing else
in this change is executable.
Both red tests were guards firing as designed, and both are replaced by
assertions that can still fail rather than deleted.
The suburb note. `Referral.suburb` exists now, so the intake form's sentence
("It is not yet recorded on the referral") had become a false reassurance
about what the record holds. The note now says the suburb IS recorded, and
the pin is INVERTED rather than removed: it fails the day the field goes
away, pointing the other direction. A third half pins the honest "not known"
option against SUBURB_UNKNOWN_REASONS, because a person of no fixed abode
must be referable and a hand-listed options list omitted that cohort here
once. No street, number or postcode was added (`PD-3`).
The ED hub. The staleness canary fired because RF-009 is seeded: the first
ED-addressed referral, at rph-ed, triaged 210 minutes before it was raised.
The finding it stood in for is discharged and replaced by the assertion it
was a stand-in for — the hub RENDERS that row, with both clocks legible on
screen ("In department 4h 05m since triage", "Since referral 35m waiting").
The canary survives, inverted: the inbox must not go empty again.
The clock that could not stop. `referralWaitLabel` counts from raisedAt to
now with no reference to triagedAt, so it kept printing a growing wait for
somebody already in a department. The board and the match view now read
`referralWaitLine`, which goes through `referralClocks` (`P9-D7`) and words a
stopped span differently from a running wait, composing from
REFERRAL_CLOCK_TERMS so the "arriv" guard reaches this wording too. The
board's "Waiting" column header moved with it, since it could otherwise sit
over a span that had ended. `ward-management-network.tsx` is untouched.
Two doc comments asserting "no ED-addressed referral can carry a triage time"
were corrected; RF-009 falsified them.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`sinceReferralRunning` has two branches and every seeded referral took the same one. Stopping needs triage AFTER the referral — the community expect who has since arrived — and all nine seeded referrals were the opposite case, triaged before anyone referred. So the stopped wording could not appear on a screen built to show both, and RF-009 made it look covered while being the ED presentation, which correctly keeps running. Measured across all nine before acting on the report, not inferred: running was true for every one of them. Reported by Ward Referrals, whose screens are the ones that would have shown one of the two wordings and never the other. RF-003 carries it. A crisis service refers, the patient arrives 25 minutes later: the referral clock ends at 25 and stops, the department clock starts then and runs. It is a real story rather than a fixture shaped to satisfy a branch, and it leaves RF-001 as the not-yet-arrived expect so all three shapes are seeded — absent, running with a gap, and stopped. The canary now asserts the third shape too, and it was the one missing while the other two looked like full coverage. What makes this worth a commit message rather than a line: the code was correct, both branches were tested with hand-made referrals, and the FIXTURE was silent. Nothing in the derivation, its tests or typecheck could have said so, because none of them looks at what the seed actually contains. That is the same class as a field with no producer, one layer along — the difference being that here the producer exists and no seeded case reaches it. Also corrected while checking: a report that RECEIVE_REFERRAL still had no triagedAt field. It does, added an hour earlier. The claim was true when its author first read the file and was carried past the commit that answered it. Gates: 112 files handed in / 112 ran, 1515 collected, 1514 passed, 1 failed — tests/ward-referral-suburb-pin.test.ts, red by design and handed back untouched. typecheck 0 errors. eslint clean. prettier clean. The new canary mutation-proven by removing the seeded instant; the restore missed on its first attempt and was verified by diffing the whole file against HEAD rather than by re-running the test.
…ow-wave1-referral-corrections
…stantly The stopped-clock gap was found by a peer who happened to be reading two surfaces at once and noticed one of two labels had never once rendered. That is luck, not method, and a class found only by luck is the one most worth a mechanical guard. So: name the branches a referral screen can render, and require the seed to reach each one. The expected set is exact rather than "more than one", because an exact set fails in both directions — a branch that vanishes is caught as loudly as one never seeded. It went red immediately on two more instances of the same class, and one of them is mine from two hours ago: - Referral.suburb never took `unknown` anywhere in the seed. The union exists so a patient of no fixed abode can be referred at all — and then every seeded referral named a place, so the label rendered on no screen. The fix and its own missing fixture are the same defect one layer apart. - No referral was ever addressed to a community team, so that hub would have been built against an empty list and looked correct while showing nothing. RF-006 carries the unknown suburb: police-brought, no address on record, which is the archetype. homeRegion stays Kimberley and that is not a contradiction — a service can know the broad area without knowing the dwelling, which is why the two are stored separately and neither is derived from the other. RF-007 gains a community-team arm beside its ward arm, with the note that the arm has no team id and that seeding it does not pretend that gap is shut. What a field-level check would have missed, and the reason this is branch-level: one fixture DID carry triagedAt, which made the case look covered while being the opposite shape. A fixture that exercises a FIELD is not a fixture that exercises a BRANCH, and two of three shapes present reads as coverage. The second destination then fired a pin in the visibility suite, which had recorded "the shipped seed cannot test this rule" as an assertion. It fired correctly, its premise had moved, and the replacement is stronger: the seed now carries a real multi-destination referral, so FD-23 is asserted against the shipped fixture and not only against one built to break it. Mutation-proven by leaking the other destinations into the ward view — RF-007 names the exact failure. Stated limits, so nobody reads the guard as more than it is: referral surface only, listed branches only, default scenario only. It cannot know about a branch nobody thought to list. Gates: 113 files handed in / 113 ran, 1521 collected, 1520 passed, 1 failed — tests/ward-referral-suburb-pin.test.ts, red by design, fixed on the referral branch and not yet folded here. typecheck 0 errors. eslint clean. prettier clean.
…ow-wave1-referral-corrections
…ked undecided 2026-08-31: a ward is NOT told that a patient is also referred elsewhere — not even the bare fact with nowhere named. The badge stays gone, and the code and test stop saying the question is open, because it is not. WHY THIS IS A COMMIT RATHER THAN A NOTE. The previous commit deliberately recorded this as provisional, listing every place an answer would land. That was right for an hour and became wrong the moment he answered. A marker announcing an open decision that has closed is the provisional-values rule failing in reverse — the next reader finds a live question and treats a settled answer as a placeholder. Same failure as a stale prohibition, aimed the other way. THE COST IS ACCEPTED, NOT RETIRED, AND THAT IS THE RULING RATHER THAN A FOOTNOTE. Twice it was argued that `withdrawnReferrals` already pays for hiding this, so strict was "free". It does not. `ACCEPT_IN_PRINCIPLE` is that field's only writer — measured, and now pinned by a test on Ward Core's side — so nothing reaches a ward until somebody accepts, and the deliberation window, the one window where a ward's decision is still open, is unprotected by construction. A ward may hold a bed for a patient who is going elsewhere. He was given that trade in plain terms — a bed possibly held empty, against a patient possibly deprioritised by every ward offered — and chose this side. That is a clinical price knowingly paid, and it is written down as one, because "free" and "worth it" decay differently: the first invites a future reader to undo the rule on discovering the cost. THE LOSING ARGUMENTS ARE KEPT, NOT DELETED, and this is the part I would defend hardest. The case FOR the badge is strong — the owner's own stated reason for the rule is "so a ward does not spend its time on a patient who is being placed elsewhere", which argues for telling the ward. A reader who rediscovers that with no record of it having been weighed would reasonably conclude nobody had thought about it. A decision survives on its argument; the argument it defeated is what stops it being relitigated from scratch. HOW THE QUESTION REACHED HIM AT ALL, since it nearly did not. One session ruled it strict on the ground that the cost was retired; the session that owns the rule retracted that premise, then restored it an hour later without withdrawing the retraction — because a ruling had landed in between and made its supporting arguments feel re-validated. Nothing in the code had changed, only the conclusion's popularity. It has since confirmed the measurement independently and agreed the restatement was wrong. That failure is worth more than this commit: it is not a wrong instrument giving a confident answer, which is the shape we have been cataloguing all night. It is a CORRECT instrument, already fired, whose result was discarded because the verdict went the way the reader would have voted. Verified: the guard runs green after the wording change, 1 file handed in, 1 ran, 2 passed, exit 0; prettier clean. Both assertions were mutation-proven in the previous commit and neither changed behaviour here — only what they say when they fail. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…e now says which Ward Verifier walked three wards live and found both. I verified each in source before changing anything, and its numbers are the proof rather than the reasoning: rph-adult-secure capacity "Held 1" list beneath: nothing held (WF-003 still OFFERS "Hold a bed") bty-older-adult capacity "Held 0" list beneath: WF-304 "Bed held · 19m left" One ward over-reports and the other under-reports, against a list printed inches below. WHY THEY CANNOT AGREE. `capacityBreakdown` computes `held = max(empty - availableNow, 0)` — a bed that is physically empty but not currently offered. `ward-bed-availability.ts` contains no reference to `bedHeldUntil`, `bedHold` or `heldFor` at all, so a bed kept for a named patient can never enter that figure. They are two legitimate quantities wearing one word, not one quantity computed wrongly. The same shape for "Confirmed", and RPH is why it survived: the capacity chip counts discharges CONFIRMED TODAY while the control below reports the allocatable bed count the ward last CONFIRMED. On RPH both read 1 and the ambiguity is invisible; on FRE they read 1 and 3. Anyone checking the first ward alphabetically sees nothing wrong. WHAT I DID NOT DO, AND THE RESTRAINT IS THE DECISION. I did not rename either chip. "Held" is written by five surfaces and "Currently confirmed" is pinned by `ward-screen.dom.test.tsx` and by `ui-ward-roles.spec.ts` — a Playwright spec needing the app up and the machine-wide heavy lock, which is contended tonight and which I therefore cannot run. Renaming would mean shipping a change whose only guard I could not execute, to fix a wording problem. That trade is the wrong way round. Nor is renaming clearly right: whether a bed held for a patient should reduce a ward's allocatable count is a MODEL question, and if the model reconciles them the labels may not need to change at all. Handed to Ward Core rather than pre-empted here. SO THIS IS A MITIGATION AND SAYS SO. The bed note now states both distinctions in the ward's own words — that Held here is not a bed kept for a named patient, that those are counted separately in the list below, and that the two figures can disagree; and that the chip's Confirmed and the form's "currently confirmed" are different quantities. The note was pinned by nothing, so this costs no coverage and breaks no gate. A reader can now tell which number they are looking at. Two numbers that disagree are still two numbers that disagree, and only the model can close that. VERIFIER'S OTHER TWO SUSPICIONS CAME BACK CLEAN and that is a result, not an absence: bed totals reconcile on all three wards (20/20, 22/22, 11/11), and every freshness stamp was in the past with every expected time in the future. The count defects found on two other screens tonight do not reproduce here. I ALSO CHECKED A THIRD COLLISION AND IT DOES NOT EXIST. `stageCopy` carries `bed_held.shortLabel = "Held"` and `handover_ready.shortLabel = "Ready"`, both of which would clash with the chips — but this screen renders `.label`, never `.shortLabel`, so "Bed held" and "Handover ready" appear instead. Reporting the negative because I went looking for it on the strength of the two real ones, and a suspicion that survives unstated becomes a finding later. Verified: the ward-screen set discovered from disk, 20 files handed in, 20 ran, 235 passed, exit 0; prettier clean. Read on the running screen at BTY, where the new sentences render and "Held 0 … Currently confirmed 1" is visible as the note now describes. One instrument note, because it nearly became a false negative: my first check for the new text used the browser's element-name search, which reads roles and labels rather than paragraph prose, and found nothing. The change was there. A search that cannot see the kind of thing you are looking for reports absence exactly like a missing change does. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
… blank TR-D1 puts the booking on the sending team, and this department is the sending team for its own patients. The control sits beside "Mark handover ready" in the patients section, because both share one precondition: stage bed_held. The escort question opens BLANK and nothing supplies an answer for it — not the patient's legal status, not the last booking, not a "usually" (owner's ruling, relayed 2026-08-30). A pre-filled clinical judgement is answered by clicking past it, and the record then asserts that a clinician decided when nobody did. The provider picker opens unchosen for the same reason, and its options are derived from TRANSPORT_PROVIDERS rather than hand-listed. Both unavailable states use aria-disabled plus an inert handler plus a reason reachable by keyboard, never the native attribute and never both; the panel is deliberately not a <form>, so there is no implicit Enter submission for an aria-disabled confirm control to fail to stop. The reducer refuses a missing escort answer, a booking outside bed_held, a second booking and a provider off the list independently — the control mirrors those four and contradicts none of them. HANDOVER_READY's fabricated escort derivation is deliberately left in place: another session owns it and removing it here would dead-end "Mark handover ready" before this control lands. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…er fail is replaced
Ward Core made `withdrawnReferrals[].reason` a `WithdrawalReason` union with the wording
in `withdrawalReasonLabels`, so this page's stopgap can go. The label is byte-identical to
the sentence this page shipped — checked, not accepted on the claim:
withdrawalReasonLabels.another_unit_accepted
= "Withdrawn — another unit accepted this patient."
Nothing on screen changes. What changes is that the sentence has ONE home rather than two
copies agreeing today, which was the argument my own note made for wanting this.
MY VACUITY CANARY FIRED, AND THAT IS THE INTERESTING PART.
The old guard asserted that no unit name survives into the rendered withdrawal line. Once
the fixture holds `another_unit_accepted` instead of "Referral withdrawn once RGH Adult
Secure confirmed the bed", THERE IS NO NAME IN ANY INPUT THE MODEL CAN PRODUCE — so that
assertion could no longer fail. The canary beside it said so in its own words: "the
fixture's reasons no longer name anything, so this guard is vacuous."
A guard that cannot fail is worse than no guard, because it reports safety it is not
checking. So it is REPLACED rather than deleted, and rather than relaxed to keep it green
— the two ways this normally ends.
WHAT IS STILL THIS PAGE'S TO GET WRONG, and it is a different failure from the one that
started this. Prose can no longer arrive from the model, so the remaining mistake is
rendering the raw union member: `another_unit_accepted` on a clinical screen is not a
privacy failure any more, it is an incomprehensible one. That is exactly what a careless
"simplify" back to `{entry.reason}` produces. The new assertions pin the rendered text to
the shared label, reject any underscore, and still refuse every unit name in the network —
that last one now guarding the LABEL's future wording rather than the fixture's present
contents, which is where the risk actually moved to.
Mutation-proven: rendering `entry.reason` reddens with "the withdrawal code is being
rendered instead of its label: expected 'another_unit_accepted' not to be
'another_unit_accepted'".
AND I LOST THIS CHANGE ONCE WHILE PROVING IT, WHICH IS WORTH RECORDING. Restoring the
mutated file with `git show HEAD:<path>` restored the merge commit's version — correct,
clean, and not what I wanted, because the label switch was still uncommitted. The restore
did exactly what it was asked. The rule I already knew and did not follow: commit before
mutating. It cost one redo because I checked the file afterwards rather than assuming a
successful restore meant a correct one.
Verified: 3 files handed in, 3 ran, 25 passed, exit 0; eslint clean; prettier clean.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…t is showing
`referralWaitLabel` was `formatElapsed(minutesUntil(now, raisedAt))` — a clock with no
stop condition. It counts from the referral forever, including long after the person has
been triaged and is sitting in a department. Both render sites now read `referralClocks`,
which stops the referral clock at triage and reports which of the two it returned.
I NEARLY COMMITTED A FALSE CLAIM ABOUT THIS, AND THE CORRECTION IS THE USEFUL PART.
Measuring the fixture found `RF-003` reading "4h 55m waiting" against a true referral wait
of 25 minutes — nearly twelvefold, and I wrote that up as a live defect on this screen.
It is not. `RF-003` is `accepted`, and `referralQueueOrder` filters to `queued`, so this
screen never shows it. Every referral the network queue does show was triaged BEFORE the
mental-health referral was raised, so the old label was correct for all of them.
So on this surface the defect is LATENT, not live: the wrong function was wired in, and
the fixture happens to hold no queued referral that exposes it. `RF-001` is queued and not
yet in a department — triaging it during a demonstration makes it visible immediately.
The measurement was real and the inference from it was wrong. I had a number and assumed
it reached the screen; the queue filter is one function call away and I had not looked.
THAT CHANGED THE TESTS, not just the wording. There is no seeded state on this screen where
the old and new labels differ, so a test comparing them would pass on the broken code. The
guards therefore assert ADOPTION: every queue row must carry one of the two clock terms
("since referral" or "referral to triage") and must not carry the other. The old label
produced a bare "4h 40m waiting" with no term at all, so that fails on every row under the
old code regardless of whether any number was wrong — which is the assertion that would
have caught this.
Two more, both about a value that reads as its own opposite:
- `inDepartment` is `number | undefined`, and undefined means NOT THERE, never 0.
"0m in department" reads as "just got there" and would sort them as the newest arrival
while they are somewhere else.
- No row may say "arriv". The field is `triagedAt`; a patient arrives, waits, and is
triaged some time later, and on a busy night that gap is not small. Ward Referrals
caught Ward Core writing "arrival" three times beside a field named triage, which is
how a wrong word reaches a screen.
Both canaries name their own vacuity, and one names the remedy rather than the symptom:
if the seed stops containing a queued referral with no triage time, restore one rather
than delete the test.
Wording comes from `REFERRAL_CLOCK_TERMS` and never from this file — a second spelling of
one field is this project's most expensive defect class, and I have now seen it produce
two separate defects tonight.
NO MUTATION EVIDENCE IS CLAIMED HERE, deliberately. Ward Referrals reports that a mutation
whose anchor matches more than one site is REFUSED by the harness while the test run then
passes for no reason at all — a non-run that reads exactly like a pass. My earlier
mutations tonight were each confirmed applied by grepping the file afterwards, but I have
not re-audited them under that finding, so I am not restating them as proof here.
Verified: tsc exit 0; eslint clean; prettier clean; the network set discovered from disk,
12 files handed in, 12 ran, 166 passed, exit 0.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
HANDOVER_READY fabricated a transport job on the spot: it invented the provider as TRANSPORT_PROVIDERS[0] whenever the event carried no choice, and it answered the escort question by computing legalStatus !== "Voluntary". That is a clinical judgement made by no person, rendered on screen as though a clinician had made it, and wrong in both directions — a voluntary patient can need an escort, and a detained one settled enough to travel may not. It survived BOOK_TRANSPORT landing by an hour, deliberately, because removing it before a booking control existed would have dead-ended "Mark handover ready" on a screen this session does not own. The control landed at caacf1e with the escort question blank and the provider unchosen, so the bridge goes. HANDOVER_READY now REQUIRES a booked transport rather than inventing one. The two changes cannot be apart: a stage reachable with no transport, on a model where nothing else creates one, is a patient marked ready to hand over with no way to move them. THE PART WORTH READING. Repairing the callers of a new rule removes the only evidence the rule works. Fourteen walks were fixed to book first, the suite went green, and a mutation deleting the new precondition ALSO left 68 tests green — because every walk now satisfied it and none of them tested it. The precondition existed with nothing guarding it, and the green suite was what hid that. Both directions are now tested and both are mutation-proven. The test that asserted the fabrication was PRESENT has been deleted by its own instructions, which is what it was written for: it carried the removal steps in its body and was worded so it could not stay green afterwards. Its two source- reading imports went with it rather than being left as dead weight lint would have to complain about later. ed-screen.tsx's handoverBlockedReason said in its own comment that stage bed_held was "the ONLY precondition". It was, until this commit. Left alone, it would have offered a handover the reducer refuses — a control advertising an action it cannot perform. A mirror function has to move in the same change as the rule it mirrors, not the next time somebody reads it. Also here, and found by the same insertion: ward-flow-contracts.test.ts indexed its walk by hand-counted positions, so adding one step shifted every index after it and failed with a number rather than a reason. The indices are derived by name now, bracketing the step they measure. The first version of that derivation was itself wrong — findIndex returned the FIRST ACCEPT_IN_PRINCIPLE, four steps early — and the suite passed anyway, because nothing between the two touches the bed count the assertion reads. An adjacency pin now fails if a before/after pair ever stops bracketing its step. Gates: 115 files handed in / 115 ran, 1543 collected, 1543 passed, 0 failed — green in full for the first time tonight. typecheck 0 errors. eslint clean. prettier clean.
The owner's ruling: a patient's urgency tier appears on EVERY emergency department card, beside the stage, spelled out in full. Tier 3 is the substance of it, not a detail. If the tier showed only on the urgent patients, its ABSENCE would become the signal for the least urgent one, and nobody reads an absence. So the label is unconditional and sits in the same position on every card, whatever the tier. The words come from urgencyTierLabel(), the one spelling the boards and the pickers already read, never a second copy written here. Where "beside the stage" lands, per card type: - outbox row: beside the stage label in the header, which is where that card already states its stage. - patient card: beside the position line, which is that card's one statement of where the patient is in the flow, and which falls back to the stage label itself. A patient card has no dedicated stage element. One neutral tone for all three tiers. No colour-coding by tier: a colour is a threshold wearing a different costume, and nobody ruled on one. Deliberately NOT changed, all three held back by the owner: - DEFAULT_DRAFT.urgency stays 3. Opening on the least urgent tier is the software declining to escalate on a clinician's behalf. - The option values stay bare numbers; tests and the model read them. - The shortlist-panel badge still renders the bare tier. The ruling was about ED cards, and a badge has a width. The tier's test id is ward-ed-tier-, not ward-ed-patient-tier-: two suites select patient rows by the ward-ed-patient- prefix, and that name would have silently doubled their row counts. Tests: three mutations, each predicted in writing first. Hiding the tier on tier 3 fails the card-count assertion; a bare "Tier N" fails the text assertion. The third mutation, on the outbox, SURVIVED first time - no tier-3 patient sits in this department's outbox in today's fixture - so the outbox test now drives one there through the real controls, and the mutation fails as it should. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…-phases-6-7-design
…e/ward-flow-phases-6-7-design
Prettier only: line reflow and quote normalisation, verified line by line rather than assumed from the tool's name. `git diff -w` reported the same 26/10 as the plain diff, which looked like a semantic change and is not — reflowing one long line into three shows under -w as well, because the whitespace it ignores is within lines rather than between them. Reading the diff was what settled it. They arrived with the merges rather than being written here. Formatting is in neither `test`, `typecheck` nor `lint`, so the ordinary loop reports green while a changed-file check fails on the exact blob — which is why this is a commit and not a note. Left alone deliberately: an unused `ClockAdvancer` in tests/ward-capacity-view.dom.test.tsx, an eslint WARNING and not an error. It is another session's helper and "nothing imports it" is not sufficient grounds to remove an export in this repository. Flagged rather than swept. Gates: 117 files handed in / 117 ran, 1550 collected, 1550 passed, 0 failed. typecheck 0 errors. prettier clean across src/components/ward-management and tests. eslint 0 errors, 1 warning as above.
… a merge Two branches hold documents only, and merging either would have been wrong: 7 and 11 real conflicts, roughly ninety thousand lines of unrelated repository history dragged onto the ward line, and 6,722 deletions nobody has reviewed. Their value is the documents, so the documents come across as file copies. Twenty-five did not exist here at all. Nine of those are written design specs that existed on no other branch, and one is the community hub — a screen with no route, no component and no page, fully specified on a branch nobody merges. That is the reason the copy mattered more than it sounded. Five overwrote an older version, and each was checked individually rather than copied on the strength of the list. All five are genuinely newer at source, and the ledger is the clearest: 908 bytes here was a pointer stub, 261KB there is the document it points at. Seven ward documents were deliberately NOT copied because the main line's version is newer — mission-and-refusals, clinician-check, phase-6-7-decisions, roadmap, the phase-3 workspace README and the two 2026-08-25 plans. A wholesale copy of all 103 ward documents would have destroyed them silently. Verified after the fact that none of the seven moved, and that the staged set equals the intended set exactly. MY FIRST PROBE OF ALL THIRTY WAS WRONG AND SAID SO LOUDLY. It reported every file as DIFFERS, including files that do not exist here — because `git rev-parse HEAD:<missing>` ECHOES ITS ARGUMENT to stdout before failing, so the "absent" branch never matched. Had it been wrong the other way it would have said "identical" and I would have skipped the copies. Re-probed with `git cat-file -e`, which cannot answer that way. A probe that answers plausibly when it fails is worse than one that errors. All thirty verified by hash against their source blobs, 30 identical and 0 mismatched, per the instruction not to verify by eye. NOT FORMATTED, DELIBERATELY. Twenty-five fail prettier, and they fail it identically on their source branches — which follows from the hash identity rather than being separately assumed. Reformatting would break the byte-identity that makes a later re-copy or comparison meaningful, and would rewrite twenty- five documents this session does not own. Ward Flow is never pushed, so no changed-file check ever sees them. Stated rather than silently skipped. Gates: 117 files handed in / 117 ran, 1550 collected, 1550 passed, 0 failed. typecheck 0 errors. No source file changed.
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
This pull request has been ignored for the connected project Preview Branches by Supabase. |
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_6dac079c-e01e-4ae8-85d0-3a76f497b2c6) |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1888ad197a
ℹ️ 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".
| state: "occupied", | ||
| pulledAt: movement.transport?.collectedAt ?? null, | ||
| arrivedAt: event.now, |
There was a problem hiding this comment.
Create the admission only when the patient arrives
When HOLD_BED succeeds—before transport is even booked—this block appends an admission marked occupied with arrivedAt set to the hold time. RELEASE_HOLD never removes that record, while PATIENT_ARRIVED does not create or correct it, so admission-derived views can show a held or released patient as already in the bed and accrue length of stay from the wrong event. Move the admission append to PATIENT_ARRIVED; the focused regression should assert that admissions remain unchanged after hold/release and increase only after arrival.
Useful? React with 👍 / 👎.
| const run = spawnSync(`npx vitest run ${handedIn.join(" ")} --reporter=json --outputFile="${reportPath}"`, { | ||
| stdio: ["ignore", "inherit", "inherit"], | ||
| shell: true, |
There was a problem hiding this comment.
Acquire the run-coordinator lease before spawning Vitest
When this wrapper runs concurrently with verification in another worktree, spawning npx vitest directly bypasses scripts/run-vitest.mjs and the repository-wide focused/full-suite admission limits. The default invocation discovers the entire Ward suite, so concurrent calls can oversubscribe workers and induce OOM or dropped files in this or another run; checking this wrapper's own JSON report cannot protect the other process. Route the run through the coordinator and preserve its distinct busy outcome instead of reporting it as a test failure.
AGENTS.md reference: AGENTS.md:L192-L193
Useful? React with 👍 / 👎.
| <p className={styles.emptyNote} data-testid="ward-patient-search-people-empty"> | ||
| Nobody of that name or record number is known to this system. If the person in front of you is real, they need | ||
| adding before they can be referred. |
There was a problem hiding this comment.
Provide the add-patient action promised by empty search
When a search finds no person, this new state tells the clinician the person must be added before referral but exposes no link, form, or action to do so. A repository-wide search for ADD_PATIENT under src finds only the event declaration and reducer handler, with no dispatcher, so the documented search → add → refer workflow is unreachable through the UI. Add a wired action from this empty state and a DOM test proving the created person becomes searchable.
Useful? React with 👍 / 👎.
|
|
||
| export default async function WardPersonPage({ params }: { params: Promise<{ patientId: string }> }) { | ||
| const { patientId } = await params; | ||
| return <PersonScreen patientId={decodeURIComponent(patientId)} />; |
There was a problem hiding this comment.
Pass the already-decoded route parameter through unchanged
Next's route matcher already decodes dynamic parameters, so a request such as /people/foo%25bar supplies patientId === "foo%bar"; decoding it again here throws URIError: URI malformed. Consequently, an unknown identifier containing a literal percent sign produces a server error instead of reaching PersonScreen's conservative “No such person” state. Pass patientId directly, and apply the same correction to the newly added board route.
Useful? React with 👍 / 👎.
Ward Flow — synthetic bed-flow prototype (DRAFT, not for merge)
This is a draft for visibility and off-machine safety. It is not proposed for merging, and it
cannot merge as it stands — the branch is ~299 commits ahead of
mainand conflicts with it.Summary
Ward Flow is an offline synthetic prototype of a statewide psychiatric bed-flow coordination
hub, built as design scratch. It is not clinical decision support, it is not validated, and it
reaches no real data.
src/app/mockups/ward-flow/**, which 404s in production.src/components/ward-management/**.answer path. Verified file-by-file, not sampled.
configuration, a few lines teaching
ci-change-scopethat ward paths are ward paths, and ahardened
check:dead-code-candidate(Ward Flow exposed a real hole in it — four zero-importersymbols that were all alive).
The fixtures contain eight name-shaped patient records with dates of birth, UMRNs, legal status
and Mental Health Act form codes. Every one is invented.
The naming is a deliberate design decision, documented in
ward-patients-seed.tsitself: thesurnames were invented so that no real person can match, while still giving related-name search
genuine near-misses to demonstrate —
HallowaybesideHallowin,MarrowbybesideMarrowbee.Dates of birth are fixed rather than computed so screenshots stay consistent. Every screen that
renders them carries a visible synthetic-data marker.
No Mental Health Act figure, timeframe or threshold is invented anywhere in this prototype. Form
codes appear as names only (
No form,1A,3B,3D,4A,4C) with no durations attached.What landed most recently
1 2 3while every display surface said "Tier 1 · most urgent"; the ED referral form defaults to3, which is least urgent. A clinician reading a bigger number as more urgent would have filedthe sickest patient last. The default is deliberately unchanged — opening on the least urgent tier
is the software declining to escalate on a clinician's behalf.
rule left standing beneath it. It will read "Not enough data to compute" until enough referral
arrival instants exist — that is the change working, not a regression.
FD-23): no ward-facing surface reveals where else a patient has beenreferred. A guard now enforces it and found two further leaks on introduction.
Known state
pending; roughly a dozen values (catchment suburbs, transport providers, community teams, the
urgent-mark reasons) are placeholders awaiting the owner's real-world data.
the time of opening: 1550 unit tests passing, typecheck clean, eslint clean.
the task ledger, and nine design specifications.
Verification
Local only — no provider-backed gate was run and none is claimed.
Note
Low Risk
Changes are documentation, agent process, and generated indexes—no application runtime, auth, or data paths in this diff.
Overview
Adds durable “commit as you go” guidance for Cursor Cloud agents in
AGENTS.md: commit coherent units before context switches, treat formatting as its own commit, avoidgit add -A, and document pre-commit blocks whensrc/components/ortests/are dirty.Refreshes repo catalogs to match expanded Ward Flow mockups:
data/repo-awareness-snapshot.jsonbumps captured SHA and registers six new routes (morning,referrals/referrals/new,out-of-area,wards,board/[unitId]; site-map also listspeople/[patientId]), with page/mockup counts and many new ward-flow docs indexed.docs/codebase-index.mdupdates the ward model (23 units) and documents Phase 7 referral intake/board/match, Phase 8 out-of-area ledger and ward index, plus related tests.docs/site-map.mdanddocs/scripts-index.mdget matching route/script count tweaks.Introduces or extends superpowers plans for Ward Flow Phases 6 (morning bed state), 7 (referral front door), and 8 (travel bands / distance), and annotates older plans (Phase 4 gate deferral note; superseded standalone nav plan marked non-executable).
Misc: new P2 inbox item for network diagram print (
@media print);Disclosureadoption manifest addstests/ward-governance-thin-sample.test.ts.Reviewed by Cursor Bugbot for commit 1888ad1. Configure here.