From 82a1477f1268776cbd805880479f5e0ef86bf092 Mon Sep 17 00:00:00 2001
From: Claude
Date: Wed, 11 Nov 2026 15:43:32 +0000
Subject: [PATCH 1/7] Record D-153: keep the drawn-time settle check, defer
A9-01
Josh's "Yes to all recommendations" (11 Nov 2026, 15:41 UTC) keeps PR
#1607's phone release gate settle-check fix and defers a separate agent
GitHub identity until after the pilot. FACTS.md and OWNER-RULINGS.md are
regenerated.
Co-Authored-By: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01GxPPxnKy5cgHmGa8NdCd2U
---
docs/FACTS.md | 4 +-
docs/ward-flow/OWNER-RULINGS.md | 582 ++++++++++++++++----------------
docs/ward-flow/decisions.md | 9 +
3 files changed, 303 insertions(+), 292 deletions(-)
diff --git a/docs/FACTS.md b/docs/FACTS.md
index de66aaee5..6515eed8b 100644
--- a/docs/FACTS.md
+++ b/docs/FACTS.md
@@ -13,8 +13,8 @@ green. Quote a fact instead of restating it, and it updates itself when the sour
| `migration-count` | 18 | Numbered database migrations | `backend/ward-flow/migrations/` |
| `ward-route-count` | 61 | `page.tsx` routes under `/ward-flow` | `src/app/ward-flow/` |
| `app-route-count` | 65 | Pages and route handlers: the Ward Flow routes plus sign-in and the `/mockups` digest | `src/app/ward-flow/, src/app/ward-flow-sign-in/, src/app/mockups/` |
-| `latest-decision` | D-152 | Newest entry in the decision log | `docs/ward-flow/decisions.md` |
-| `next-decision` | D-153 | Number to use for the next decision | `docs/ward-flow/decisions.md` |
+| `latest-decision` | D-153 | Newest entry in the decision log | `docs/ward-flow/decisions.md` |
+| `next-decision` | D-154 | Number to use for the next decision | `docs/ward-flow/decisions.md` |
| `node-engine` | >=24.22.1 <25 | Supported Node.js range | `package.json` |
| `backend-env-vars` | 33 names, [listed below](#backend-env-vars) | Environment variables the shared backend reads | `backend/ward-flow/*.mjs` |
diff --git a/docs/ward-flow/OWNER-RULINGS.md b/docs/ward-flow/OWNER-RULINGS.md
index f1964fba9..e0b2b08b3 100644
--- a/docs/ward-flow/OWNER-RULINGS.md
+++ b/docs/ward-flow/OWNER-RULINGS.md
@@ -9,7 +9,7 @@ Scanned `docs/ward-flow/decisions.md`, `docs/ward-flow/owner-*.md` and `docs/his
One file matches `*owner*` but not `owner-*` and is deliberately excluded: `how-to-write-to-the-owner.md`
— it is instructions for writing TO the owner, not a record of what he ruled.
-**705 rulings/items extracted, across 43 of 44 files.**
+**706 rulings/items extracted, across 43 of 44 files.**
**0 file(s) UNPARSED** — no recognised ruling structure found; listed, not dropped. See below.
**1 file(s) recorded as holding no ruling of their own**, each with its reason. See below.
@@ -32,7 +32,7 @@ line, in the order discovered — confirm which instance you mean before citing
---
-## ID index — 455 distinct IDs
+## ID index — 456 distinct IDs
So an ID like `D-9` or `O-17.1` can be looked up directly, without knowing which file it lives in.
Bare numbered rulings (files that number "1., 2., 3. …" with no owner-issued letter code) are
@@ -72,248 +72,249 @@ safe global key across 30 files.
| `D-1` ⚠️ **3 occurrences in 3 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-12-four-answers.md`:16 | D-1 · Legal forms: split the list in two |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:85 | D-1 — 🔴 The screen is called **Patient**, not "Patient Now" |
-| ↳ | `decisions.md`:172 | D-1. Ward Flow is local only, with no linked repository (Replaced by D-27) |
+| ↳ | `decisions.md`:173 | D-1. Ward Flow is local only, with no linked repository (Replaced by D-27) |
| `D-2` ⚠️ **3 occurrences in 3 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-12-four-answers.md`:57 | D-2 · Raise the three unbindable table thresholds |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:108 | D-2 — 🔴 Communication: BUILD IT. The system must be able to tell someone something. |
-| ↳ | `decisions.md`:182 | D-2. Railway is disconnected |
+| ↳ | `decisions.md`:183 | D-2. Railway is disconnected |
| `D-3` ⚠️ **2 occurrences in 2 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:215 | D-3 — 🔴 The 12px floor is ADOPTED. Screen by screen, not as a sweep. |
-| ↳ | `decisions.md`:193 | D-3. The PsychSift and Database code is being retired from Ward Flow |
+| ↳ | `decisions.md`:194 | D-3. The PsychSift and Database code is being retired from Ward Flow |
| `D-3b` | `../history/archive/dated-notes/owner-decisions-2026-09-12-four-answers.md`:77 | D-3b · The data-conditional absence sweep is DEFERRED |
| `D-4` ⚠️ **4 occurrences in 3 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-12-four-answers.md`:97 | D-4 · The deliberate sub-12px exception stands if its reasoning is sound |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:263 | D-4 · WARD LEAD RULING, not the owner's — the Access record's false assurance |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:390 | D-4 ADDENDUM, 2026-09-11 — 🔴 the OTHER half of that sentence, found by Lane C after the fix |
-| ↳ | `decisions.md`:201 | D-4. Synthetic patients only |
+| ↳ | `decisions.md`:202 | D-4. Synthetic patients only |
| `D-5` ⚠️ **4 occurrences in 3 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-12-routing-and-nine-flags.md`:15 | D-5 · 🔴 A NEW STANDING RULE: PROTECTED-WORK DELETIONS COME TO WARD VERIFIER FIRST |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:340 | D-5 · WARD LEAD RULING, not the owner's — gender reaches BOTH bed gates or neither |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:431 | D-5 MEASUREMENT, 2026-09-11 — the wiring task handed back, and the arithmetic is starker than I expected |
-| ↳ | `decisions.md`:209 | D-5. Parallel threads in separate worktrees, folded one at a time |
+| ↳ | `decisions.md`:210 | D-5. Parallel threads in separate worktrees, folded one at a time |
| `D-6` ⚠️ **3 occurrences in 3 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-12-routing-and-nine-flags.md`:52 | D-6 · 🟢 THE `--t-0` COLLISION — amend the brief in the same change |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:472 | D-6 · WARD LEAD RULING — "Not recorded" and "Not yet recorded" are BOTH used, deliberately |
-| ↳ | `decisions.md`:220 | D-6. Aboriginal cultural safety review deferred |
+| ↳ | `decisions.md`:221 | D-6. Aboriginal cultural safety review deferred |
| `D-7` ⚠️ **3 occurrences in 3 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-12-routing-and-nine-flags.md`:72 | D-7 · 🟢 THE REMAINING SEVEN ARE FLAGGED TO WARD LEAD |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:499 | D-7 · WARD LEAD RULING — the Patient screen SHOWS gender, and the addition is logged as an addition |
-| ↳ | `decisions.md`:228 | D-7. The first four milestones, in this order |
+| ↳ | `decisions.md`:229 | D-7. The first four milestones, in this order |
| `D-8` ⚠️ **3 occurrences in 3 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-12-fold-and-authorisation.md`:9 | D-8 · 🔴 "FOLD INTO MAIN" ALWAYS MEANS THE LOCAL WARD FLOW LINE |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:545 | D-8 · WARD LEAD RULING — the announced marker says "invented NAMES", never "invented people" |
-| ↳ | `decisions.md`:240 | D-8. Josh's standing rulings, numbered R1 to R16, for every session and tool |
+| ↳ | `decisions.md`:241 | D-8. Josh's standing rulings, numbered R1 to R16, for every session and tool |
| `D-9` ⚠️ **3 occurrences in 3 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-12-fold-and-authorisation.md`:51 | D-9 · 🟢 THE MENTAL HEALTH ACT AUTHORISATION GATE IS OVERRIDABLE — with the owner's clinical reason |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:589 | D-9 · WARD LEAD RULING — the referral board's table MAY scroll sideways; the missing thing is the affordance |
-| ↳ | `decisions.md`:272 | D-9. Josh's rulings of 25 September, 11:43 to 12:00 (R17 to R21) |
+| ↳ | `decisions.md`:273 | D-9. Josh's rulings of 25 September, 11:43 to 12:00 (R17 to R21) |
| `D-10` ⚠️ **3 occurrences in 3 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-12-fold-and-authorisation.md`:91 | D-10 · ⏸️ THE SMALL-TEXT RAISE IS DEFERRED — with the triggers written down, because a deferral without one is a drop |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:618 | D-10 · WARD LEAD RULING — the overflow affordance is a SENTENCE first and a measured border second, not the standard's shade |
-| ↳ | `decisions.md`:286 | D-10. Gate build type check (R22) |
+| ↳ | `decisions.md`:287 | D-10. Gate build type check (R22) |
| `D-11` ⚠️ **3 occurrences in 3 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-12-fold-and-authorisation.md`:129 | D-11 · 🔴 TWO LEDGERS IS CORRECT AND INTENDED — WARD FLOW TASKS GO ON THE WARD FLOW LEDGER, AND THE MAIN PROJECT'S IS DISREGARDED FOR THIS PROJECT |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:685 | D-11 · A-1 — the half-written referral history WARNS, and is never stored |
-| ↳ | `decisions.md`:295 | D-11. Rulings of 25 September, 12:41 (R23 to R25) |
+| ↳ | `decisions.md`:296 | D-11. Rulings of 25 September, 12:41 (R23 to R25) |
| `D-12` ⚠️ **2 occurrences in 2 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:704 | D-12 · A-2 — the referrer is the RECORDED SOURCE, and a person only if the record carries one |
-| ↳ | `decisions.md`:311 | D-12. Rulings of 25 September, 12:43 (R26 to R31) |
+| ↳ | `decisions.md`:312 | D-12. Rulings of 25 September, 12:43 (R26 to R31) |
| `D-13` ⚠️ **2 occurrences in 2 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:720 | D-13 · A-3 and A-4 — both resolve FOR THE CODE; the drawings change |
-| ↳ | `decisions.md`:330 | D-13. Tooling tests in batch gates (R32) |
+| ↳ | `decisions.md`:331 | D-13. Tooling tests in batch gates (R32) |
| `D-14` ⚠️ **2 occurrences in 2 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:739 | D-14 · A-6 and A-8 — ADD THE LINK, AND MAKE THE GUARD ENFORCE IN THE SAME CHANGE. Never one without the other. |
-| ↳ | `decisions.md`:340 | D-14. Standing defaults: decide small things without asking |
+| ↳ | `decisions.md`:341 | D-14. Standing defaults: decide small things without asking |
| `D-15` ⚠️ **2 occurrences in 2 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:778 | D-15 · A-7 — the figure NAMES ITS OWN CLOCK, or it does not ship |
-| ↳ | `decisions.md`:352 | D-15. Rulings of 25 September, 13:26 (R33 to R35) |
+| ↳ | `decisions.md`:353 | D-15. Rulings of 25 September, 13:26 (R33 to R35) |
| `D-16` ⚠️ **2 occurrences in 2 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:799 | D-16 · WARD LEAD RULING — the unwired primary actions use §8.6's EXISTING sentence; nothing new is invented |
-| ↳ | `decisions.md`:365 | D-16. Legal overrides and form receipts, 25 September 13:51 UTC |
+| ↳ | `decisions.md`:366 | D-16. Legal overrides and form receipts, 25 September 13:51 UTC |
| `D-17` ⚠️ **2 occurrences in 2 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:843 | D-17 · WARD LEAD RULING — A-8: FD-23 wins. The Patient screen never shows where else a patient has been. |
-| ↳ | `decisions.md`:378 | D-17. Morning roll-up time is Josh's own default (25 September) |
+| ↳ | `decisions.md`:379 | D-17. Morning roll-up time is Josh's own default (25 September) |
| `D-18` ⚠️ **2 occurrences in 2 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:878 | D-18 · WARD LEAD RULING — the referral query contract carries MODEL values; and `gp` is a question for the owner, not a mapping for me |
-| ↳ | `decisions.md`:389 | D-18. The privacy lock stops saving only for typed text (25 September) |
+| ↳ | `decisions.md`:390 | D-18. The privacy lock stops saving only for typed text (25 September) |
| `D-19` ⚠️ **2 occurrences in 2 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:922 | D-19 · WARD LEAD RULING — "something is already open for this person" is PERMITTED; "where" never is |
-| ↳ | `decisions.md`:400 | D-19. Rulings of 25 September, evening (walkthrough, leave beds, PsychSift, gender display) |
+| ↳ | `decisions.md`:401 | D-19. Rulings of 25 September, evening (walkthrough, leave beds, PsychSift, gender display) |
| `D-20` ⚠️ **3 occurrences in 2 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:967 | D-20 · WARD LEAD RULING — the drawing wins; the vocabulary anchor was never the heading |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:1010 | D-20-REVISED · the drawing renames BOTH, so neither side is taken whole |
-| ↳ | `decisions.md`:430 | D-20. One high-contrast colour check retired (26 September) |
+| ↳ | `decisions.md`:431 | D-20. One high-contrast colour check retired (26 September) |
| `D-21` ⚠️ **2 occurrences in 2 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:1058 | D-21 · WARD LEAD RULING — tasks 10 and 12 are CLOSED, not deferred, and not built as callerless modules |
-| ↳ | `decisions.md`:443 | D-21. Coordinator bed board: all nine recommended answers (26 September) |
+| ↳ | `decisions.md`:444 | D-21. Coordinator bed board: all nine recommended answers (26 September) |
| `D-22` ⚠️ **2 occurrences in 2 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:1084 | D-22 · SHAPE 2 — the landmark label and the heading stop being two strings |
-| ↳ | `decisions.md`:455 | D-22. No legal limits in hour rules; the rest are labelled defaults (26 September) |
+| ↳ | `decisions.md`:456 | D-22. No legal limits in hour rules; the rest are labelled defaults (26 September) |
| `D-23` ⚠️ **2 occurrences in 2 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:1121 | D-23 · The bed board passes an empty leave-bed list into three capacity figures, and says nothing |
-| ↳ | `decisions.md`:471 | D-23. Leave-bed fields kept; patient search stops matching by ward (26 September) |
+| ↳ | `decisions.md`:472 | D-23. Leave-bed fields kept; patient search stops matching by ward (26 September) |
| `D-24` ⚠️ **2 occurrences in 2 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:1142 | D-24 · The raw `1.2` line-height — token it, and accept that community no longer matches |
-| ↳ | `decisions.md`:485 | D-24. Hour-rules morning list: all A (26 September) |
+| ↳ | `decisions.md`:486 | D-24. Hour-rules morning list: all A (26 September) |
| `D-25` ⚠️ **2 occurrences in 2 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:1161 | D-25 · Command's `deadlineLine` sentence is not built — and this DEFERS the R-5 question rather than answering it |
-| ↳ | `decisions.md`:509 | D-25. The lone-patient check counts free beds on both matching paths (26 September) |
+| ↳ | `decisions.md`:510 | D-25. The lone-patient check counts free beds on both matching paths (26 September) |
| `D-26` ⚠️ **2 occurrences in 2 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:1184 | D-26 · `edPressure` becomes a projection of `edHomeSummaries` — the two screens stop being able to disagree |
-| ↳ | `decisions.md`:517 | D-26. Select fold checks by changed risk (27 September) |
+| ↳ | `decisions.md`:518 | D-26. Select fold checks by changed risk (27 September) |
| `D-27` ⚠️ **2 occurrences in 2 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:1213 | D-27 · The word for a free bed is READY — and the ruling is about the WORD, not the identifier |
-| ↳ | `decisions.md`:535 | D-27. Ward Flow uses its dedicated repository (28 September) |
-| `D-28` | `decisions.md`:548 | D-28. Design Modernization & Anti-Box Architecture (30 September 2026) |
-| `D-29` | `decisions.md`:562 | D-29. Act time limits shown as a labelled synthetic demo (4 October 2026) |
+| ↳ | `decisions.md`:536 | D-27. Ward Flow uses its dedicated repository (28 September) |
+| `D-28` | `decisions.md`:549 | D-28. Design Modernization & Anti-Box Architecture (30 September 2026) |
+| `D-29` | `decisions.md`:563 | D-29. Act time limits shown as a labelled synthetic demo (4 October 2026) |
| `D-30` ⚠️ **2 occurrences in 2 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:1238 | D-30 · `Unit.forensic` is a WARD flag, and every rendered string says so |
-| ↳ | `decisions.md`:577 | D-30. Central Coordinator Approval for Inter-Ward Bed Transfers (6 October 2026) |
+| ↳ | `decisions.md`:578 | D-30. Central Coordinator Approval for Inter-Ward Bed Transfers (6 October 2026) |
| `D-31` ⚠️ **2 occurrences in 2 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:1367 | D-31 · The plan's disposition — delete one half, re-derive the other |
-| ↳ | `decisions.md`:584 | D-31. Gender-Diverse Bed Placement Policy & Clinical Override Safety (6 October 2026) |
+| ↳ | `decisions.md`:585 | D-31. Gender-Diverse Bed Placement Policy & Clinical Override Safety (6 October 2026) |
| `D-32` ⚠️ **2 occurrences in 2 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:1289 | D-32 · D-6 is restated: ONE WORDING PER STATE, and the states are named |
-| ↳ | `decisions.md`:591 | D-32. FIFO Waiting Time with Clinical Triage Badging vs Algorithmic Urgency Sorting (6 October 2026) |
+| ↳ | `decisions.md`:592 | D-32. FIFO Waiting Time with Clinical Triage Badging vs Algorithmic Urgency Sorting (6 October 2026) |
| `D-33` ⚠️ **2 occurrences in 2 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:1322 | D-33 · "Nothing outstanding" does three jobs, and only one of them may keep the words |
-| ↳ | `decisions.md`:598 | D-33. Separation of Local Ward Shift Notes from Statewide Coordinator Telemetry (6 October 2026) |
+| ↳ | `decisions.md`:599 | D-33. Separation of Local Ward Shift Notes from Statewide Coordinator Telemetry (6 October 2026) |
| `D-34` ⚠️ **2 occurrences in 2 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:1347 | D-34 · A hypothetical destination row may not use obligation vocabulary at all |
-| ↳ | `decisions.md`:605 | D-34. Acute Medical Deterioration Bed Release and Escalation Protocol (6 October 2026) |
+| ↳ | `decisions.md`:606 | D-34. Acute Medical Deterioration Bed Release and Escalation Protocol (6 October 2026) |
| `D-35` ⚠️ **2 occurrences in 2 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:1388 | D-35 · The ED pressure strip does not narrow to the selected service, and #6 is CLOSED |
-| ↳ | `decisions.md`:612 | D-35. Deferral of Regional Multi-Leg Transport & RFDS Repatriation (6 October 2026) |
+| ↳ | `decisions.md`:613 | D-35. Deferral of Regional Multi-Leg Transport & RFDS Repatriation (6 October 2026) |
| `D-36` ⚠️ **2 occurrences in 2 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:1405 | D-36 · _Referrals into this ward_ / _into the team_ — PRESENTATION, with a condition |
-| ↳ | `decisions.md`:619 | D-36. Affirmation of External Governance & Clinical Safety Hard Gates (6 October 2026) (Replaced by D-87) |
+| ↳ | `decisions.md`:620 | D-36. Affirmation of External Governance & Clinical Safety Hard Gates (6 October 2026) (Replaced by D-87) |
| `D-37` ⚠️ **2 occurrences in 2 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:1411 | D-37 · _Clinically ready, not yet gone_ — PRESENTATION, and the heading must name its subject |
-| ↳ | `decisions.md`:634 | D-37. External Governance Hard Gate Operational Postures & Resolution Roadmap (6 October 2026) (Replaced by D-87) |
+| ↳ | `decisions.md`:635 | D-37. External Governance Hard Gate Operational Postures & Resolution Roadmap (6 October 2026) (Replaced by D-87) |
| `D-38` ⚠️ **2 occurrences in 2 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:1417 | D-38 · _People currently in a hospital bed_ on community team statistics — an aggregate that identifies at small N |
-| ↳ | `decisions.md`:661 | D-38. Leave, absent without leave and CTO on the Patient page (9 October 2026) |
+| ↳ | `decisions.md`:662 | D-38. Leave, absent without leave and CTO on the Patient page (9 October 2026) |
| `D-39` ⚠️ **2 occurrences in 2 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:1437 | D-39 · _Contacts_ and _Time to first contact_ — HELD, build neither |
-| ↳ | `decisions.md`:679 | D-39. Patients are identified by UMRN, never by a WF number (9 October 2026) |
+| ↳ | `decisions.md`:680 | D-39. Patients are identified by UMRN, never by a WF number (9 October 2026) |
| `D-40` ⚠️ **2 occurrences in 2 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:1445 | D-40 · An empty check array never claims reconciliation — on either shell surface |
-| ↳ | `decisions.md`:723 | D-40. A second community treatment order keeps the first (9 October 2026) |
+| ↳ | `decisions.md`:724 | D-40. A second community treatment order keeps the first (9 October 2026) |
| `D-41` ⚠️ **2 occurrences in 2 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:1478 | D-41 · `corridorCounts` is extended, and its POPULATION changes |
-| ↳ | `decisions.md`:734 | D-41. Delays board filters narrow the table and state the hidden count (9 October 2026) |
+| ↳ | `decisions.md`:735 | D-41. Delays board filters narrow the table and state the hidden count (9 October 2026) |
| `D-42` ⚠️ **2 occurrences in 2 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:1490 | D-42 · _Referrals into this ward_ renders bare counts. No share. |
-| ↳ | `decisions.md`:747 | D-42. 12px typography floor with no uppercase exception reaffirmed in Design System v9 (9–10 October 2026) |
+| ↳ | `decisions.md`:748 | D-42. 12px typography floor with no uppercase exception reaffirmed in Design System v9 (9–10 October 2026) |
| `D-43` ⚠️ **2 occurrences in 2 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:1724 | D-43 · Ward Lead ruling — the bed board's ordinal contradiction is a LANE TASK |
-| ↳ | `decisions.md`:753 | D-43. Colour lives in glyphs and the hero; red means act now with a thin 1px edge, never a fill (9 October 2026) |
+| ↳ | `decisions.md`:754 | D-43. Colour lives in glyphs and the hero; red means act now with a thin 1px edge, never a fill (9 October 2026) |
| `D-44` ⚠️ **2 occurrences in 2 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:1791 | D-44 · The community scope note is reworded and the comparison panel stays |
-| ↳ | `decisions.md`:760 | D-44. Compact sizing, white cards and smaller text retained over expression-test card tints (9 October 2026) |
+| ↳ | `decisions.md`:761 | D-44. Compact sizing, white cards and smaller text retained over expression-test card tints (9 October 2026) |
| `D-45` ⚠️ **2 occurrences in 2 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:1803 | D-45 · 🔴 THE SEED DECISION — the fixture gains BOTH, as ONE change, after the statistics screens |
-| ↳ | `decisions.md`:766 | D-45. Phone is its own 390×844 design, never a desktop reflow (9 October 2026) |
+| ↳ | `decisions.md`:767 | D-45. Phone is its own 390×844 design, never a desktop reflow (9 October 2026) |
| `D-46` ⚠️ **2 occurrences in 2 files — not unique** | | |
| ↳ | `../history/archive/dated-notes/owner-decisions-2026-09-1x.md`:1984 | D-46 · A guard that fails whenever the machine is busy |
-| ↳ | `decisions.md`:773 | D-46. Every page has a focal part, a part of its own, a page-specific hero and command-bar action rules (9 October 2026) |
-| `D-47` | `decisions.md`:779 | D-47. Glass stays about 80% opaque with the top sheen kept (10 October 2026) |
-| `D-48` | `decisions.md`:785 | D-48. Figures stay in Geist Mono with tabular digits while chart axes use Geist Sans (10 October 2026) |
-| `D-49` | `decisions.md`:791 | D-49. Ward Flow 44px default row target (dense 36px, touch 52px) vs retired 48px PsychSift tap knob (10 October 2026) |
-| `D-50` | `decisions.md`:797 | D-50. Neutral T1 urgency pill vs Form 1A amber statutory warning badge (10 October 2026) |
-| `D-51` | `decisions.md`:803 | D-51. Delays filters hide rows with a stated hidden count while other queues highlight by default (10 October 2026) |
-| `D-52` | `decisions.md`:810 | D-52. 10 October page review rulings: time-axis overdue zone, 15px/20px strip figures, 8px people pips and incremental shared parts (10 October 2026) |
-| `D-53` | `decisions.md`:816 | D-53. Elevate design language and Glare mode adopted for Design System v9 (10 October 2026) |
-| `D-54` | `decisions.md`:823 | D-54. Tier time thresholds remain synthetic demo values requiring clinical sign-off (10 October 2026) |
-| `D-55` | `decisions.md`:831 | D-55. Supabase is Ward Flow's database and sign-in provider, and Azure is retired (15 October 2026) |
-| `D-56` | `decisions.md`:837 | D-56. Josh signs the clinical values that have a recorded source, for the prototype (15 October 2026) |
-| `D-57` | `decisions.md`:843 | D-57. Twenty Top 25 items move from the showcase into the real screens, each behind its own flag (15 October 2026) |
-| `D-58` | `decisions.md`:870 | D-58. Josh signs three more clinical values that have a national source, for the prototype (15 October 2026) |
-| `D-59` | `decisions.md`:876 | D-59. Each role sees only what it needs, from one access table (15 October 2026) |
-| `D-60` | `decisions.md`:882 | D-60. Criteria led discharge, for voluntary patients only (15 October 2026) |
-| `D-61` | `decisions.md`:891 | D-61. Tasks and Alerts are one queue called Alerts (16 October 2026) |
-| `D-62` | `decisions.md`:897 | D-62. The one Alerts queue is signed off and its flag removed (16 October 2026) |
-| `D-63` | `decisions.md`:903 | D-63. Josh signs the remaining clinical values and the criteria list, for the prototype (16 October 2026) |
-| `D-64` | `decisions.md`:911 | D-64. Criteria led discharge shows on the boards and warns at departure (16 October 2026) |
-| `D-65` | `decisions.md`:918 | D-65. Criteria led discharge on in production, typed patient specific criteria, and the AWOL bed hold review paused (17 October 2026) |
-| `D-66` | `decisions.md`:934 | D-66. Granted roles work without a second approval (17 October 2026) |
-| `D-67` | `decisions.md`:942 | D-67. Recover synthetic form drafts within the browser session (17 October 2026) |
-| `D-68` | `decisions.md`:949 | D-68. Bed meeting TV content and access (19 October 2026) |
-| `D-69` | `decisions.md`:957 | D-69. A referral with no legal status: "Needs an involuntary-capable bed until status is set." (20 October 2026) |
-| `D-70` | `decisions.md`:964 | D-70. No computed Mental Health Act periods: show only the recorded due time (20 October 2026) |
-| `D-71` | `decisions.md`:970 | D-71. Audit 1 clinical safety fixes: no suggested wards, wait-order queues, recorded facts only (20 October 2026) |
-| `D-72` | `decisions.md`:983 | D-72. Seven product decisions for the 21 October fix plan (21 October 2026) |
-| `D-73` | `decisions.md`:999 | D-73. Batch merge safeguards (21 October 2026) |
-| `D-74` | `decisions.md`:1015 | D-74. Owner answers to the 21 October open questions (21 October 2026) |
-| `D-75` | `decisions.md`:1036 | D-75. UX audit follow-up, page caching, community gaps and the extra-travel bed hold (21 October 2026) |
-| `D-76` | `decisions.md`:1049 | D-76. Community referral gaps: the full move journey and asking more wards (21 October 2026) |
-| `D-77` | `decisions.md`:1058 | D-77. The ten bigger UX changes approved for build (22 October 2026) |
-| `D-78` | `decisions.md`:1078 | D-78. One decline list for wards and coordinators (22 October 2026) |
-| `D-79` | `decisions.md`:1089 | D-79. Wards see the full referral, with name and UMRN, before they answer (23 October 2026) |
-| `D-80` | `decisions.md`:1100 | D-80. Role access plan version 2: all 17 recommendations (23 October 2026) |
-| `D-81` | `decisions.md`:1115 | D-81. Pulled, with a pull time limit, for a bed kept for an incoming patient (24 October 2026) |
-| `D-82` | `decisions.md`:1122 | D-82. A PR may raise a phone size limit by up to 5 KB per screen without asking (26 October 2026) |
-| `D-83` | `decisions.md`:1130 | D-83. Patient names stay in full on every screen, copy and TV; privacy work must not make the app less useful (26 October 2026) |
-| `D-84` | `decisions.md`:1141 | D-84. The live app stays on Railway Singapore for now, and there is no server-side TV account yet (26 October 2026) |
-| `D-85` | `decisions.md`:1149 | D-85. "Held" only for a bed kept for its own patient; "pulled" for an incoming bed; "held up" for a delay (26 October 2026) |
-| `D-86` | `decisions.md`:1156 | D-86. No second approver for role grants, at pilot or otherwise (27 October 2026) |
-| `D-87` | `decisions.md`:1163 | D-87. D-36 and D-37 are replaced by the real-data gate (27 October 2026) |
-| `D-88` | `decisions.md`:1175 | D-88. The public demo opens with a demo code once the front door is on (27 October 2026) |
-| `D-89` | `decisions.md`:1182 | D-89. Redesign mock-up copies of large screens may pass the 1,500-line cap (30 October 2026) |
-| `D-90` | `decisions.md`:1190 | D-90. Help gets How do I…, Show me runs everywhere, and Help is built without a separate mock-up step (30 October 2026) |
-| `D-91` | `decisions.md`:1201 | D-91. One design home for the redesign, and Josh's answers on Help, Settings, search and notifications (31 October 2026) |
-| `D-92` | `decisions.md`:1236 | D-92. Help goes live before its mock-up review, one search box on Capacity, and referral rows on Home open the panel (31 October 2026) |
-| `D-93` | `decisions.md`:1246 | D-93. Legal form times show only as typed, never as a countdown (31 October 2026) |
-| `D-94` | `decisions.md`:1254 | D-94. Design rulings of 31 October 2026 |
-| `D-95` | `decisions.md`:1267 | D-95. One page scroll, and a click outside closes a drawer (3 November 2026) |
-| `D-96` | `decisions.md`:1277 | D-96. A named bed is pulled, and its patient is not also waiting (8 November 2026) |
-| `D-97` | `decisions.md`:1287 | D-97. Owner answers on seven open items: N21, N25, N26, N29, N40, N41 and WF-B008 (8 November 2026) |
-| `D-98` | `decisions.md`:1300 | D-98. Friction pass: Quick bar, Patient peek and Ward peek (8 November 2026) |
-| `D-99` | `decisions.md`:1309 | D-99. "Real details" in the referral drawer's sample patients (25 September 2026) |
-| `D-100` | `decisions.md`:1315 | D-100. "Under a form" means one rule everywhere (9 November 2026) |
-| `D-101` | `decisions.md`:1322 | D-101. Clinically ready for discharge, with one delay reason (9 November 2026) |
-| `D-102` | `decisions.md`:1331 | D-102. Report-a-problem screenshots are blurred, and patient-table exports carry no names (9 November 2026) |
-| `D-103` | `decisions.md`:1340 | D-103. Any coordinator on shift may mark a bed override reviewed (9 November 2026) |
-| `D-104` | `decisions.md`:1347 | D-104. Care plans and safety flags: five defaults (9 November 2026) |
-| `D-105` | `decisions.md`:1359 | D-105. Optional clerk details on the patient record (8 November 2026) |
-| `D-106` | `decisions.md`:1366 | D-106. Clinical values from the 9 November 2026 documentation audit (9 November 2026) |
-| `D-107` | `decisions.md`:1378 | D-107. Working rules and documentation from the 9 November 2026 audit (9 November 2026) |
-| `D-108` | `decisions.md`:1392 | D-108. Six bed-flow features: keep D-93 and D-72, ED long-wait next steps (9 November 2026) |
-| `D-109` | `decisions.md`:1401 | D-109. Home's bed alert line tests the whole number shown (9 November 2026) |
-| `D-110` | `decisions.md`:1408 | D-110. ED and referral flow: neutral fit line, clearance timing and alternatives to admission (9 November 2026) |
-| `D-111` | `decisions.md`:1418 | D-111. Review and audit plan: first guards and four open answers (9 November 2026) |
-| `D-112` | `decisions.md`:1432 | D-112. Phone bottom bar: the floating bar (9 November 2026) |
-| `D-113` | `decisions.md`:1441 | D-113. Second patient identifier on accept and Mark done; no initials search (9 November 2026) |
-| `D-114` | `decisions.md`:1451 | D-114. Allow pull_request_target for three workflows only (9 November 2026) |
-| `D-115` | `decisions.md`:1459 | D-115. One count on every screen, and a missing patient shown everywhere (9 November 2026) |
-| `D-116` | `decisions.md`:1473 | D-116. Page tops: the PR #1581 recommendations stand (9 November 2026) |
-| `D-117` | `decisions.md`:1490 | D-117. Care plans follow-up: frequent presenter count, tribunal date and ward flags (10 November 2026) |
-| `D-118` | `decisions.md`:1499 | D-118. Page tops: Referrals and Ward phone limits up to 8 KB above main (10 November 2026) |
-| `D-119` | `decisions.md`:1505 | D-119. Page tops: phone limits raised to measured sizes, trim after merge (10 November 2026) |
-| `D-120` | `decisions.md`:1512 | D-120. Discharge and capacity values: pull limit, transport delay and discharge date slip (9 November 2026) |
-| `D-121` | `decisions.md`:1521 | D-121. Working rules, guards and your PC from the 10 November 2026 audit (10 November 2026) |
-| `D-122` | `decisions.md`:1535 | D-122. Clinical and privacy answers from the 10 November 2026 audit (10 November 2026) |
-| `D-123` | `decisions.md`:1548 | D-123. Master audit list answers: two clinical fixes and five working rules (10 November 2026) |
-| `D-124` | `decisions.md`:1562 | D-124. Offline saves whose record changed are refused and shown (10 November 2026) |
-| `D-125` | `decisions.md`:1569 | D-125. Top 10 audit follow-ups approved (10 November 2026) |
-| `D-126` | `decisions.md`:1581 | D-126. Ranked lists: neutral orders approved (10 November 2026) |
-| `D-127` | `decisions.md`:1600 | D-127. Neutral wording for a community treatment order at arrival (10 November 2026) |
-| `D-128` | `decisions.md`:1607 | D-128. Final master audit: nine answers (10 November 2026) |
-| `D-129` | `decisions.md`:1622 | D-129. Master audit findings: every recommendation approved (10 November 2026) |
-| `D-130` | `decisions.md`:1672 | D-130. Audit follow-ups: every remaining recommendation approved (10 November 2026) |
-| `D-131` | `decisions.md`:1688 | D-131. Hard-case demo data allowed, and the movement diagram follows the engine (10 November 2026) |
-| `D-132` | `decisions.md`:1697 | D-132. Audit fixes group 1: docs and CI guards (10 November 2026) |
-| `D-135` | `decisions.md`:1707 | D-135. Batch 4, outages and pilot control: pack age and plan answers (10 November 2026) |
-| `D-136` | `decisions.md`:1716 | D-136. Feature batch 3 (shift change and follow-up): two signed values (10 November 2026) |
-| `D-137` | `decisions.md`:1728 | D-137. Feature build batch 5: count leak first, TV change mark and QR library (10 November 2026) |
-| `D-138` | `decisions.md`:1744 | D-138. Phone alert urgency by kind (10 November 2026) |
-| `D-139` | `decisions.md`:1750 | D-139. Phone Statistics ward list A to Z, and leave return times as typed (10 November 2026) |
-| `D-142` | `decisions.md`:1760 | D-142. Batch 4: design review and all twelve answers approved, D-80 amended for site features (11 November 2026) |
-| `D-143` | `decisions.md`:1772 | D-143. MHERT is MHERL in the glossary; SMHMR deferred (11 November 2026) |
-| `D-144` | `decisions.md`:1780 | D-144. Chip rows move only sideways on a phone (11 November 2026) |
-| `D-146` | `decisions.md`:1786 | D-146. Performance audit fixes and the phone speed target (11 November 2026) |
-| `D-147` | `decisions.md`:1792 | D-147. Demo-code tries keep per-address keying until a trusted proxy is set (11 November 2026) |
-| `D-152` | `decisions.md`:1798 | D-152. Returned to ED prompt sees every return in shared mode (11 November 2026) |
+| ↳ | `decisions.md`:774 | D-46. Every page has a focal part, a part of its own, a page-specific hero and command-bar action rules (9 October 2026) |
+| `D-47` | `decisions.md`:780 | D-47. Glass stays about 80% opaque with the top sheen kept (10 October 2026) |
+| `D-48` | `decisions.md`:786 | D-48. Figures stay in Geist Mono with tabular digits while chart axes use Geist Sans (10 October 2026) |
+| `D-49` | `decisions.md`:792 | D-49. Ward Flow 44px default row target (dense 36px, touch 52px) vs retired 48px PsychSift tap knob (10 October 2026) |
+| `D-50` | `decisions.md`:798 | D-50. Neutral T1 urgency pill vs Form 1A amber statutory warning badge (10 October 2026) |
+| `D-51` | `decisions.md`:804 | D-51. Delays filters hide rows with a stated hidden count while other queues highlight by default (10 October 2026) |
+| `D-52` | `decisions.md`:811 | D-52. 10 October page review rulings: time-axis overdue zone, 15px/20px strip figures, 8px people pips and incremental shared parts (10 October 2026) |
+| `D-53` | `decisions.md`:817 | D-53. Elevate design language and Glare mode adopted for Design System v9 (10 October 2026) |
+| `D-54` | `decisions.md`:824 | D-54. Tier time thresholds remain synthetic demo values requiring clinical sign-off (10 October 2026) |
+| `D-55` | `decisions.md`:832 | D-55. Supabase is Ward Flow's database and sign-in provider, and Azure is retired (15 October 2026) |
+| `D-56` | `decisions.md`:838 | D-56. Josh signs the clinical values that have a recorded source, for the prototype (15 October 2026) |
+| `D-57` | `decisions.md`:844 | D-57. Twenty Top 25 items move from the showcase into the real screens, each behind its own flag (15 October 2026) |
+| `D-58` | `decisions.md`:871 | D-58. Josh signs three more clinical values that have a national source, for the prototype (15 October 2026) |
+| `D-59` | `decisions.md`:877 | D-59. Each role sees only what it needs, from one access table (15 October 2026) |
+| `D-60` | `decisions.md`:883 | D-60. Criteria led discharge, for voluntary patients only (15 October 2026) |
+| `D-61` | `decisions.md`:892 | D-61. Tasks and Alerts are one queue called Alerts (16 October 2026) |
+| `D-62` | `decisions.md`:898 | D-62. The one Alerts queue is signed off and its flag removed (16 October 2026) |
+| `D-63` | `decisions.md`:904 | D-63. Josh signs the remaining clinical values and the criteria list, for the prototype (16 October 2026) |
+| `D-64` | `decisions.md`:912 | D-64. Criteria led discharge shows on the boards and warns at departure (16 October 2026) |
+| `D-65` | `decisions.md`:919 | D-65. Criteria led discharge on in production, typed patient specific criteria, and the AWOL bed hold review paused (17 October 2026) |
+| `D-66` | `decisions.md`:935 | D-66. Granted roles work without a second approval (17 October 2026) |
+| `D-67` | `decisions.md`:943 | D-67. Recover synthetic form drafts within the browser session (17 October 2026) |
+| `D-68` | `decisions.md`:950 | D-68. Bed meeting TV content and access (19 October 2026) |
+| `D-69` | `decisions.md`:958 | D-69. A referral with no legal status: "Needs an involuntary-capable bed until status is set." (20 October 2026) |
+| `D-70` | `decisions.md`:965 | D-70. No computed Mental Health Act periods: show only the recorded due time (20 October 2026) |
+| `D-71` | `decisions.md`:971 | D-71. Audit 1 clinical safety fixes: no suggested wards, wait-order queues, recorded facts only (20 October 2026) |
+| `D-72` | `decisions.md`:984 | D-72. Seven product decisions for the 21 October fix plan (21 October 2026) |
+| `D-73` | `decisions.md`:1000 | D-73. Batch merge safeguards (21 October 2026) |
+| `D-74` | `decisions.md`:1016 | D-74. Owner answers to the 21 October open questions (21 October 2026) |
+| `D-75` | `decisions.md`:1037 | D-75. UX audit follow-up, page caching, community gaps and the extra-travel bed hold (21 October 2026) |
+| `D-76` | `decisions.md`:1050 | D-76. Community referral gaps: the full move journey and asking more wards (21 October 2026) |
+| `D-77` | `decisions.md`:1059 | D-77. The ten bigger UX changes approved for build (22 October 2026) |
+| `D-78` | `decisions.md`:1079 | D-78. One decline list for wards and coordinators (22 October 2026) |
+| `D-79` | `decisions.md`:1090 | D-79. Wards see the full referral, with name and UMRN, before they answer (23 October 2026) |
+| `D-80` | `decisions.md`:1101 | D-80. Role access plan version 2: all 17 recommendations (23 October 2026) |
+| `D-81` | `decisions.md`:1116 | D-81. Pulled, with a pull time limit, for a bed kept for an incoming patient (24 October 2026) |
+| `D-82` | `decisions.md`:1123 | D-82. A PR may raise a phone size limit by up to 5 KB per screen without asking (26 October 2026) |
+| `D-83` | `decisions.md`:1131 | D-83. Patient names stay in full on every screen, copy and TV; privacy work must not make the app less useful (26 October 2026) |
+| `D-84` | `decisions.md`:1142 | D-84. The live app stays on Railway Singapore for now, and there is no server-side TV account yet (26 October 2026) |
+| `D-85` | `decisions.md`:1150 | D-85. "Held" only for a bed kept for its own patient; "pulled" for an incoming bed; "held up" for a delay (26 October 2026) |
+| `D-86` | `decisions.md`:1157 | D-86. No second approver for role grants, at pilot or otherwise (27 October 2026) |
+| `D-87` | `decisions.md`:1164 | D-87. D-36 and D-37 are replaced by the real-data gate (27 October 2026) |
+| `D-88` | `decisions.md`:1176 | D-88. The public demo opens with a demo code once the front door is on (27 October 2026) |
+| `D-89` | `decisions.md`:1183 | D-89. Redesign mock-up copies of large screens may pass the 1,500-line cap (30 October 2026) |
+| `D-90` | `decisions.md`:1191 | D-90. Help gets How do I…, Show me runs everywhere, and Help is built without a separate mock-up step (30 October 2026) |
+| `D-91` | `decisions.md`:1202 | D-91. One design home for the redesign, and Josh's answers on Help, Settings, search and notifications (31 October 2026) |
+| `D-92` | `decisions.md`:1237 | D-92. Help goes live before its mock-up review, one search box on Capacity, and referral rows on Home open the panel (31 October 2026) |
+| `D-93` | `decisions.md`:1247 | D-93. Legal form times show only as typed, never as a countdown (31 October 2026) |
+| `D-94` | `decisions.md`:1255 | D-94. Design rulings of 31 October 2026 |
+| `D-95` | `decisions.md`:1268 | D-95. One page scroll, and a click outside closes a drawer (3 November 2026) |
+| `D-96` | `decisions.md`:1278 | D-96. A named bed is pulled, and its patient is not also waiting (8 November 2026) |
+| `D-97` | `decisions.md`:1288 | D-97. Owner answers on seven open items: N21, N25, N26, N29, N40, N41 and WF-B008 (8 November 2026) |
+| `D-98` | `decisions.md`:1301 | D-98. Friction pass: Quick bar, Patient peek and Ward peek (8 November 2026) |
+| `D-99` | `decisions.md`:1310 | D-99. "Real details" in the referral drawer's sample patients (25 September 2026) |
+| `D-100` | `decisions.md`:1316 | D-100. "Under a form" means one rule everywhere (9 November 2026) |
+| `D-101` | `decisions.md`:1323 | D-101. Clinically ready for discharge, with one delay reason (9 November 2026) |
+| `D-102` | `decisions.md`:1332 | D-102. Report-a-problem screenshots are blurred, and patient-table exports carry no names (9 November 2026) |
+| `D-103` | `decisions.md`:1341 | D-103. Any coordinator on shift may mark a bed override reviewed (9 November 2026) |
+| `D-104` | `decisions.md`:1348 | D-104. Care plans and safety flags: five defaults (9 November 2026) |
+| `D-105` | `decisions.md`:1360 | D-105. Optional clerk details on the patient record (8 November 2026) |
+| `D-106` | `decisions.md`:1367 | D-106. Clinical values from the 9 November 2026 documentation audit (9 November 2026) |
+| `D-107` | `decisions.md`:1379 | D-107. Working rules and documentation from the 9 November 2026 audit (9 November 2026) |
+| `D-108` | `decisions.md`:1393 | D-108. Six bed-flow features: keep D-93 and D-72, ED long-wait next steps (9 November 2026) |
+| `D-109` | `decisions.md`:1402 | D-109. Home's bed alert line tests the whole number shown (9 November 2026) |
+| `D-110` | `decisions.md`:1409 | D-110. ED and referral flow: neutral fit line, clearance timing and alternatives to admission (9 November 2026) |
+| `D-111` | `decisions.md`:1419 | D-111. Review and audit plan: first guards and four open answers (9 November 2026) |
+| `D-112` | `decisions.md`:1433 | D-112. Phone bottom bar: the floating bar (9 November 2026) |
+| `D-113` | `decisions.md`:1442 | D-113. Second patient identifier on accept and Mark done; no initials search (9 November 2026) |
+| `D-114` | `decisions.md`:1452 | D-114. Allow pull_request_target for three workflows only (9 November 2026) |
+| `D-115` | `decisions.md`:1460 | D-115. One count on every screen, and a missing patient shown everywhere (9 November 2026) |
+| `D-116` | `decisions.md`:1474 | D-116. Page tops: the PR #1581 recommendations stand (9 November 2026) |
+| `D-117` | `decisions.md`:1491 | D-117. Care plans follow-up: frequent presenter count, tribunal date and ward flags (10 November 2026) |
+| `D-118` | `decisions.md`:1500 | D-118. Page tops: Referrals and Ward phone limits up to 8 KB above main (10 November 2026) |
+| `D-119` | `decisions.md`:1506 | D-119. Page tops: phone limits raised to measured sizes, trim after merge (10 November 2026) |
+| `D-120` | `decisions.md`:1513 | D-120. Discharge and capacity values: pull limit, transport delay and discharge date slip (9 November 2026) |
+| `D-121` | `decisions.md`:1522 | D-121. Working rules, guards and your PC from the 10 November 2026 audit (10 November 2026) |
+| `D-122` | `decisions.md`:1536 | D-122. Clinical and privacy answers from the 10 November 2026 audit (10 November 2026) |
+| `D-123` | `decisions.md`:1549 | D-123. Master audit list answers: two clinical fixes and five working rules (10 November 2026) |
+| `D-124` | `decisions.md`:1563 | D-124. Offline saves whose record changed are refused and shown (10 November 2026) |
+| `D-125` | `decisions.md`:1570 | D-125. Top 10 audit follow-ups approved (10 November 2026) |
+| `D-126` | `decisions.md`:1582 | D-126. Ranked lists: neutral orders approved (10 November 2026) |
+| `D-127` | `decisions.md`:1601 | D-127. Neutral wording for a community treatment order at arrival (10 November 2026) |
+| `D-128` | `decisions.md`:1608 | D-128. Final master audit: nine answers (10 November 2026) |
+| `D-129` | `decisions.md`:1623 | D-129. Master audit findings: every recommendation approved (10 November 2026) |
+| `D-130` | `decisions.md`:1673 | D-130. Audit follow-ups: every remaining recommendation approved (10 November 2026) |
+| `D-131` | `decisions.md`:1689 | D-131. Hard-case demo data allowed, and the movement diagram follows the engine (10 November 2026) |
+| `D-132` | `decisions.md`:1698 | D-132. Audit fixes group 1: docs and CI guards (10 November 2026) |
+| `D-135` | `decisions.md`:1708 | D-135. Batch 4, outages and pilot control: pack age and plan answers (10 November 2026) |
+| `D-136` | `decisions.md`:1717 | D-136. Feature batch 3 (shift change and follow-up): two signed values (10 November 2026) |
+| `D-137` | `decisions.md`:1729 | D-137. Feature build batch 5: count leak first, TV change mark and QR library (10 November 2026) |
+| `D-138` | `decisions.md`:1745 | D-138. Phone alert urgency by kind (10 November 2026) |
+| `D-139` | `decisions.md`:1751 | D-139. Phone Statistics ward list A to Z, and leave return times as typed (10 November 2026) |
+| `D-142` | `decisions.md`:1761 | D-142. Batch 4: design review and all twelve answers approved, D-80 amended for site features (11 November 2026) |
+| `D-143` | `decisions.md`:1773 | D-143. MHERT is MHERL in the glossary; SMHMR deferred (11 November 2026) |
+| `D-144` | `decisions.md`:1781 | D-144. Chip rows move only sideways on a phone (11 November 2026) |
+| `D-146` | `decisions.md`:1787 | D-146. Performance audit fixes and the phone speed target (11 November 2026) |
+| `D-147` | `decisions.md`:1793 | D-147. Demo-code tries keep per-address keying until a trusted proxy is set (11 November 2026) |
+| `D-152` | `decisions.md`:1799 | D-152. Returned to ED prompt sees every return in shared mode (11 November 2026) |
+| `D-153` | `decisions.md`:1807 | D-153. Keep the drawn-time settle check, and defer a separate agent GitHub identity (11 November 2026) |
| `D9-1` | `../history/archive/dated-notes/ward-flow-phase-9-decisions.md`:62 | D9-1 — The wait becomes prominent, and its ceiling is removed; it never outranks urgency (SESSION) |
| `D9-2` | `../history/archive/dated-notes/ward-flow-phase-9-decisions.md`:102 | D9-2 — Declaring escalation records it and marks the screens, and changes nothing else (SESSION, and partly blocked) |
| `D9-3` | `../history/archive/dated-notes/ward-flow-phase-9-decisions.md`:126 | D9-3 — The retrospective is one person's journey replayed, plus the ward prediction track record last (SESSION) |
@@ -622,151 +623,152 @@ safe global key across 30 files.
**Title:** Ward Flow — decision log
-143 item(s):
-
-- `D-1` — line 172, heading: "D-1. Ward Flow is local only, with no linked repository (Replaced by D-27)"
-- `D-2` — line 182, heading: "D-2. Railway is disconnected"
-- `D-3` — line 193, heading: "D-3. The PsychSift and Database code is being retired from Ward Flow"
-- `D-4` — line 201, heading: "D-4. Synthetic patients only"
-- `D-5` — line 209, heading: "D-5. Parallel threads in separate worktrees, folded one at a time"
-- `D-6` — line 220, heading: "D-6. Aboriginal cultural safety review deferred"
-- `D-7` — line 228, heading: "D-7. The first four milestones, in this order"
-- `D-8` — line 240, heading: "D-8. Josh's standing rulings, numbered R1 to R16, for every session and tool"
-- `D-9` — line 272, heading: "D-9. Josh's rulings of 25 September, 11:43 to 12:00 (R17 to R21)"
-- `D-10` — line 286, heading: "D-10. Gate build type check (R22)"
-- `D-11` — line 295, heading: "D-11. Rulings of 25 September, 12:41 (R23 to R25)"
-- `D-12` — line 311, heading: "D-12. Rulings of 25 September, 12:43 (R26 to R31)"
-- `D-13` — line 330, heading: "D-13. Tooling tests in batch gates (R32)"
-- `D-14` — line 340, heading: "D-14. Standing defaults: decide small things without asking"
-- `D-15` — line 352, heading: "D-15. Rulings of 25 September, 13:26 (R33 to R35)"
-- `D-16` — line 365, heading: "D-16. Legal overrides and form receipts, 25 September 13:51 UTC"
-- `D-17` — line 378, heading: "D-17. Morning roll-up time is Josh's own default (25 September)"
-- `D-18` — line 389, heading: "D-18. The privacy lock stops saving only for typed text (25 September)"
-- `D-19` — line 400, heading: "D-19. Rulings of 25 September, evening (walkthrough, leave beds, PsychSift, gender display)"
-- `D-20` — line 430, heading: "D-20. One high-contrast colour check retired (26 September)"
-- `D-21` — line 443, heading: "D-21. Coordinator bed board: all nine recommended answers (26 September)"
-- `D-22` — line 455, heading: "D-22. No legal limits in hour rules; the rest are labelled defaults (26 September)"
-- `D-23` — line 471, heading: "D-23. Leave-bed fields kept; patient search stops matching by ward (26 September)"
-- `D-24` — line 485, heading: "D-24. Hour-rules morning list: all A (26 September)"
-- `D-25` — line 509, heading: "D-25. The lone-patient check counts free beds on both matching paths (26 September)"
-- `D-26` — line 517, heading: "D-26. Select fold checks by changed risk (27 September)"
-- `D-27` — line 535, heading: "D-27. Ward Flow uses its dedicated repository (28 September)"
-- `D-28` — line 548, heading: "D-28. Design Modernization & Anti-Box Architecture (30 September 2026)"
-- `D-29` — line 562, heading: "D-29. Act time limits shown as a labelled synthetic demo (4 October 2026)"
-- `D-30` — line 577, heading: "D-30. Central Coordinator Approval for Inter-Ward Bed Transfers (6 October 2026)"
-- `D-31` — line 584, heading: "D-31. Gender-Diverse Bed Placement Policy & Clinical Override Safety (6 October 2026)"
-- `D-32` — line 591, heading: "D-32. FIFO Waiting Time with Clinical Triage Badging vs Algorithmic Urgency Sorting (6 October 2026)"
-- `D-33` — line 598, heading: "D-33. Separation of Local Ward Shift Notes from Statewide Coordinator Telemetry (6 October 2026)"
-- `D-34` — line 605, heading: "D-34. Acute Medical Deterioration Bed Release and Escalation Protocol (6 October 2026)"
-- `D-35` — line 612, heading: "D-35. Deferral of Regional Multi-Leg Transport & RFDS Repatriation (6 October 2026)"
-- `D-36` — line 619, heading: "D-36. Affirmation of External Governance & Clinical Safety Hard Gates (6 October 2026) (Replaced by D-87)"
-- `D-37` — line 634, heading: "D-37. External Governance Hard Gate Operational Postures & Resolution Roadmap (6 October 2026) (Replaced by D-87)"
-- `D-38` — line 661, heading: "D-38. Leave, absent without leave and CTO on the Patient page (9 October 2026)"
-- `D-39` — line 679, heading: "D-39. Patients are identified by UMRN, never by a WF number (9 October 2026)"
-- `D-40` — line 723, heading: "D-40. A second community treatment order keeps the first (9 October 2026)"
-- `D-41` — line 734, heading: "D-41. Delays board filters narrow the table and state the hidden count (9 October 2026)"
-- `D-42` — line 747, heading: "D-42. 12px typography floor with no uppercase exception reaffirmed in Design System v9 (9–10 October 2026)"
-- `D-43` — line 753, heading: "D-43. Colour lives in glyphs and the hero; red means act now with a thin 1px edge, never a fill (9 October 2026)"
-- `D-44` — line 760, heading: "D-44. Compact sizing, white cards and smaller text retained over expression-test card tints (9 October 2026)"
-- `D-45` — line 766, heading: "D-45. Phone is its own 390×844 design, never a desktop reflow (9 October 2026)"
-- `D-46` — line 773, heading: "D-46. Every page has a focal part, a part of its own, a page-specific hero and command-bar action rules (9 October 2026)"
-- `D-47` — line 779, heading: "D-47. Glass stays about 80% opaque with the top sheen kept (10 October 2026)"
-- `D-48` — line 785, heading: "D-48. Figures stay in Geist Mono with tabular digits while chart axes use Geist Sans (10 October 2026)"
-- `D-49` — line 791, heading: "D-49. Ward Flow 44px default row target (dense 36px, touch 52px) vs retired 48px PsychSift tap knob (10 October 2026)"
-- `D-50` — line 797, heading: "D-50. Neutral T1 urgency pill vs Form 1A amber statutory warning badge (10 October 2026)"
-- `D-51` — line 803, heading: "D-51. Delays filters hide rows with a stated hidden count while other queues highlight by default (10 October 2026)"
-- `D-52` — line 810, heading: "D-52. 10 October page review rulings: time-axis overdue zone, 15px/20px strip figures, 8px people pips and incremental shared parts (10 October 2026)"
-- `D-53` — line 816, heading: "D-53. Elevate design language and Glare mode adopted for Design System v9 (10 October 2026)"
-- `D-54` — line 823, heading: "D-54. Tier time thresholds remain synthetic demo values requiring clinical sign-off (10 October 2026)"
-- `D-55` — line 831, heading: "D-55. Supabase is Ward Flow's database and sign-in provider, and Azure is retired (15 October 2026)"
-- `D-56` — line 837, heading: "D-56. Josh signs the clinical values that have a recorded source, for the prototype (15 October 2026)"
-- `D-57` — line 843, heading: "D-57. Twenty Top 25 items move from the showcase into the real screens, each behind its own flag (15 October 2026)"
-- `D-58` — line 870, heading: "D-58. Josh signs three more clinical values that have a national source, for the prototype (15 October 2026)"
-- `D-59` — line 876, heading: "D-59. Each role sees only what it needs, from one access table (15 October 2026)"
-- `D-60` — line 882, heading: "D-60. Criteria led discharge, for voluntary patients only (15 October 2026)"
-- `D-61` — line 891, heading: "D-61. Tasks and Alerts are one queue called Alerts (16 October 2026)"
-- `D-62` — line 897, heading: "D-62. The one Alerts queue is signed off and its flag removed (16 October 2026)"
-- `D-63` — line 903, heading: "D-63. Josh signs the remaining clinical values and the criteria list, for the prototype (16 October 2026)"
-- `D-64` — line 911, heading: "D-64. Criteria led discharge shows on the boards and warns at departure (16 October 2026)"
-- `D-65` — line 918, heading: "D-65. Criteria led discharge on in production, typed patient specific criteria, and the AWOL bed hold review paused (17 October 2026)"
-- `D-66` — line 934, heading: "D-66. Granted roles work without a second approval (17 October 2026)"
-- `D-67` — line 942, heading: "D-67. Recover synthetic form drafts within the browser session (17 October 2026)"
-- `D-68` — line 949, heading: "D-68. Bed meeting TV content and access (19 October 2026)"
-- `D-69` — line 957, heading: "D-69. A referral with no legal status: "Needs an involuntary-capable bed until status is set." (20 October 2026)"
-- `D-70` — line 964, heading: "D-70. No computed Mental Health Act periods: show only the recorded due time (20 October 2026)"
-- `D-71` — line 970, heading: "D-71. Audit 1 clinical safety fixes: no suggested wards, wait-order queues, recorded facts only (20 October 2026)"
-- `D-72` — line 983, heading: "D-72. Seven product decisions for the 21 October fix plan (21 October 2026)"
-- `D-73` — line 999, heading: "D-73. Batch merge safeguards (21 October 2026)"
-- `D-74` — line 1015, heading: "D-74. Owner answers to the 21 October open questions (21 October 2026)"
-- `D-75` — line 1036, heading: "D-75. UX audit follow-up, page caching, community gaps and the extra-travel bed hold (21 October 2026)"
-- `D-76` — line 1049, heading: "D-76. Community referral gaps: the full move journey and asking more wards (21 October 2026)"
-- `D-77` — line 1058, heading: "D-77. The ten bigger UX changes approved for build (22 October 2026)"
-- `D-78` — line 1078, heading: "D-78. One decline list for wards and coordinators (22 October 2026)"
-- `D-79` — line 1089, heading: "D-79. Wards see the full referral, with name and UMRN, before they answer (23 October 2026)"
-- `D-80` — line 1100, heading: "D-80. Role access plan version 2: all 17 recommendations (23 October 2026)"
-- `D-81` — line 1115, heading: "D-81. Pulled, with a pull time limit, for a bed kept for an incoming patient (24 October 2026)"
-- `D-82` — line 1122, heading: "D-82. A PR may raise a phone size limit by up to 5 KB per screen without asking (26 October 2026)"
-- `D-83` — line 1130, heading: "D-83. Patient names stay in full on every screen, copy and TV; privacy work must not make the app less useful (26 October 2026)"
-- `D-84` — line 1141, heading: "D-84. The live app stays on Railway Singapore for now, and there is no server-side TV account yet (26 October 2026)"
-- `D-85` — line 1149, heading: "D-85. "Held" only for a bed kept for its own patient; "pulled" for an incoming bed; "held up" for a delay (26 October 2026)"
-- `D-86` — line 1156, heading: "D-86. No second approver for role grants, at pilot or otherwise (27 October 2026)"
-- `D-87` — line 1163, heading: "D-87. D-36 and D-37 are replaced by the real-data gate (27 October 2026)"
-- `D-88` — line 1175, heading: "D-88. The public demo opens with a demo code once the front door is on (27 October 2026)"
-- `D-89` — line 1182, heading: "D-89. Redesign mock-up copies of large screens may pass the 1,500-line cap (30 October 2026)"
-- `D-90` — line 1190, heading: "D-90. Help gets How do I…, Show me runs everywhere, and Help is built without a separate mock-up step (30 October 2026)"
-- `D-91` — line 1201, heading: "D-91. One design home for the redesign, and Josh's answers on Help, Settings, search and notifications (31 October 2026)"
-- `D-92` — line 1236, heading: "D-92. Help goes live before its mock-up review, one search box on Capacity, and referral rows on Home open the panel (31 October 2026)"
-- `D-93` — line 1246, heading: "D-93. Legal form times show only as typed, never as a countdown (31 October 2026)"
-- `D-94` — line 1254, heading: "D-94. Design rulings of 31 October 2026"
-- `D-95` — line 1267, heading: "D-95. One page scroll, and a click outside closes a drawer (3 November 2026)"
-- `D-96` — line 1277, heading: "D-96. A named bed is pulled, and its patient is not also waiting (8 November 2026)"
-- `D-97` — line 1287, heading: "D-97. Owner answers on seven open items: N21, N25, N26, N29, N40, N41 and WF-B008 (8 November 2026)"
-- `D-98` — line 1300, heading: "D-98. Friction pass: Quick bar, Patient peek and Ward peek (8 November 2026)"
-- `D-99` — line 1309, heading: "D-99. "Real details" in the referral drawer's sample patients (25 September 2026)"
-- `D-100` — line 1315, heading: "D-100. "Under a form" means one rule everywhere (9 November 2026)"
-- `D-101` — line 1322, heading: "D-101. Clinically ready for discharge, with one delay reason (9 November 2026)"
-- `D-102` — line 1331, heading: "D-102. Report-a-problem screenshots are blurred, and patient-table exports carry no names (9 November 2026)"
-- `D-103` — line 1340, heading: "D-103. Any coordinator on shift may mark a bed override reviewed (9 November 2026)"
-- `D-104` — line 1347, heading: "D-104. Care plans and safety flags: five defaults (9 November 2026)"
-- `D-105` — line 1359, heading: "D-105. Optional clerk details on the patient record (8 November 2026)"
-- `D-106` — line 1366, heading: "D-106. Clinical values from the 9 November 2026 documentation audit (9 November 2026)"
-- `D-107` — line 1378, heading: "D-107. Working rules and documentation from the 9 November 2026 audit (9 November 2026)"
-- `D-108` — line 1392, heading: "D-108. Six bed-flow features: keep D-93 and D-72, ED long-wait next steps (9 November 2026)"
-- `D-109` — line 1401, heading: "D-109. Home's bed alert line tests the whole number shown (9 November 2026)"
-- `D-110` — line 1408, heading: "D-110. ED and referral flow: neutral fit line, clearance timing and alternatives to admission (9 November 2026)"
-- `D-111` — line 1418, heading: "D-111. Review and audit plan: first guards and four open answers (9 November 2026)"
-- `D-112` — line 1432, heading: "D-112. Phone bottom bar: the floating bar (9 November 2026)"
-- `D-113` — line 1441, heading: "D-113. Second patient identifier on accept and Mark done; no initials search (9 November 2026)"
-- `D-114` — line 1451, heading: "D-114. Allow pull_request_target for three workflows only (9 November 2026)"
-- `D-115` — line 1459, heading: "D-115. One count on every screen, and a missing patient shown everywhere (9 November 2026)"
-- `D-116` — line 1473, heading: "D-116. Page tops: the PR #1581 recommendations stand (9 November 2026)"
-- `D-117` — line 1490, heading: "D-117. Care plans follow-up: frequent presenter count, tribunal date and ward flags (10 November 2026)"
-- `D-118` — line 1499, heading: "D-118. Page tops: Referrals and Ward phone limits up to 8 KB above main (10 November 2026)"
-- `D-119` — line 1505, heading: "D-119. Page tops: phone limits raised to measured sizes, trim after merge (10 November 2026)"
-- `D-120` — line 1512, heading: "D-120. Discharge and capacity values: pull limit, transport delay and discharge date slip (9 November 2026)"
-- `D-121` — line 1521, heading: "D-121. Working rules, guards and your PC from the 10 November 2026 audit (10 November 2026)"
-- `D-122` — line 1535, heading: "D-122. Clinical and privacy answers from the 10 November 2026 audit (10 November 2026)"
-- `D-123` — line 1548, heading: "D-123. Master audit list answers: two clinical fixes and five working rules (10 November 2026)"
-- `D-124` — line 1562, heading: "D-124. Offline saves whose record changed are refused and shown (10 November 2026)"
-- `D-125` — line 1569, heading: "D-125. Top 10 audit follow-ups approved (10 November 2026)"
-- `D-126` — line 1581, heading: "D-126. Ranked lists: neutral orders approved (10 November 2026)"
-- `D-127` — line 1600, heading: "D-127. Neutral wording for a community treatment order at arrival (10 November 2026)"
-- `D-128` — line 1607, heading: "D-128. Final master audit: nine answers (10 November 2026)"
-- `D-129` — line 1622, heading: "D-129. Master audit findings: every recommendation approved (10 November 2026)"
-- `D-130` — line 1672, heading: "D-130. Audit follow-ups: every remaining recommendation approved (10 November 2026)"
-- `D-131` — line 1688, heading: "D-131. Hard-case demo data allowed, and the movement diagram follows the engine (10 November 2026)"
-- `D-132` — line 1697, heading: "D-132. Audit fixes group 1: docs and CI guards (10 November 2026)"
-- `D-135` — line 1707, heading: "D-135. Batch 4, outages and pilot control: pack age and plan answers (10 November 2026)"
-- `D-136` — line 1716, heading: "D-136. Feature batch 3 (shift change and follow-up): two signed values (10 November 2026)"
-- `D-137` — line 1728, heading: "D-137. Feature build batch 5: count leak first, TV change mark and QR library (10 November 2026)"
-- `D-138` — line 1744, heading: "D-138. Phone alert urgency by kind (10 November 2026)"
-- `D-139` — line 1750, heading: "D-139. Phone Statistics ward list A to Z, and leave return times as typed (10 November 2026)"
-- `D-142` — line 1760, heading: "D-142. Batch 4: design review and all twelve answers approved, D-80 amended for site features (11 November 2026)"
-- `D-143` — line 1772, heading: "D-143. MHERT is MHERL in the glossary; SMHMR deferred (11 November 2026)"
-- `D-144` — line 1780, heading: "D-144. Chip rows move only sideways on a phone (11 November 2026)"
-- `D-146` — line 1786, heading: "D-146. Performance audit fixes and the phone speed target (11 November 2026)"
-- `D-147` — line 1792, heading: "D-147. Demo-code tries keep per-address keying until a trusted proxy is set (11 November 2026)"
-- `D-152` — line 1798, heading: "D-152. Returned to ED prompt sees every return in shared mode (11 November 2026)"
+144 item(s):
+
+- `D-1` — line 173, heading: "D-1. Ward Flow is local only, with no linked repository (Replaced by D-27)"
+- `D-2` — line 183, heading: "D-2. Railway is disconnected"
+- `D-3` — line 194, heading: "D-3. The PsychSift and Database code is being retired from Ward Flow"
+- `D-4` — line 202, heading: "D-4. Synthetic patients only"
+- `D-5` — line 210, heading: "D-5. Parallel threads in separate worktrees, folded one at a time"
+- `D-6` — line 221, heading: "D-6. Aboriginal cultural safety review deferred"
+- `D-7` — line 229, heading: "D-7. The first four milestones, in this order"
+- `D-8` — line 241, heading: "D-8. Josh's standing rulings, numbered R1 to R16, for every session and tool"
+- `D-9` — line 273, heading: "D-9. Josh's rulings of 25 September, 11:43 to 12:00 (R17 to R21)"
+- `D-10` — line 287, heading: "D-10. Gate build type check (R22)"
+- `D-11` — line 296, heading: "D-11. Rulings of 25 September, 12:41 (R23 to R25)"
+- `D-12` — line 312, heading: "D-12. Rulings of 25 September, 12:43 (R26 to R31)"
+- `D-13` — line 331, heading: "D-13. Tooling tests in batch gates (R32)"
+- `D-14` — line 341, heading: "D-14. Standing defaults: decide small things without asking"
+- `D-15` — line 353, heading: "D-15. Rulings of 25 September, 13:26 (R33 to R35)"
+- `D-16` — line 366, heading: "D-16. Legal overrides and form receipts, 25 September 13:51 UTC"
+- `D-17` — line 379, heading: "D-17. Morning roll-up time is Josh's own default (25 September)"
+- `D-18` — line 390, heading: "D-18. The privacy lock stops saving only for typed text (25 September)"
+- `D-19` — line 401, heading: "D-19. Rulings of 25 September, evening (walkthrough, leave beds, PsychSift, gender display)"
+- `D-20` — line 431, heading: "D-20. One high-contrast colour check retired (26 September)"
+- `D-21` — line 444, heading: "D-21. Coordinator bed board: all nine recommended answers (26 September)"
+- `D-22` — line 456, heading: "D-22. No legal limits in hour rules; the rest are labelled defaults (26 September)"
+- `D-23` — line 472, heading: "D-23. Leave-bed fields kept; patient search stops matching by ward (26 September)"
+- `D-24` — line 486, heading: "D-24. Hour-rules morning list: all A (26 September)"
+- `D-25` — line 510, heading: "D-25. The lone-patient check counts free beds on both matching paths (26 September)"
+- `D-26` — line 518, heading: "D-26. Select fold checks by changed risk (27 September)"
+- `D-27` — line 536, heading: "D-27. Ward Flow uses its dedicated repository (28 September)"
+- `D-28` — line 549, heading: "D-28. Design Modernization & Anti-Box Architecture (30 September 2026)"
+- `D-29` — line 563, heading: "D-29. Act time limits shown as a labelled synthetic demo (4 October 2026)"
+- `D-30` — line 578, heading: "D-30. Central Coordinator Approval for Inter-Ward Bed Transfers (6 October 2026)"
+- `D-31` — line 585, heading: "D-31. Gender-Diverse Bed Placement Policy & Clinical Override Safety (6 October 2026)"
+- `D-32` — line 592, heading: "D-32. FIFO Waiting Time with Clinical Triage Badging vs Algorithmic Urgency Sorting (6 October 2026)"
+- `D-33` — line 599, heading: "D-33. Separation of Local Ward Shift Notes from Statewide Coordinator Telemetry (6 October 2026)"
+- `D-34` — line 606, heading: "D-34. Acute Medical Deterioration Bed Release and Escalation Protocol (6 October 2026)"
+- `D-35` — line 613, heading: "D-35. Deferral of Regional Multi-Leg Transport & RFDS Repatriation (6 October 2026)"
+- `D-36` — line 620, heading: "D-36. Affirmation of External Governance & Clinical Safety Hard Gates (6 October 2026) (Replaced by D-87)"
+- `D-37` — line 635, heading: "D-37. External Governance Hard Gate Operational Postures & Resolution Roadmap (6 October 2026) (Replaced by D-87)"
+- `D-38` — line 662, heading: "D-38. Leave, absent without leave and CTO on the Patient page (9 October 2026)"
+- `D-39` — line 680, heading: "D-39. Patients are identified by UMRN, never by a WF number (9 October 2026)"
+- `D-40` — line 724, heading: "D-40. A second community treatment order keeps the first (9 October 2026)"
+- `D-41` — line 735, heading: "D-41. Delays board filters narrow the table and state the hidden count (9 October 2026)"
+- `D-42` — line 748, heading: "D-42. 12px typography floor with no uppercase exception reaffirmed in Design System v9 (9–10 October 2026)"
+- `D-43` — line 754, heading: "D-43. Colour lives in glyphs and the hero; red means act now with a thin 1px edge, never a fill (9 October 2026)"
+- `D-44` — line 761, heading: "D-44. Compact sizing, white cards and smaller text retained over expression-test card tints (9 October 2026)"
+- `D-45` — line 767, heading: "D-45. Phone is its own 390×844 design, never a desktop reflow (9 October 2026)"
+- `D-46` — line 774, heading: "D-46. Every page has a focal part, a part of its own, a page-specific hero and command-bar action rules (9 October 2026)"
+- `D-47` — line 780, heading: "D-47. Glass stays about 80% opaque with the top sheen kept (10 October 2026)"
+- `D-48` — line 786, heading: "D-48. Figures stay in Geist Mono with tabular digits while chart axes use Geist Sans (10 October 2026)"
+- `D-49` — line 792, heading: "D-49. Ward Flow 44px default row target (dense 36px, touch 52px) vs retired 48px PsychSift tap knob (10 October 2026)"
+- `D-50` — line 798, heading: "D-50. Neutral T1 urgency pill vs Form 1A amber statutory warning badge (10 October 2026)"
+- `D-51` — line 804, heading: "D-51. Delays filters hide rows with a stated hidden count while other queues highlight by default (10 October 2026)"
+- `D-52` — line 811, heading: "D-52. 10 October page review rulings: time-axis overdue zone, 15px/20px strip figures, 8px people pips and incremental shared parts (10 October 2026)"
+- `D-53` — line 817, heading: "D-53. Elevate design language and Glare mode adopted for Design System v9 (10 October 2026)"
+- `D-54` — line 824, heading: "D-54. Tier time thresholds remain synthetic demo values requiring clinical sign-off (10 October 2026)"
+- `D-55` — line 832, heading: "D-55. Supabase is Ward Flow's database and sign-in provider, and Azure is retired (15 October 2026)"
+- `D-56` — line 838, heading: "D-56. Josh signs the clinical values that have a recorded source, for the prototype (15 October 2026)"
+- `D-57` — line 844, heading: "D-57. Twenty Top 25 items move from the showcase into the real screens, each behind its own flag (15 October 2026)"
+- `D-58` — line 871, heading: "D-58. Josh signs three more clinical values that have a national source, for the prototype (15 October 2026)"
+- `D-59` — line 877, heading: "D-59. Each role sees only what it needs, from one access table (15 October 2026)"
+- `D-60` — line 883, heading: "D-60. Criteria led discharge, for voluntary patients only (15 October 2026)"
+- `D-61` — line 892, heading: "D-61. Tasks and Alerts are one queue called Alerts (16 October 2026)"
+- `D-62` — line 898, heading: "D-62. The one Alerts queue is signed off and its flag removed (16 October 2026)"
+- `D-63` — line 904, heading: "D-63. Josh signs the remaining clinical values and the criteria list, for the prototype (16 October 2026)"
+- `D-64` — line 912, heading: "D-64. Criteria led discharge shows on the boards and warns at departure (16 October 2026)"
+- `D-65` — line 919, heading: "D-65. Criteria led discharge on in production, typed patient specific criteria, and the AWOL bed hold review paused (17 October 2026)"
+- `D-66` — line 935, heading: "D-66. Granted roles work without a second approval (17 October 2026)"
+- `D-67` — line 943, heading: "D-67. Recover synthetic form drafts within the browser session (17 October 2026)"
+- `D-68` — line 950, heading: "D-68. Bed meeting TV content and access (19 October 2026)"
+- `D-69` — line 958, heading: "D-69. A referral with no legal status: "Needs an involuntary-capable bed until status is set." (20 October 2026)"
+- `D-70` — line 965, heading: "D-70. No computed Mental Health Act periods: show only the recorded due time (20 October 2026)"
+- `D-71` — line 971, heading: "D-71. Audit 1 clinical safety fixes: no suggested wards, wait-order queues, recorded facts only (20 October 2026)"
+- `D-72` — line 984, heading: "D-72. Seven product decisions for the 21 October fix plan (21 October 2026)"
+- `D-73` — line 1000, heading: "D-73. Batch merge safeguards (21 October 2026)"
+- `D-74` — line 1016, heading: "D-74. Owner answers to the 21 October open questions (21 October 2026)"
+- `D-75` — line 1037, heading: "D-75. UX audit follow-up, page caching, community gaps and the extra-travel bed hold (21 October 2026)"
+- `D-76` — line 1050, heading: "D-76. Community referral gaps: the full move journey and asking more wards (21 October 2026)"
+- `D-77` — line 1059, heading: "D-77. The ten bigger UX changes approved for build (22 October 2026)"
+- `D-78` — line 1079, heading: "D-78. One decline list for wards and coordinators (22 October 2026)"
+- `D-79` — line 1090, heading: "D-79. Wards see the full referral, with name and UMRN, before they answer (23 October 2026)"
+- `D-80` — line 1101, heading: "D-80. Role access plan version 2: all 17 recommendations (23 October 2026)"
+- `D-81` — line 1116, heading: "D-81. Pulled, with a pull time limit, for a bed kept for an incoming patient (24 October 2026)"
+- `D-82` — line 1123, heading: "D-82. A PR may raise a phone size limit by up to 5 KB per screen without asking (26 October 2026)"
+- `D-83` — line 1131, heading: "D-83. Patient names stay in full on every screen, copy and TV; privacy work must not make the app less useful (26 October 2026)"
+- `D-84` — line 1142, heading: "D-84. The live app stays on Railway Singapore for now, and there is no server-side TV account yet (26 October 2026)"
+- `D-85` — line 1150, heading: "D-85. "Held" only for a bed kept for its own patient; "pulled" for an incoming bed; "held up" for a delay (26 October 2026)"
+- `D-86` — line 1157, heading: "D-86. No second approver for role grants, at pilot or otherwise (27 October 2026)"
+- `D-87` — line 1164, heading: "D-87. D-36 and D-37 are replaced by the real-data gate (27 October 2026)"
+- `D-88` — line 1176, heading: "D-88. The public demo opens with a demo code once the front door is on (27 October 2026)"
+- `D-89` — line 1183, heading: "D-89. Redesign mock-up copies of large screens may pass the 1,500-line cap (30 October 2026)"
+- `D-90` — line 1191, heading: "D-90. Help gets How do I…, Show me runs everywhere, and Help is built without a separate mock-up step (30 October 2026)"
+- `D-91` — line 1202, heading: "D-91. One design home for the redesign, and Josh's answers on Help, Settings, search and notifications (31 October 2026)"
+- `D-92` — line 1237, heading: "D-92. Help goes live before its mock-up review, one search box on Capacity, and referral rows on Home open the panel (31 October 2026)"
+- `D-93` — line 1247, heading: "D-93. Legal form times show only as typed, never as a countdown (31 October 2026)"
+- `D-94` — line 1255, heading: "D-94. Design rulings of 31 October 2026"
+- `D-95` — line 1268, heading: "D-95. One page scroll, and a click outside closes a drawer (3 November 2026)"
+- `D-96` — line 1278, heading: "D-96. A named bed is pulled, and its patient is not also waiting (8 November 2026)"
+- `D-97` — line 1288, heading: "D-97. Owner answers on seven open items: N21, N25, N26, N29, N40, N41 and WF-B008 (8 November 2026)"
+- `D-98` — line 1301, heading: "D-98. Friction pass: Quick bar, Patient peek and Ward peek (8 November 2026)"
+- `D-99` — line 1310, heading: "D-99. "Real details" in the referral drawer's sample patients (25 September 2026)"
+- `D-100` — line 1316, heading: "D-100. "Under a form" means one rule everywhere (9 November 2026)"
+- `D-101` — line 1323, heading: "D-101. Clinically ready for discharge, with one delay reason (9 November 2026)"
+- `D-102` — line 1332, heading: "D-102. Report-a-problem screenshots are blurred, and patient-table exports carry no names (9 November 2026)"
+- `D-103` — line 1341, heading: "D-103. Any coordinator on shift may mark a bed override reviewed (9 November 2026)"
+- `D-104` — line 1348, heading: "D-104. Care plans and safety flags: five defaults (9 November 2026)"
+- `D-105` — line 1360, heading: "D-105. Optional clerk details on the patient record (8 November 2026)"
+- `D-106` — line 1367, heading: "D-106. Clinical values from the 9 November 2026 documentation audit (9 November 2026)"
+- `D-107` — line 1379, heading: "D-107. Working rules and documentation from the 9 November 2026 audit (9 November 2026)"
+- `D-108` — line 1393, heading: "D-108. Six bed-flow features: keep D-93 and D-72, ED long-wait next steps (9 November 2026)"
+- `D-109` — line 1402, heading: "D-109. Home's bed alert line tests the whole number shown (9 November 2026)"
+- `D-110` — line 1409, heading: "D-110. ED and referral flow: neutral fit line, clearance timing and alternatives to admission (9 November 2026)"
+- `D-111` — line 1419, heading: "D-111. Review and audit plan: first guards and four open answers (9 November 2026)"
+- `D-112` — line 1433, heading: "D-112. Phone bottom bar: the floating bar (9 November 2026)"
+- `D-113` — line 1442, heading: "D-113. Second patient identifier on accept and Mark done; no initials search (9 November 2026)"
+- `D-114` — line 1452, heading: "D-114. Allow pull_request_target for three workflows only (9 November 2026)"
+- `D-115` — line 1460, heading: "D-115. One count on every screen, and a missing patient shown everywhere (9 November 2026)"
+- `D-116` — line 1474, heading: "D-116. Page tops: the PR #1581 recommendations stand (9 November 2026)"
+- `D-117` — line 1491, heading: "D-117. Care plans follow-up: frequent presenter count, tribunal date and ward flags (10 November 2026)"
+- `D-118` — line 1500, heading: "D-118. Page tops: Referrals and Ward phone limits up to 8 KB above main (10 November 2026)"
+- `D-119` — line 1506, heading: "D-119. Page tops: phone limits raised to measured sizes, trim after merge (10 November 2026)"
+- `D-120` — line 1513, heading: "D-120. Discharge and capacity values: pull limit, transport delay and discharge date slip (9 November 2026)"
+- `D-121` — line 1522, heading: "D-121. Working rules, guards and your PC from the 10 November 2026 audit (10 November 2026)"
+- `D-122` — line 1536, heading: "D-122. Clinical and privacy answers from the 10 November 2026 audit (10 November 2026)"
+- `D-123` — line 1549, heading: "D-123. Master audit list answers: two clinical fixes and five working rules (10 November 2026)"
+- `D-124` — line 1563, heading: "D-124. Offline saves whose record changed are refused and shown (10 November 2026)"
+- `D-125` — line 1570, heading: "D-125. Top 10 audit follow-ups approved (10 November 2026)"
+- `D-126` — line 1582, heading: "D-126. Ranked lists: neutral orders approved (10 November 2026)"
+- `D-127` — line 1601, heading: "D-127. Neutral wording for a community treatment order at arrival (10 November 2026)"
+- `D-128` — line 1608, heading: "D-128. Final master audit: nine answers (10 November 2026)"
+- `D-129` — line 1623, heading: "D-129. Master audit findings: every recommendation approved (10 November 2026)"
+- `D-130` — line 1673, heading: "D-130. Audit follow-ups: every remaining recommendation approved (10 November 2026)"
+- `D-131` — line 1689, heading: "D-131. Hard-case demo data allowed, and the movement diagram follows the engine (10 November 2026)"
+- `D-132` — line 1698, heading: "D-132. Audit fixes group 1: docs and CI guards (10 November 2026)"
+- `D-135` — line 1708, heading: "D-135. Batch 4, outages and pilot control: pack age and plan answers (10 November 2026)"
+- `D-136` — line 1717, heading: "D-136. Feature batch 3 (shift change and follow-up): two signed values (10 November 2026)"
+- `D-137` — line 1729, heading: "D-137. Feature build batch 5: count leak first, TV change mark and QR library (10 November 2026)"
+- `D-138` — line 1745, heading: "D-138. Phone alert urgency by kind (10 November 2026)"
+- `D-139` — line 1751, heading: "D-139. Phone Statistics ward list A to Z, and leave return times as typed (10 November 2026)"
+- `D-142` — line 1761, heading: "D-142. Batch 4: design review and all twelve answers approved, D-80 amended for site features (11 November 2026)"
+- `D-143` — line 1773, heading: "D-143. MHERT is MHERL in the glossary; SMHMR deferred (11 November 2026)"
+- `D-144` — line 1781, heading: "D-144. Chip rows move only sideways on a phone (11 November 2026)"
+- `D-146` — line 1787, heading: "D-146. Performance audit fixes and the phone speed target (11 November 2026)"
+- `D-147` — line 1793, heading: "D-147. Demo-code tries keep per-address keying until a trusted proxy is set (11 November 2026)"
+- `D-152` — line 1799, heading: "D-152. Returned to ED prompt sees every return in shared mode (11 November 2026)"
+- `D-153` — line 1807, heading: "D-153. Keep the drawn-time settle check, and defer a separate agent GitHub identity (11 November 2026)"
### `../history/archive/dated-notes/ward-management-decisions.md` (2026-08-18 – 2026-10-24 (mixed, see file))
diff --git a/docs/ward-flow/decisions.md b/docs/ward-flow/decisions.md
index 60a106e44..03b2b8ae8 100644
--- a/docs/ward-flow/decisions.md
+++ b/docs/ward-flow/decisions.md
@@ -166,6 +166,7 @@ Checked 9 November 2026 (D-107 audit). "Partly replaced" means part of the entry
| [D-146](#d-146-performance-audit-fixes-and-the-phone-speed-target-11-november-2026) | Performance audit fixes and the phone speed target | Active |
| [D-147](#d-147-demo-code-tries-keep-per-address-keying-until-a-trusted-proxy-is-set-11-november-2026) | Demo-code tries keep per-address keying until a trusted proxy is set | Active |
| [D-152](#d-152-returned-to-ed-prompt-sees-every-return-in-shared-mode-11-november-2026) | Returned to ED prompt sees every return in shared mode | Active |
+| [D-153](#d-153-keep-the-drawn-time-settle-check-and-defer-a-separate-agent-github-identity-11-november-2026) | Keep the drawn-time settle check, and defer a separate agent GitHub identity | Active |
---
@@ -1802,3 +1803,11 @@ is inferred.
- **Role access, kept narrow:** only a community desk scoped to one team receives `diversionReturns`, and only for referrals it took and is already sent. Ward, ED, officer, admin, executive and service coordinator copies never carry them. A statewide coordinator already holds the whole workspace. The record names no ED, movement or person, carries no typed text, and the reducer never writes it. A shared copy whose return names a referral the desk was not sent fails the shared-state check.
- **D-14:** no new patient-link reader. The match still runs in `community/diversion-return-review.ts`, which D-136 item 5 allows.
- **Boundary:** synthetic data only. A review prompt only, as D-136. Changes no signed clinical value or rule, and approves no merge or release.
+
+## D-153. Keep the drawn-time settle check, and defer a separate agent GitHub identity (11 November 2026)
+
+- **Date:** 11 November 2026, 11:41 PM AWST (15:41 UTC). **Decided by:** Josh, "Yes to all recommendations" in the Ward Flow project chat, answering the coordinator's list of five open questions. Two of those answers are rulings and are recorded here. The community desk ED heads-up answer is still unclear and will be recorded separately. The uptime monitor secrets and the GitHub and Railway settings are tasks for Josh, not rulings.
+- **Decision:**
+ 1. **Phone release gate settle check:** the shared helper `waitForAccessibleReadiness` (`tests/helpers/ward-release-gate.ts`, changed in PR #1607) stays as it is. It counts a 100 ms tick against its 5-second settle budget only when the browser drew a frame in that tick, or while fonts load. A cold WebKit page held by D-137 can pause drawing after its first frame. Motion that keeps the browser drawing still fails after 5 seconds, and the test timeout still bounds a renderer that never recovers.
+ 2. **A9-01, a separate GitHub identity for the agents:** deferred until after the pilot. Agents keep publishing as they do now.
+- **Boundary:** synthetic data only. Changes no clinical value or rule, weakens no release gate check, and approves no merge, release or settings change.
From 1591e73373106b73789d982cc7d82457c40f4df1 Mon Sep 17 00:00:00 2001
From: Claude
Date: Wed, 11 Nov 2026 17:28:26 +0000
Subject: [PATCH 2/7] Install browser system packages from the cache when the
Ubuntu mirror is down
Since about 14:30Z on 11 Nov every browser job fails because the runner
cannot resolve azure.archive.ubuntu.com, and Playwright's install-deps
always runs apt-get update first. The seed step already copies the
main-saved packages for this exact runner image into apt's archive folder,
so the setup now installs those as local files with downloads switched
off, installs the browsers, and passes only if each one launches. If any
part fails it falls back to the online path as before. The shared-mode
job also restores WebKit's cached packages and uses the same script for
both engines.
Co-Authored-By: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01GxPPxmrqgwa3hCAtaMf9JT
(cherry picked from commit c9bc9f0958cdba133810003c3ede13882d0f5840)
---
.github/workflows/ward-flow.yml | 17 ++++--
scripts/ward-ci-public/install-chromium.mjs | 59 ++++++++++++++++++++-
tests/ward-browser-install.test.ts | 48 +++++++++++++++++
3 files changed, 118 insertions(+), 6 deletions(-)
diff --git a/.github/workflows/ward-flow.yml b/.github/workflows/ward-flow.yml
index e788f4262..ec29641f4 100644
--- a/.github/workflows/ward-flow.yml
+++ b/.github/workflows/ward-flow.yml
@@ -374,18 +374,27 @@ jobs:
restore-keys: |
ward-release-apt-${{ runner.os }}-chromium-${{ steps.shared-version.outputs.version }}-
ward-release-apt-${{ runner.os }}-${{ steps.shared-version.outputs.version }}-
+ # WebKit's own packages, so the offline install covers both engines when the mirror is down.
+ - name: Restore release WebKit system packages
+ if: ${{ !cancelled() && steps.install.outcome == 'success' && steps.backend-install.outcome == 'success' }}
+ uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
+ with:
+ path: ~/.cache/ward-release-apt-webkit
+ key: ward-release-apt-${{ runner.os }}-webkit-${{ steps.shared-version.outputs.version }}-${{ steps.shared-version.outputs.image }}
+ restore-keys: |
+ ward-release-apt-${{ runner.os }}-webkit-${{ steps.shared-version.outputs.version }}-
- name: Seed release system packages
if: ${{ !cancelled() && steps.install.outcome == 'success' && steps.backend-install.outcome == 'success' }}
- run: node scripts/ward-ci-public/apt-archive-cache.mjs seed "$HOME/.cache/ward-release-apt"
+ run: |
+ node scripts/ward-ci-public/apt-archive-cache.mjs seed "$HOME/.cache/ward-release-apt"
+ node scripts/ward-ci-public/apt-archive-cache.mjs seed "$HOME/.cache/ward-release-apt-webkit"
- name: Install Chromium and WebKit for the shared-mode journeys
id: shared-chromium
timeout-minutes: 10
if: ${{ !cancelled() && steps.install.outcome == 'success' && steps.backend-install.outcome == 'success' }}
env:
PLAYWRIGHT_CACHE_HIT: ${{ steps.shared-cache.outputs.cache-hit }}
- run: |
- node scripts/ward-ci-public/install-chromium.mjs
- node node_modules/playwright/cli.js install --with-deps webkit
+ run: node scripts/ward-ci-public/install-chromium.mjs chromium webkit
- name: Run the shared-mode browser journey
timeout-minutes: 20
if: ${{ !cancelled() && steps.shared-chromium.outcome == 'success' }}
diff --git a/scripts/ward-ci-public/install-chromium.mjs b/scripts/ward-ci-public/install-chromium.mjs
index cdaa7da06..e88a3d9af 100644
--- a/scripts/ward-ci-public/install-chromium.mjs
+++ b/scripts/ward-ci-public/install-chromium.mjs
@@ -1,6 +1,8 @@
import { spawnSync } from "node:child_process";
+import { existsSync, readdirSync } from "node:fs";
import { fileURLToPath, pathToFileURL } from "node:url";
-import { resolve } from "node:path";
+import { join, resolve } from "node:path";
+import { APT_ARCHIVES } from "./apt-archive-cache.mjs";
const root = fileURLToPath(new URL("../../", import.meta.url));
const cli = resolve(root, "node_modules/playwright/cli.js");
@@ -21,7 +23,10 @@ export function waitForPackageManager(deadline, now = Date.now) {
/**
* Six-minute shared budget, two attempts per operation; the workflow adds an eight-minute outer bound.
- * @param {{ browsers?: string[], cacheHit?: boolean, execute?: (command: string, args: string[], timeoutMs: number) => { status: number | null, error?: { code?: string }, signal?: string | null }, now?: () => number, log?: (message: string) => void, waitForIdle?: (deadline: number) => void }} options
+ * When the seed step has left cached system packages in apt's archive folder, they are installed
+ * first with no network at all, then each browser must launch; only if that fails does the setup
+ * fall back to Playwright's own apt-get, which needs the runner's Ubuntu mirror.
+ * @param {{ browsers?: string[], cacheHit?: boolean, cachedDebs?: string[], execute?: (command: string, args: string[], timeoutMs: number) => { status: number | null, error?: { code?: string }, signal?: string | null }, now?: () => number, log?: (message: string) => void, waitForIdle?: (deadline: number) => void }} options
*/
export function installBrowsers({
browsers = ["chromium"],
@@ -30,6 +35,7 @@ export function installBrowsers({
now = Date.now,
log = console.log,
waitForIdle = execute === runBounded ? (deadline) => waitForPackageManager(deadline, now) : () => {},
+ cachedDebs = execute === runBounded ? archiveDebs() : [],
} = {}) {
if (
!browsers.length ||
@@ -39,6 +45,7 @@ export function installBrowsers({
throw new Error("Invalid browser selection; name unique supported engines");
const label = browsers.length === 1 && browsers[0] === "chromium" ? "Chromium" : "Playwright browser";
const deadline = now() + 360_000;
+ if (cachedDebs.length && installOffline({ browsers, cachedDebs, execute, now, deadline, label, log }) === 0) return 0;
const operations = cacheHit
? [
["install-deps", ...browsers],
@@ -67,6 +74,54 @@ export function installBrowsers({
return 0;
}
+/** The cached .deb files the seed step copied in; none when the cache missed. */
+export function archiveDebs(dir = APT_ARCHIVES) {
+ return existsSync(dir)
+ ? readdirSync(dir)
+ .filter((name) => name.endsWith(".deb"))
+ .sort()
+ .map((name) => join(dir, name))
+ : [];
+}
+
+/** A fixed script; the Playwright path and the engine names arrive as arguments, never as code. */
+const LAUNCH_PROBE = `const [playwright, ...names] = process.argv.slice(1);
+const pw = require(playwright);
+(async () => {
+ for (const name of names) {
+ const browser = await pw[name].launch();
+ await browser.close();
+ }
+})().catch((error) => {
+ console.error(error);
+ process.exit(1);
+});`;
+
+/**
+ * The runner's Ubuntu mirror can be unreachable for hours (11 Nov 2026: "Temporary failure
+ * resolving 'azure.archive.ubuntu.com'"), and Playwright's install-deps always runs apt-get update
+ * first. Install the cached packages as local files with downloads switched off, fetch the browsers
+ * alone, and pass only if every browser launches. Any failure hands back to the online path.
+ */
+function installOffline({ browsers, cachedDebs, execute, now, deadline, label, log }) {
+ log(`${label} setup: installing ${cachedDebs.length} cached system packages without the network.`);
+ const steps = [
+ ["sudo", ["apt-get", "install", "-y", "--no-download", "--no-install-recommends", ...cachedDebs], 120_000],
+ [process.execPath, [cli, "install", ...browsers], 120_000],
+ [process.execPath, ["-e", LAUNCH_PROBE, resolve(root, "node_modules/playwright"), ...browsers], 60_000],
+ ];
+ for (const [command, args, limit] of steps) {
+ const remaining = deadline - now();
+ const result = remaining > 0 ? execute(command, args, Math.min(limit, remaining)) : undefined;
+ if (!result || result.error || result.status !== 0) {
+ log(`${label} setup: the offline install did not finish; trying the runner's apt mirror.`);
+ return 1;
+ }
+ }
+ log(`${label} setup: installed from cached system packages; every browser launches.`);
+ return 0;
+}
+
// Existing consumers retain their default Chromium-only command and retry budget.
/** Compatibility entry point for callers requesting Chromium only. @param {Parameters[0]} [options] */
export function installChromium(options) {
diff --git a/tests/ward-browser-install.test.ts b/tests/ward-browser-install.test.ts
index 7d5bbcb3b..58159bff7 100644
--- a/tests/ward-browser-install.test.ts
+++ b/tests/ward-browser-install.test.ts
@@ -34,6 +34,54 @@ describe("bounded release browser setup", () => {
]);
});
+ it("installs cached system packages without the network and stops once every browser launches", () => {
+ const commands: string[][] = [];
+ expect(
+ installChromium({
+ browsers: ["chromium", "webkit"],
+ cacheHit: true,
+ cachedDebs: ["/var/cache/apt/archives/a.deb", "/var/cache/apt/archives/b.deb"],
+ log: () => {},
+ execute(command, args) {
+ commands.push([command, ...args]);
+ return { status: 0 };
+ },
+ }),
+ ).toBe(0);
+ expect(commands).toHaveLength(3);
+ expect(commands[0]).toEqual([
+ "sudo",
+ "apt-get",
+ "install",
+ "-y",
+ "--no-download",
+ "--no-install-recommends",
+ "/var/cache/apt/archives/a.deb",
+ "/var/cache/apt/archives/b.deb",
+ ]);
+ expect(commands[1].slice(2)).toEqual(["install", "chromium", "webkit"]);
+ expect(commands[2][1]).toBe("-e");
+ expect(commands[2].slice(-2)).toEqual(["chromium", "webkit"]);
+ expect(commands.flat()).not.toContain("install-deps");
+ expect(commands.flat()).not.toContain("--with-deps");
+ });
+
+ it("falls back to the apt mirror when a browser does not launch after the offline install", () => {
+ const commands: string[][] = [];
+ expect(
+ installChromium({
+ cachedDebs: ["/var/cache/apt/archives/a.deb"],
+ log: () => {},
+ execute(command, args) {
+ commands.push([command, ...args]);
+ return { status: args[0] === "-e" ? 1 : 0 };
+ },
+ }),
+ ).toBe(0);
+ expect(commands).toHaveLength(4);
+ expect(commands[3].slice(2)).toEqual(["install", "--with-deps", "chromium"]);
+ });
+
it("refuses an unsupported or empty engine selection without invoking a command", () => {
const execute = () => {
throw new Error("must not start an installation");
From 55dc47a20b9981d247e86e209a4bb54393494328 Mon Sep 17 00:00:00 2001
From: Claude
Date: Wed, 11 Nov 2026 17:31:11 +0000
Subject: [PATCH 3/7] Restore WebKit's cached packages from the path the save
step used
The cache path is part of the cache version, so a different restore path
never finds the release gate's WebKit cache. Seed Chromium's packages,
then restore WebKit's into the same folder and seed again. (Codex review)
Co-Authored-By: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01GxPPxmrqgwa3hCAtaMf9JT
(cherry picked from commit a5dd0ff6bd545858d1bf6a444ccf58b43ceabd96)
---
.github/workflows/ward-flow.yml | 14 ++++++++------
1 file changed, 8 insertions(+), 6 deletions(-)
diff --git a/.github/workflows/ward-flow.yml b/.github/workflows/ward-flow.yml
index ec29641f4..c7ce0b48c 100644
--- a/.github/workflows/ward-flow.yml
+++ b/.github/workflows/ward-flow.yml
@@ -374,20 +374,22 @@ jobs:
restore-keys: |
ward-release-apt-${{ runner.os }}-chromium-${{ steps.shared-version.outputs.version }}-
ward-release-apt-${{ runner.os }}-${{ steps.shared-version.outputs.version }}-
- # WebKit's own packages, so the offline install covers both engines when the mirror is down.
+ - name: Seed release system packages
+ if: ${{ !cancelled() && steps.install.outcome == 'success' && steps.backend-install.outcome == 'success' }}
+ run: node scripts/ward-ci-public/apt-archive-cache.mjs seed "$HOME/.cache/ward-release-apt"
+ # WebKit's own packages too, so the offline install covers both engines when the mirror is down.
+ # Same path as the save step, because the path is part of the cache version.
- name: Restore release WebKit system packages
if: ${{ !cancelled() && steps.install.outcome == 'success' && steps.backend-install.outcome == 'success' }}
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9
with:
- path: ~/.cache/ward-release-apt-webkit
+ path: ~/.cache/ward-release-apt
key: ward-release-apt-${{ runner.os }}-webkit-${{ steps.shared-version.outputs.version }}-${{ steps.shared-version.outputs.image }}
restore-keys: |
ward-release-apt-${{ runner.os }}-webkit-${{ steps.shared-version.outputs.version }}-
- - name: Seed release system packages
+ - name: Seed release WebKit system packages
if: ${{ !cancelled() && steps.install.outcome == 'success' && steps.backend-install.outcome == 'success' }}
- run: |
- node scripts/ward-ci-public/apt-archive-cache.mjs seed "$HOME/.cache/ward-release-apt"
- node scripts/ward-ci-public/apt-archive-cache.mjs seed "$HOME/.cache/ward-release-apt-webkit"
+ run: node scripts/ward-ci-public/apt-archive-cache.mjs seed "$HOME/.cache/ward-release-apt"
- name: Install Chromium and WebKit for the shared-mode journeys
id: shared-chromium
timeout-minutes: 10
From dc9e2ef9945d4da7c4d46e0f6dcfaa8f44a2cc76 Mon Sep 17 00:00:00 2001
From: Claude
Date: Wed, 11 Nov 2026 18:25:59 +0000
Subject: [PATCH 4/7] Draw Home's code-split cards with a held first page
Since D-137 a fresh device is held, so the browser draws the whole page
on the pass after hydration. Home's lazily loaded "Beds that fit" and
"since you were last here" cards had not arrived by then, drew nothing
and popped in: a 0.134 desktop layout shift (release gate, reproduced on
a local production build). Home's code-split modules now register their
loaders, and a held first load waits for them (at most 3 s) before it
shows the page. Loads that are not held are unchanged. 24 of 24 local
layout-shift runs pass on the production build; a DOM test covers the
wait and failed before.
Co-Authored-By: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01GxPPxnKy5cgHmGa8NdCd2U
---
.../coordinator/home-activity-lazy.tsx | 3 ++
.../coordinator/home-list-lazy.tsx | 3 ++
.../coordinator/home-outage-catch-up-lazy.tsx | 3 ++
.../coordinator/home-since-banner-lazy.tsx | 3 ++
.../shell/held-reveal-loaders.ts | 32 ++++++++++++++++
.../shell/ward-access-hold.tsx | 18 ++++++++-
tests/ward-access-first-paint.dom.test.tsx | 37 +++++++++++++++++++
7 files changed, 97 insertions(+), 2 deletions(-)
create mode 100644 src/components/ward-management/shell/held-reveal-loaders.ts
diff --git a/src/components/ward-management/coordinator/home-activity-lazy.tsx b/src/components/ward-management/coordinator/home-activity-lazy.tsx
index 62310a0ca..c834f5681 100644
--- a/src/components/ward-management/coordinator/home-activity-lazy.tsx
+++ b/src/components/ward-management/coordinator/home-activity-lazy.tsx
@@ -3,6 +3,7 @@
import dynamic from "next/dynamic";
import { preloadWhenIdle } from "../preload-when-idle";
+import { loadBeforeHeldReveal } from "../shell/held-reveal-loaders";
/**
* Phone budget (31 Oct 2026, Home and Activity build): the Planned list brings the forecast model
@@ -16,3 +17,5 @@ const loadPlannedList = () => import("./planned-list");
export const PlannedList = dynamic(() => loadPlannedList().then((m) => m.PlannedList), { loading: () => null });
preloadWhenIdle(loadPlannedList);
+// A held first load draws this card with the page (D-137 follow-up, held-reveal-loaders.ts).
+loadBeforeHeldReveal(loadPlannedList);
diff --git a/src/components/ward-management/coordinator/home-list-lazy.tsx b/src/components/ward-management/coordinator/home-list-lazy.tsx
index b1ada57cb..db759e879 100644
--- a/src/components/ward-management/coordinator/home-list-lazy.tsx
+++ b/src/components/ward-management/coordinator/home-list-lazy.tsx
@@ -3,6 +3,7 @@
import dynamic from "next/dynamic";
import { preloadWhenIdle } from "../preload-when-idle";
+import { loadBeforeHeldReveal } from "../shell/held-reveal-loaders";
// A1K-01 / DR-03 (26 Oct 2026): the server draws My list's rows before their code arrives, so their
// styles load with Home. Without them the rows paint as plain text, then grow into cards and push
// Home down (0.16 on a phone). My list itself now loads with the page (below).
@@ -36,3 +37,5 @@ preloadWhenIdle(loadBedsGapCard);
export const HomeReleaseSheet = dynamic(() => import("./home-release-sheet").then((m) => m.HomeReleaseSheet), {
loading: () => null,
});
+// A held first load draws this card with the page (D-137 follow-up, held-reveal-loaders.ts).
+loadBeforeHeldReveal(loadBedsGapCard);
diff --git a/src/components/ward-management/coordinator/home-outage-catch-up-lazy.tsx b/src/components/ward-management/coordinator/home-outage-catch-up-lazy.tsx
index c8c4403b1..f388e6de3 100644
--- a/src/components/ward-management/coordinator/home-outage-catch-up-lazy.tsx
+++ b/src/components/ward-management/coordinator/home-outage-catch-up-lazy.tsx
@@ -3,6 +3,7 @@
import dynamic from "next/dynamic";
import { preloadWhenIdle } from "../preload-when-idle";
+import { loadBeforeHeldReveal } from "../shell/held-reveal-loaders";
/**
* Phone budget: Catch-up after the outage (Batch 4 item 25, D-142) renders nothing until an outage
@@ -16,3 +17,5 @@ export const HomeOutageCatchUp = dynamic(() => loadCatchUp().then((m) => m.HomeO
});
preloadWhenIdle(loadCatchUp);
+// A held first load draws this card with the page (D-137 follow-up, held-reveal-loaders.ts).
+loadBeforeHeldReveal(loadCatchUp);
diff --git a/src/components/ward-management/coordinator/home-since-banner-lazy.tsx b/src/components/ward-management/coordinator/home-since-banner-lazy.tsx
index 83e52f2a4..2b8be030d 100644
--- a/src/components/ward-management/coordinator/home-since-banner-lazy.tsx
+++ b/src/components/ward-management/coordinator/home-since-banner-lazy.tsx
@@ -3,6 +3,7 @@
import dynamic from "next/dynamic";
import { preloadWhenIdle } from "../preload-when-idle";
+import { loadBeforeHeldReveal } from "../shell/held-reveal-loaders";
/**
* Phone budget: the "since you were last here" line brings the last-look comparison and its sheet
@@ -16,3 +17,5 @@ export const HomeSinceBanner = dynamic(() => loadSinceBanner().then((m) => m.Hom
});
preloadWhenIdle(loadSinceBanner);
+// A held first load draws this card with the page (D-137 follow-up, held-reveal-loaders.ts).
+loadBeforeHeldReveal(loadSinceBanner);
diff --git a/src/components/ward-management/shell/held-reveal-loaders.ts b/src/components/ward-management/shell/held-reveal-loaders.ts
new file mode 100644
index 000000000..104d20713
--- /dev/null
+++ b/src/components/ward-management/shell/held-reveal-loaders.ts
@@ -0,0 +1,32 @@
+/**
+ * D-137 follow-up (11 Nov 2026): since a fresh device is held, the browser draws the whole page
+ * itself, on the pass straight after hydration. Before the hold, the server drew a route's
+ * code-split cards into the page. Now a card whose code had not arrived drew nothing and then
+ * popped in: Home's "Beds that fit" and "since you were last here" cards pushed Home down by 0.13
+ * on a desktop (release-gate layout shift). A route's code-split modules register their loaders
+ * here, and a held first load waits for them before it shows the page (`WardDeskHold`). A load
+ * that is not held does not wait, and the loaders still preload when idle as before.
+ */
+const loaders = new Set<() => Promise>();
+
+/** The longest a held first load waits for its cards' code. A slower card loads as it did before. */
+const HELD_REVEAL_WAIT_MS = 3_000;
+
+export function loadBeforeHeldReveal(...load: Array<() => Promise>): void {
+ if (typeof window === "undefined") return;
+ for (const loader of load) loaders.add(loader);
+}
+
+export function hasHeldRevealLoaders(): boolean {
+ return loaders.size > 0;
+}
+
+/** Starts every registered load and settles when all have finished, or after the wait. */
+export function heldRevealReady(waitMs = HELD_REVEAL_WAIT_MS): Promise {
+ const all = Promise.allSettled([...loaders].map((load) => load())).then(() => undefined);
+ return Promise.race([all, new Promise((resolve) => window.setTimeout(resolve, waitMs))]);
+}
+
+export function resetHeldRevealLoadersForTests(): void {
+ loaders.clear();
+}
diff --git a/src/components/ward-management/shell/ward-access-hold.tsx b/src/components/ward-management/shell/ward-access-hold.tsx
index 76d656df3..48858505d 100644
--- a/src/components/ward-management/shell/ward-access-hold.tsx
+++ b/src/components/ward-management/shell/ward-access-hold.tsx
@@ -1,6 +1,7 @@
"use client";
-import { useEffect, useSyncExternalStore, type ReactNode } from "react";
+import { useEffect, useState, useSyncExternalStore, type ReactNode } from "react";
+import { hasHeldRevealLoaders, heldRevealReady } from "./held-reveal-loaders";
import { WARD_DESK_PENDING_ATTRIBUTE } from "./ward-desk-early";
const never = () => () => {};
@@ -46,5 +47,18 @@ export function WardDeskHold({
children: ReactNode;
}) {
const deskKnown = useDeskKnown();
- return held && !deskKnown ? placeholder : children;
+ // The route's code-split cards are fetched before a held page is shown, so they are drawn with
+ // it rather than popping in after (held-reveal-loaders.ts).
+ const [cardsReady, setCardsReady] = useState(() => !held || !hasHeldRevealLoaders());
+ useEffect(() => {
+ if (!held || !deskKnown || cardsReady) return;
+ let live = true;
+ void heldRevealReady().then(() => {
+ if (live) setCardsReady(true);
+ });
+ return () => {
+ live = false;
+ };
+ }, [held, deskKnown, cardsReady]);
+ return held && !(deskKnown && cardsReady) ? placeholder : children;
}
diff --git a/tests/ward-access-first-paint.dom.test.tsx b/tests/ward-access-first-paint.dom.test.tsx
index 5af2d08c1..852787e23 100644
--- a/tests/ward-access-first-paint.dom.test.tsx
+++ b/tests/ward-access-first-paint.dom.test.tsx
@@ -3,6 +3,10 @@ import { hydrateRoot } from "react-dom/client";
import { renderToString } from "react-dom/server";
import { afterEach, describe, expect, it } from "vitest";
import { WardDeskHold, WardDeskPendingClear } from "@/components/ward-management/shell/ward-access-hold";
+import {
+ loadBeforeHeldReveal,
+ resetHeldRevealLoadersForTests,
+} from "@/components/ward-management/shell/held-reveal-loaders";
import {
WARD_DESK_EARLY_SCRIPT,
WARD_DESK_HELD_COOKIE,
@@ -131,6 +135,39 @@ describe("server page held for a desk other than the coordinator's", () => {
act(() => root?.unmount());
});
+ it("keeps a held page's placeholder until the route's code-split cards have loaded (D-137 follow-up)", async () => {
+ let finish: () => void = () => undefined;
+ const card = new Promise((resolve) => {
+ finish = resolve;
+ });
+ loadBeforeHeldReveal(() => card);
+ try {
+ const tree = (
+ Loading page
}>
+ Home
+
+ );
+ const container = document.createElement("div");
+ container.innerHTML = renderToString(tree);
+ document.body.appendChild(container);
+ let root: ReturnType | undefined;
+ await act(async () => {
+ root = hydrateRoot(container, tree);
+ });
+ // The desk is known, but a card's code has not arrived: the page waits rather than jump.
+ expect(container.querySelector('[data-testid="placeholder"]')).not.toBeNull();
+ expect(container.querySelector('[data-testid="page"]')).toBeNull();
+ await act(async () => {
+ finish();
+ await card;
+ });
+ expect(container.querySelector('[data-testid="page"]')).not.toBeNull();
+ act(() => root?.unmount());
+ } finally {
+ resetHeldRevealLoadersForTests();
+ }
+ });
+
it("keeps the server's page only for the coordinator", () => {
const html = renderToString(
Loading page}>
From e3ed4c5e2d4ce1d1e9beb2822ab94ac7cd4eaee5 Mon Sep 17 00:00:00 2001
From: Claude
Date: Wed, 11 Nov 2026 18:38:56 +0000
Subject: [PATCH 5/7] Skip cached packages that would downgrade, and qualify
D-153's motion claim
A browser package cache saved on an older runner image can hold a package
older than the installed one. Naming it asks apt for a downgrade, which -y
refuses, so the whole offline install failed. Those files are now left out.
D-153 now says the settle check ignores infinite animations and the running
Undo bar, as the helper does.
Co-Authored-By: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01GxPPxnKy5cgHmGa8NdCd2U
---
docs/ward-flow/decisions.md | 2 +-
scripts/ward-ci-public/install-chromium.mjs | 40 ++++++++++++++++++++-
tests/ward-browser-install.test.ts | 22 +++++++++++-
3 files changed, 61 insertions(+), 3 deletions(-)
diff --git a/docs/ward-flow/decisions.md b/docs/ward-flow/decisions.md
index 03b2b8ae8..1a75a842e 100644
--- a/docs/ward-flow/decisions.md
+++ b/docs/ward-flow/decisions.md
@@ -1808,6 +1808,6 @@ is inferred.
- **Date:** 11 November 2026, 11:41 PM AWST (15:41 UTC). **Decided by:** Josh, "Yes to all recommendations" in the Ward Flow project chat, answering the coordinator's list of five open questions. Two of those answers are rulings and are recorded here. The community desk ED heads-up answer is still unclear and will be recorded separately. The uptime monitor secrets and the GitHub and Railway settings are tasks for Josh, not rulings.
- **Decision:**
- 1. **Phone release gate settle check:** the shared helper `waitForAccessibleReadiness` (`tests/helpers/ward-release-gate.ts`, changed in PR #1607) stays as it is. It counts a 100 ms tick against its 5-second settle budget only when the browser drew a frame in that tick, or while fonts load. A cold WebKit page held by D-137 can pause drawing after its first frame. Motion that keeps the browser drawing still fails after 5 seconds, and the test timeout still bounds a renderer that never recovers.
+ 1. **Phone release gate settle check:** the shared helper `waitForAccessibleReadiness` (`tests/helpers/ward-release-gate.ts`, changed in PR #1607) stays as it is. It counts a 100 ms tick against its 5-second settle budget only when the browser drew a frame in that tick, or while fonts load. A cold WebKit page held by D-137 can pause drawing after its first frame. Finite motion, or geometry that keeps changing while the browser draws, still fails after 5 seconds, and the test timeout still bounds a renderer that never recovers. The helper ignores infinite animations and the running Undo availability bar, so those can carry on while the page settles.
2. **A9-01, a separate GitHub identity for the agents:** deferred until after the pilot. Agents keep publishing as they do now.
- **Boundary:** synthetic data only. Changes no clinical value or rule, weakens no release gate check, and approves no merge, release or settings change.
diff --git a/scripts/ward-ci-public/install-chromium.mjs b/scripts/ward-ci-public/install-chromium.mjs
index e88a3d9af..61efdca64 100644
--- a/scripts/ward-ci-public/install-chromium.mjs
+++ b/scripts/ward-ci-public/install-chromium.mjs
@@ -35,7 +35,7 @@ export function installBrowsers({
now = Date.now,
log = console.log,
waitForIdle = execute === runBounded ? (deadline) => waitForPackageManager(deadline, now) : () => {},
- cachedDebs = execute === runBounded ? archiveDebs() : [],
+ cachedDebs = execute === runBounded ? withoutDowngrades(archiveDebs()) : [],
} = {}) {
if (
!browsers.length ||
@@ -84,6 +84,44 @@ export function archiveDebs(dir = APT_ARCHIVES) {
: [];
}
+/** Each installed package's version, keyed by "name arch", read once from dpkg. */
+function installedVersions() {
+ const result = spawnSync("dpkg-query", ["-W", "-f", "${Package} ${Architecture} ${Version}\\n"], {
+ encoding: "utf8",
+ });
+ const versions = new Map();
+ for (const line of result.status === 0 ? result.stdout.split("\n") : []) {
+ const [name, arch, version] = line.split(" ");
+ if (version) versions.set(`${name} ${arch}`, version);
+ }
+ return versions;
+}
+
+/** True when dpkg orders version `a` before version `b`. */
+function olderThan(a, b) {
+ return spawnSync("dpkg", ["--compare-versions", a, "lt", b]).status === 0;
+}
+
+/**
+ * A cache saved on an older runner image can hold a package older than the one installed. Naming
+ * that file asks apt for a downgrade, which `-y` refuses, and the whole offline install fails. Leave
+ * such files out; archive names are `name_version_arch.deb`, with an epoch's colon written `%3a`.
+ */
+export function withoutDowngrades(debs, installed = installedVersions(), isOlder = olderThan) {
+ return debs.filter((path) => {
+ const parts = path
+ .split("/")
+ .pop()
+ .replace(/\.deb$/, "")
+ .split("_");
+ if (parts.length !== 3) return true;
+ const [name, rawVersion, arch] = parts;
+ const current = installed.get(`${name} ${arch}`);
+ const version = decodeURIComponent(rawVersion);
+ return !current || current === version || !isOlder(version, current);
+ });
+}
+
/** A fixed script; the Playwright path and the engine names arrive as arguments, never as code. */
const LAUNCH_PROBE = `const [playwright, ...names] = process.argv.slice(1);
const pw = require(playwright);
diff --git a/tests/ward-browser-install.test.ts b/tests/ward-browser-install.test.ts
index 58159bff7..d4ec9fc0e 100644
--- a/tests/ward-browser-install.test.ts
+++ b/tests/ward-browser-install.test.ts
@@ -1,5 +1,5 @@
import { describe, expect, it } from "vitest";
-import { installChromium } from "../scripts/ward-ci-public/install-chromium.mjs";
+import { installChromium, withoutDowngrades } from "../scripts/ward-ci-public/install-chromium.mjs";
describe("bounded release browser setup", () => {
it("installs both required engines on a cold runner", () => {
@@ -82,6 +82,26 @@ describe("bounded release browser setup", () => {
expect(commands[3].slice(2)).toEqual(["install", "--with-deps", "chromium"]);
});
+ it("leaves out a cached package older than the installed one, so apt is never asked to downgrade", () => {
+ const dir = "/var/cache/apt/archives";
+ const installed = new Map([
+ ["libnss3 amd64", "2:3.98-1build1"],
+ ["fonts-liberation all", "1:1.07.4-11"],
+ ]);
+ const older = (a: string, b: string) => a < b;
+ expect(
+ withoutDowngrades(
+ [
+ `${dir}/libnss3_2%3a3.90-1_amd64.deb`,
+ `${dir}/fonts-liberation_1%3a1.07.4-11_all.deb`,
+ `${dir}/libwoff1_1.0.2-2build1_amd64.deb`,
+ ],
+ installed,
+ older,
+ ),
+ ).toEqual([`${dir}/fonts-liberation_1%3a1.07.4-11_all.deb`, `${dir}/libwoff1_1.0.2-2build1_amd64.deb`]);
+ });
+
it("refuses an unsupported or empty engine selection without invoking a command", () => {
const execute = () => {
throw new Error("must not start an installation");
From 47d45b0fbe61d1e59c9f5ff3a50e1c6aa3420ea8 Mon Sep 17 00:00:00 2001
From: Claude
Date: Wed, 11 Nov 2026 18:44:29 +0000
Subject: [PATCH 6/7] Tell knip the installer calls dpkg and dpkg-query
Same line as #1631, so the two branches stay identical here.
Co-Authored-By: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01GxPPxnKy5cgHmGa8NdCd2U
---
knip.json | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/knip.json b/knip.json
index 76b004981..139a2f86a 100644
--- a/knip.json
+++ b/knip.json
@@ -21,5 +21,5 @@
}
},
"ignoreDependencies": ["@axe-core/playwright", "playwright-core", "rimraf", "tsx"],
- "ignoreBinaries": ["pgrep", "openssl", "jq"]
+ "ignoreBinaries": ["pgrep", "openssl", "jq", "dpkg", "dpkg-query"]
}
From 26c04243195beca4026cc72df1d4c4b91745796f Mon Sep 17 00:00:00 2001
From: Claude
Date: Wed, 11 Nov 2026 19:11:43 +0000
Subject: [PATCH 7/7] List the held-reveal wait as a UI pause, not an invented
figure
Co-Authored-By: Claude Opus 5.5
Claude-Session: https://claude.ai/code/session_01GxPPxnKy5cgHmGa8NdCd2U
---
tests/ward-no-invented-screen-figures.test.ts | 6 ++++++
1 file changed, 6 insertions(+)
diff --git a/tests/ward-no-invented-screen-figures.test.ts b/tests/ward-no-invented-screen-figures.test.ts
index cea5ec235..8c8393c80 100644
--- a/tests/ward-no-invented-screen-figures.test.ts
+++ b/tests/ward-no-invented-screen-figures.test.ts
@@ -192,6 +192,12 @@ const CONSTANT_EXCLUSIONS: ReadonlyArray<{ id: string; why: string; file?: RegEx
file: /^shell\/ward-loading-note-early\.ts$/,
name: /^WARD_LOADING_SLOW_MS$/,
},
+ {
+ id: "held-reveal-wait",
+ why: "The longest a held first load waits for its code-split cards before it shows the page anyway (D-137 follow-up, 11 Nov 2026); a UI pause, never shown, not a limit.",
+ file: /^shell\/held-reveal-loaders\.ts$/,
+ name: /^HELD_REVEAL_WAIT_MS$/,
+ },
{
id: "offline-prefetch-fresh",
why: "How long a page the router fetched in full may open offline, kept under Next's 5-minute client cache (pre-launch review E1, Q273, 2 Nov 2026); a cache window, not a limit.",