# Notes for the next agent — Ward Desk

Read `AGENTS.md` at the project root first. What this template decided, and where the
rules bent:

1. **Rail + panel is a double sidebar with ONE navigation owner.** The rail owns the
   category, the panel owns the destinations. Categories are not routes and never
   appear in a breadcrumb; `categoryOf(path)` derives the rail's active state from the
   route. Do not add a second row of top-level destinations to the top bar — it is
   utility only.
2. **Group size.** §6.3 asks for 5–9 items per labelled group. Here a category holds
   2–4, because the rail (not a group label) is the sectioning device and the panel
   header names it. Recorded in the shell comment and the README.
3. **Doctors and Employees are two views of one `staff` array.** Somebody new is
   created once, on Employees. Do not add a create action to Doctors.
4. **Ward is a scope filter on Medication orders** (above the figures, moves them);
   text and status filters are inside the list container and move the list only.
5. **Import and export share one screen with two states**: the job list, or the review
   of one job. Never both at once. Row decisions write into the fixture, so
   `accepted` / `rejected` / `pending` and the nav badge move together.
6. **Permissions are the honest disabled cases.** The signed-in user is a ward manager,
   so verifying a medication order is disabled with the reason stated
   (`ROLE_CAN_VERIFY_MEDICATION`). If you change `HD_SIGNED_IN` to a pharmacist, that
   disabled state disappears from the screens — check the tooltips still make sense.
7. **Enum coverage.** `patient.deceased`, `staff.left` and `transfer.queued` have no
   seeded record; `intervention.abandoned` and `transfer.queued` are reachable by one
   commit. Add a fixture before a screen claims a count for any of them.
8. **Time.** One helper (`formatWhen`), one countdown spelling (`formatCountdown`), and
   `Clock` takes `problem` per column — declared by the caller, because whether a past
   time is a problem is a property of the column, not of the value.
9. **Cards, not rules.** The content column is a neutral plane and every region on it is
   a card. There are four variants (`flush`, `tint`, `fill`, plain) and adding a fifth
   is a decision, not a convenience — say why in the shell comment if you do.
10. **Bed counts are data, not an enum.** `WARD_BEDS` carries each ward's beds and its
   nursing establishment; occupancy and the rota's shortfall both divide by it. Change a
   bed count and every occupancy figure in the product moves — check Reports and Today,
   not just Wards.
11. **No stored totals in finance.** `invoiceTotal`, `invoicePaid` and
   `invoiceBalance` in `metrics.js` are the only way to a number; a `paidMinor` field
   on an invoice would be a bug. An invoice's status follows its balance — the
   payment commit sets `paid` or `part_paid` from the arithmetic, never from a typed
   choice. Pending and failed payments are not paid money.
12. **A bill line names the record it bills.** `line.ref` points at an intervention,
   an admission or a medication order, and the row opens it. A line with no ref is
   allowed (an "other" tariff item) but says so on the record.
13. **Prices come from contracts, dates come from terms.** `contractedPrice` is the
   only source of a contracted price and `contractDueDays` the only source of a due
   date — no `DUE_DAYS` constant. A missing contract row is a real state ("not in the
   contract"), not a bug to paper over.
14. **Cost is derived from the record a line names.** `lineCost` costs a bed line from
   its admission's ward, a procedure from its planned theatre minutes and a drug course
   from the order's frequency against the formulary unit cost. A line with no ref has no
   cost, and Cost and margin says how much billing that is rather than guessing.
15. **The chase ladder is derived.** `nextChaseStep(lastStep)` decides what is offered;
   the step is never chosen from a menu of all five. Disputed invoices are excluded from
   the worklist — answering the query comes first.
16. **Settings are staged, and their effects are real.** Seven screens, one tab strip
   derived from the route table. If a setting cannot change something a reader can see,
   it does not belong there. `saveRoles` writes into the `ROLE_CAN_*` objects in
   `enums.js` on purpose — those objects are the live source every transition function
   reads — and bumps `rolesVersion` so the staged form remounts against what it just
   wrote. Do not copy the maps into React state; two sources would drift.
17. **The photograph is a form field.** The upload previews through `FileReader` and
   writes on Save like every other field. A staged screen may not hold an instant
   control, so "remove photograph" clears the field — it does not save.
18. **App.jsx is the router and the commit index.** The money commits live in
   `FinanceCommits.jsx` behind `makeFinanceCommits(ctx)`; give a new commit family the
   same treatment rather than growing App past a thousand lines.
19. **The rota covers Beaumont and Linden only.** `understaffedShifts` skips a ward with
   no rota rather than reporting it as unstaffed. Add rows for a third ward and it starts
   being counted; do not special-case it in a screen.
