# Ward Desk — hospital management

A double-sidebar hospital operations product for **Halden General Hospital**, built on
Reckon. Frozen clock: Tuesday 17 March 2026, 09:20.

Entry point: `HospitalDesk.dc.html`. The design system loads through `ds-base.js`
(one line to edit in a consuming project); `boot.js` loads the product's own files.

## The shell — sidebar shell, rail + panel mode

Two navigation columns on one tinted plane, divided by hairlines: a **60px rail of
six categories** (Care, Pharmacy, Finance, Business, People, Workspace) beside a **232px panel
of the destinations inside the chosen category**. The content column sits flush on the
page colour with no inset pane and is the only scroll owner.

A category is **not a route** — it is the sectioning device, so the rail is a
`tablist` over the panel with `aria-selected`, and a category holds 3–5 destinations
rather than the 5–9 a labelled group would. The panel header names the category
instead of an uppercase group label. Exactly one destination is active, derived from
the route; the panel follows the route, and a reader may also browse a category
without navigating.

Duties: search → top bar (read-only field opening the ⌘K palette) · account → the
**rail footer, at every width** · utility (notifications, theme) → top bar · page
context → the page header band.

| Width | Behaviour |
|---|---|
| ≥1280px | rail + panel |
| 1024–1280px | the panel's track leaves the grid; each rail icon opens its destinations as a flyout, labels become tooltips |
| <1024px | both columns leave the layout and return as one off-canvas drawer carrying rail **and** panel |

## The frame — one neutral plane, everything on it a card

The rail, the panel and the content column all sit on `--sidebar`. The tint is the
page; the white is the object, so **nothing in the content column is drawn with a
divider**. Every region is a card: `--card` surface, one hairline, 14px radius, the
smallest shadow in the system, separated from its neighbours by a 16px gutter.

Four card variants, and nothing else:

| Variant | For |
|---|---|
| `.hd-band` | a titled region of content |
| `.hd-band--flush` | a card whose child bleeds to its edge (tables, filter bars, bulk bars keep the card gutter on their own cells) |
| `.hd-band--tint` | recessed: muted surface, no shadow — work nobody can act on yet, and supporting detail |
| `.hd-band--fill` | stretches to its row, so cards side by side line their footers up |

Metric tiles are the one auto-fitting grid (§5.1); everything else is a column stack of
cards. Density: 28px nav rows, 24px page gutters, 20px card padding, 40px table rows.
Active state: `--sidebar-accent` wash + foreground token + medium weight, and the active
rail category takes the card colour and a hairline so it reads as the selected tab of
the panel.

## Destinations (20) and what each one shows

- **Today** — figure row, ward board, then the four lists of work waiting on somebody
  (bed waits, held discharges, the theatre list, notes owed a reply)
- **Patients** — directory with a photograph per row; `/patients/:id` is one person's
  admission, medication chart, procedures and notes; `/patients/new` is a staged form
- **Admissions** — the ward board as a table with bed-wait clocks, bulk "mark fit"
- **Interventions** — today's theatre list as a **timed ladder** over the full table;
  `/interventions/:id` carries the consent gate. This is the destination that would
  most plausibly have been a plain table and is not
- **Wards and beds** — a card per ward with one pip per bed, then the chosen ward's
  occupants beside who is rostered onto it today
- **Drug round** — active courses grouped by where their dose clock has got to:
  overdue, this hour, later today
- **Rota** — three days, three shifts, as a grid rather than a table, because the
  question is whether a shift is covered against the ward establishment
- **Reports** — six rates, each naming the set it divides and drilling through to it,
  plus occupancy by ward and the stays running long
- **Medication orders** — ward as a *scope* filter above the figures; dose clock per row
- **Formulary and stock** — reorder bar per line, courses depending on each line
- **Invoices** — one invoice per episode of care, every line naming the procedure,
  the bed days or the medication course it bills; `/invoices/:id` is the record, its
  lines linking through to the care they bill, its payments and the payer's query
- **Payments** — every movement of money; only cleared money moves a balance
- **Revenue and debt** — billed, collected and outstanding by payer, aged debt in
  buckets that partition one set, what was billed for by kind, and the past-due list
- **Contracts and tariffs** — what each payer agreed to pay for each billable item,
  as a price matrix, plus every invoice line billed off contract
