# Hearth — residence desk

A house run properly. **Blackthorn House**, a six-bedroom Edwardian house near Marlow
with an annexe, grounds and a household of seven. Frozen clock: Tuesday 6 October
2026, 19:10 — dark since 18:20, the peak tariff window shut ten minutes ago, one
child still up, and the back door unlocked.

Entry point: `HomeDesk.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 frame

One plane for the whole shell — `--sidebar`, the design system's light neutral —
and everything on it is white: a 264px **labelled sidebar panel**, inset `--space-3`
on all four sides with `--radius-2xl` and one hairline, and a content column that
paints nothing where every region is a **card**: one hairline, `--radius-2xl`, one
elevation step, never nested. The top bar is static, not sticky. The gap between cards is the only separator — there
are no rules across the content column. Density is generous: 34px nav rows, 24px
page gutters, 44px table rows.

**Recession**, one treatment product-wide: a device that is updating or switched out
of the house, and a paused routine. Never something broken — a broken routine means
the house is quietly not doing something, so it is stated at full weight.

**Two signature geometries, and nothing else draws data.** `.hh-bar` is one
proportion — a live draw, a room's share, a battery, a filter's life. `.hh-strip` is
a 24-cell day, used three ways: the tariff windows, a heating schedule, and the
windows as entered in Settings.

| Width | Behaviour |
|---|---|
| ≥1280px | sidebar labelled |
| 900–1280px | sidebar collapses to a 64px icon rail, labels become tooltips, badges become dots |
| <900px | sidebar leaves the layout and returns as an off-canvas drawer, labelled and whole |
| <767px | metric row goes to one column, tables scroll in their own container, 44px targets |

## Ten destinations, two groups of five

**The house** — Today, Rooms, Cameras, Climate, Security
**Running it** — Devices, Routines, People, Energy, Upkeep

Five people live here, not a department. The first group is what the house is *doing*
and what somebody standing in the hall reaches for; the second is what keeps it
running. The four registers a household consults occasionally hang off the
destination they belong to rather than taking a line of their own: **Bills** under
Energy, **Access** under People, **Providers** under Upkeep, and the whole **Log**
under Today. Each is still a registered route with a full screen behind it, reached
from a button on its parent.

Records: `/devices/:id`, `/people/:id`, `/providers/:id`, `/upkeep/:id`. Off the
sidebar: `/account/profile` and five settings screens. Auth: sign-in plus the whole
five-screen password reset flow. There is no `/rooms/:id` and no `/cameras/:id` —
rooms holds every room and its controls at once, which is what a house is, and the
camera screen focuses one camera as a *state*.

**Devices is not a register.** It is the screen you go to when something is wrong with
the kit: the hub's verdict and a connection test across every device, what the hub can
hear but has not taken in, the devices wanting a person, and only then the whole list.
Pairing is a three-stage dialog — what it is, where it lives, then proof it answered —
and nothing is written until the last stage.

**Cameras** leads with the live view: one focused camera at 16:9 with its controls
(clip, talk, floodlight, shutter), the other three as tiles you pick, what they caught
today, and the shutters. Watching is derived from three facts at once — online, set to
record, shutter open — so a closed shutter reads as a decision somebody made and an
unanswering camera recesses onto the plain plane instead of showing a picture it does
not have. The live view and the clips are gated on the role: a child is told why the
screen is empty rather than shown a wall of disabled buttons.

Climate is the destination that would most plausibly have been a plain table and is
not: the question a heating screen answers is *why is the house this temperature*,
which is a chain, and the strip shows it against the clock in one glance.

## The five chains

Nothing below is stored. Each is one function in `metrics.js`, called by every
surface that shows it — which is why they move while you watch.

1. **Preferences → heating.** `comfortTarget` is the warmest comfort preference among
   the people who are home *and* awake; awake is itself derived from that person's own
   bedtime and wake times against the clock. Nobody home → the away setback. Everybody
   asleep → the night setback. Raise Iris's preference on her screen and the dial on
   Today, every zone on Climate, the room cards and the Routines page all move.
2. **Usage × the tariff window → money.** `costOf` is the only place a rate is
   applied. Change a rate in Settings → Tariff and all fourteen days re-price.
3. **Entries + the alarm + darkness → whether the house is shut.**
   `ENTRY_FLAGS.insecure` scores a door or window against the alarm state and the
   dark hour; the row, the section count, the verdict sentence and the nav badge all
   read it. Move the dark hour in Settings and the count changes.
4. **A machine's hours → a job falling due.** A consumable's due state is read off
   its device's `runtimeHours`, so the ventilation filters are overdue without anybody
   writing on the job. Marking the job done resets the machine's hours, which is what
   actually clears it.
5. **Tariff + charge → what the battery should do.** `batteryAdvice` is a
   recommendation, never a stored mode.
6. **One radio reading → weak, behind, stale.** Each device has one link record —
   dBm, time to answer, firmware against latest, when it was last asked. `signalBand`
   turns the reading into one of three bands, and `LINK_FLAGS` derives the rest, so a
   device becomes a problem the moment the reading changes with nothing written on it.
   Run the test and the verdict, the figures and the badge all move together.

## Act-ability

One predicate per kind of work, read at every altitude: `DEVICE_FLAGS.needsYou`,
`ENTRY_FLAGS.insecure`, `JOB_FLAGS.needsYou`, `BILL_FLAGS.overdue`,
`REQUEST_FLAGS.waiting`, `ROUTINE_FLAGS.needsYou`, `CAMERA_FLAGS.blind` — each one is
the nav badge, the tab count, the section heading and the row.

The camera badge counts `blind`, not "not watching": a shutter somebody closed on
purpose is not work owed to anybody, and a badge that counted it would be asking to be
dismissed.

`ROUTINE_FLAGS.broken` is the one worth pointing at: it asks whether the devices a
routine names are still in the house. Switch a device out and its routines break with
nothing written on them, and the badge moves by itself.

## Commit models

Instant everywhere except `/people/:id`, `/account/profile` and the five settings
screens, which are staged. Consequential instant moves raise an undo toast.
Irreversible actions confirm: removing a device, removing somebody from the
household, revoking access, cancelling a job, deleting a routine, calling the
response company. Four actions need two decisions and open a staged dialog from an
instant screen: pairing a device, booking or quoting a job, finishing or fixing a
routine, denying a request.

Every commit lives in `HouseCommits.jsx` behind `makeHouseCommits(ctx)`, so `App.jsx`
is the router and the commit index.

## Settings bite

- **Residence** — the setbacks and the low-battery threshold. Written into
  `HOUSE_POLICY`, which `enums.js` reads at render, so saving moves every zone.
- **Tariff and meters** — three rates and two windows, with the day strip drawn from
  the form as you type. Saving re-prices Energy and moves the battery's advice.
- **Alarm and response** — the dark hour (which is what makes an unlocked door
  insecure) and the order Sentinel rings the household in, reorderable.
- **Notifications** — the event matrix, quiet hours, and who hears what read from
  each person's own preferences.
- **Data and privacy** — how long the log and the clips are kept, and whether the
  house records *which person* did something at all.

## The product's own files

`enums.js` — ten status fields with tone groupings, the classification lists, the
household policy, five role-capability maps, nine derived-flag blocks with the clock
injected, seven transition functions. `nav.js` — the route table and its projections.
`fixtures.js` — 7 people, 34 devices and one link record each, 1 hub, 3 devices heard
but not taken in, 4 cameras, 7 clips, 10 rooms, 5 zones, 10 ways in, 14 days of
electricity, 7 of water, 8 bills, 4 contracts, 10 jobs, 6 providers, 8 grants, 8
routines, 3 requests, 18 log entries. `metrics.js` — every figure any screen renders.
`lib/` — four domain-free copies, byte-identical with every other product's.

## Local components — reported, per §9

All layout or composition over existing primitives; none invents a colour, a radius
or a type size. In `HearthParts.jsx`: `Card`, `Band`, `Figures`, `Pairs`, `Rows` /
`Row`, `Bar`, `DayBars`, `DayStrip`, `Legend`, `PersonCell`, `Clock`, `Battery`,
`Signal`, `CameraView`, `VerdictLine`,
`Transitions`, `RowActions`, `DeviceControl`, `FilterBar`, `ExportMenu`,
`TableWrap`, `TableFoot`, `NoResults`, `ConfirmDialog`, `FormDialog`, `FormBar`,
`RouteNotBuilt`, `useSort` / `SortHead` / `sortRows`, and the pound/temperature/watt
formatters (currency is a product decision, so it does not live in `lib/`).
In `SettingsScreens.jsx`: `SettingsTabs`, `SettingsPage`, `useStaged`, `PhotoUpload`.

## Coverage

Values with no seeded fixture, each one click away: `alarm.triggered`, `alarm.fault`,
`zone.frost` has one (the annexe), `member.removed`, `request.denied`,
`routine.paused`. No screen claims a count for a value that has no fixture.

## Known gaps

- A window counts as insecure only when the house is armed home *and* it is on the
  ground floor, or when armed away. An open first-floor bathroom window after dark is
  therefore ventilation, not a breach — deliberate, and the reason it still appears on
  Climate as "heating with a window open".
- There is no video in a template, so the camera plate names what the camera is
  pointed at rather than faking a picture. It carries the overlay a real view would —
  live chip, clock, camera name — and the controls around it are real.
- Pagination: 34 devices and 18 log entries do not need it, so lists end in a stated
  count. A house with three hundred devices would.