- **Credit control** — the chase worklist: balance, terms, last contact and the step
  the ladder says comes next. A disputed invoice is never chased
- **Cost and margin** — billed against cost per episode (bed days at the ward's day
  cost, procedures at planned theatre minutes, drug courses from the order's own
  frequency), and the cost model stated so a figure can be argued with
- **Doctors** / **Employees** — two views of one staff array; `/doctors/:id` and
  `/employees/:id` render one record, breadcrumbed by the route it was opened from
- **Feed** — notes owed a reply, every note on every record, and every commit
- **Import and export** — job table; a failed import opens a row-by-row review whose
  decisions move the job's own accepted / rejected / pending counts

Off the sidebar: **your profile** and **seven settings screens** — Hospital, Wards and
beds, Roles and permissions, Billing, Integrations and feeds, Notifications, Security —
plus sign-in and the five-screen password reset flow. Settings is one entry in the
account menu; the level inside it is a tab strip on the page, derived from the route
table, never a third navigation column. Those eight are the only **staged** screens in
the product.

**Settings have effects, and each screen says what its own is.** Bed counts and the
nursing establishment are the denominator of every occupancy figure and of the rota's
shortfall. The role matrix writes into the capability maps every transition function
reads, so an action appears, disables with a reason, or does not exist because of it —
and it refuses to save a permission nobody would hold. The write-off threshold decides
whether a balance can be written off here or becomes a request for approval. A feed
switched off is not offered in the import dialog. The reference prefix names every
invoice raised from then on.

**The photograph upload** on your profile is a real file input with a drop zone that is
also its label, so the keyboard reaches it: JPEG or PNG up to 2 MB, validated on type
and size with the reason stated, previewed through `FileReader`, and — because the
screen is staged — a preview until Save. The four badge-system pictures stay as the
alternative, and "remove photograph" clears the field rather than saving anything. Everything else is instant, with an
undo toast where the change is consequential; discharging somebody, recording a
procedure as abandoned and stopping a course open confirm dialogs.

## The product's own files

`enums.js` — ten status fields with tone groupings, classification lists, derived
flags with the clock injected, transition functions per entity, role capability maps.
`nav.js` — the route table, the rail/panel projections, `categoryOf`.
`fixtures.js` — 12 patients, 14 staff, 10 admissions, 10 interventions, 12 orders,
10 formulary lines, 16 notes, 17 activity entries, 7 transfer jobs, the 12 rows the
patient-index import could not place, the five wards' bed counts and nursing
establishments, a three-day rota over two wards, 10 invoices with the 4 payments
against them, 15 tariff items priced across 4 payer contracts, 4 chases and the
hospital's own cost rates.

**Contracts are the join.** A tariff item's label *is* the invoice line's label, so a
line's contracted price is looked up at render and a line billed off contract is a
finding on three screens at once (the invoice record, Contracts, the palette). Issuing
an invoice takes its due date from the payer's contracted terms — 30 days for the
commissioner, 14 for an overseas visitor, none for self-pay.

**Money.** Amounts are pence, and nothing summable is stored: an invoice's total is
the sum of its lines, what it has been paid is the sum of its **cleared** payments,
and its balance is the difference. The product spells sterling itself
(`formatPounds` in `metrics.js`) rather than editing the shared USD formatter in
`lib/ui-runtime.js`, which stays byte-identical across templates. `metrics.js` — every figure on every screen.
`lib/` — the four domain-free copies, byte-identical with the other templates.

## Local additions to the design system

`Band`, `Figures`, `Pairs`, `StockBar`, `PersonCell`, `Clock`, `Transitions`,
`RowActions`, `FilterBar`, `BulkBar`, `ExportMenu`, `ImportButton`, `TableWrap`,
`TableFoot`, `NoResults`, `Note` / `NoteThread` / `NoteComposer`, `ConfirmDialog`,
`FormDialog`, `FormBar`, `useSort` / `SortHead` / `sortRows`, `AllergyChips` — all in
`HdParts.jsx`, all layout and composition over existing primitives. No new colour,
radius, shadow or type size.

## Photographs

User photos are load-bearing here: every patient row, every note, every staff record
and the account menu carry one, from `assets/img/`. A record created in-session has no
photograph and says so.
