KettlewickCover found before the kettle boils
16 of 16 slots have something in them · 8 contrast pairs computed · 0 findings · 1 open · next: /design-review
1 · Start here

Cover found before the kettle boils

Everything on this page is read from the files under docs/design/ and docs/BRAND.md. Where a decision has been made it names the record; where none has, the slot is dashed. Nothing here is written by BOSS.

Current shape

  • Who it's for: owner-operators who do the Monday schedule by hand and would keep the spreadsheet forever.
  • What it promises: cover found before the kettle boils.
  • What it refuses: it will not rank carers, track carers, or text anyone after 8pm. — DEC-003
  • How it sounds: plain, not clever (costs us delight); calm, not urgent (costs us the "act now" conversion); the owner's words, not ours.
  • What it is NOT: agency software. A marketplace. A scheduling tool.
  • The name, and why: the kettle is the owner's Monday morning; the wick is the bit that catches. It means nothing to a stranger and everything to an owner.
nascentdocs/BRAND.md · rev. 2026-09-02

The anchor — the choices that get expensive to reverse

The brand anchor: kettle copper, brown-black, two radii, one shadow

chosen · DEC-004revisit 2026-12-01docs/decisions/DEC-004-brand-anchor.md
2 · People

Marta, and 1 more.

The full persona lives here, in the design space, because this is where it gets used; the playbook carries a snippet and a link. Each card says how much of it came from a real person — a card that is 100% synthetic is a guess with a name.

Marta — primary · docs/personas/marta.md

52, owns a nine-carer agency in a market town; runs it from the kitchen table before the school run, and from the school gate after it.

The day (context)

Sunday 8:40pm or Monday 7:40am: a text says "can't do tomorrow" and the phone tree starts — eight calls, an hour, a spreadsheet open on the laptop and a biro on the back of an envelope.

Jobs

  • keep every visit covered — a missed visit is a person without care, and a call from their daughter
  • keep the carers she has; she can't hire her way out of a bad Monday
  • keep the inspector happy without a second job of paperwork
  • know, by the time the kettle boils, that Monday is sorted

Pains

  • the cover call takes an hour and eight calls, and the eighth is the one who says yes
  • she's the only one who knows who can take what; if she's ill, nobody is covered
  • every scheduling tool she's tried wanted her to move the schedule into it — she won't
  • asking at night feels like begging, and she does it anyway

Values

  • trusts a tool that does one thing and stays out of the way
  • abandons anything that tracks her carers, ranks them, or pings them at night
  • the carer's word over the system's guess
  • the spreadsheet stays — it's hers, and it works
What we don't know yet: whether she'd pay before the first missed visit, or only after · whether her carers keep the texts, or delete them · what she does when nobody says yes — does she go herself? · whether "the right three" is a rule she'd let the app apply, or one she'd override every time
synthetic 40% · real 60%docs/personas/marta.md · 2026-05-20

Priya — secondary · docs/personas/priya.md

34, a carer with two agencies, a car, and a school run of her own; eleven years in care, four with Marta.

The day (context)

7:41am, between dropping her son and her first visit, a text asks if she can take Mrs H at 9; she's already dressed, and she answers with one thumb at a red light.

Jobs

  • earn a full week without a full week of phone calls
  • say yes to the visits she can do and no to the ones she can't, without a conversation about either
  • know by 8pm what tomorrow looks like, so she can plan the school run
  • keep the clients who know her — continuity is the part of the job she likes

Pains

  • being asked by three owners at once for the same hour, then blamed by two
  • being asked at 10pm, and lying awake deciding
  • a yes that turns out not to be needed — nobody told her someone else had it
  • a schedule that changes on paper she never sees

Values

  • a yes should be a yes and a no should be nothing — no reason owed, no guilt trip
  • her phone is hers: no tracking, no app that knows where she is
  • being asked by name, by a person, not pinged by a system
  • the client's routine over the agency's convenience
What we don't know yet: whether she'd install anything at all, or only answer a text · whether "the first yes fills it" feels fair or feels like a race · whether she'd want to see the other two names on the ask · everything above is Marta's picture of her carers; nobody has interviewed a carer yet
synthetic 100% · real 0%docs/personas/priya.md · 2026-05-22
3 · The journey

From hearing about it to relying on it — and the gaps between.

The tier above the flows: the stages a person travels, what they meet at each, and where they leave. Every row says where it came from — observed, said, or assumed — because a journey invented at a desk looks like research and isn't.

The stages — the gaps between flows are the point

#StageTrying to doMeetsServing flowWhere they leaveSource
1Hear about itstop dreading Mondayanother owner's mention at a training day; the landing page—the page reads like software, not like Mondaysaid
2Try itsee if it reads their sheetpaste the week; the check screenImport the weekthe parse gets a row wrong and they don't trust the restobserved
3First valuecover one visit without ringing roundtap Ask; the first yesFind cover for a visitnobody says yes in time and it feels like the phone tree with extra stepsobserved
4Come backnext Monday, without thinkingthe day view, empty or notFind cover for a visitthey covered it by phone on Sunday night and forgot the appassumed
5Rely on itnever ring round again; tell another ownerthe empty day; the pre-pay—a carer who won't use the app, so one visit is always by phonesaid
5 stages · 2 observed · 2 said · 1 assumeddocs/product/JOURNEY.md · rev. 2026-09-08

The gaps — stages with no flow and no FEAT that owns them

  • *Come back* has no flow and no FEAT — nothing brings an owner back on Monday except the dread; a Sunday-evening line ("3 visits uncovered tomorrow") is queued by the 8pm rule and needs PAT-4's *say when*
  • the carer who won't install anything — one owner in nine; no flow serves the phone-only carer
docs/product/JOURNEY.md · The gaps
4 · Principles

3, each a direction that contains a tradeoff.

A principle is grounded when something outside the room names it — an EVID, a persona, a stage of the journey. One that only asserts is still a principle; the chip says which is which so it can be tested instead of reaffirmed.

1 · Calm over urgent

Your Monday morning gets shorter, not louder.

Grounded in: EVID-001 and EVID-002 (three owners described the Monday cover call unprompted; one said the hour is the job she hates most); DEC-003 (nothing after 8pm); docs/personas/marta.md

Why

an owner opens Kettlewick when a visit is already uncovered. Urgency is the default state of the job; the app must not add to it. Giving up: the "act now" conversion that badges and red dots buy.

Guideline

one accent, on the one thing you can act on. Everything else is quiet.

Rules

no unread counts; no badges on navigation; no red except the confirm on a destructive dialog; amber for *uncovered*, a settled green for *covered*, the copper only on the act; nothing sent after 8pm, and the screen says when it will go.

Wrong if

an owner misses an uncovered visit that a badge would have caught. Then calm cost a visit, and the rule narrows.

grounded · EVID-001, EVID-002, docs/personas/marta4 rules descenddocs/design/STYLE_GUIDE.md · Design principles · 1

2 · The row is the unit

Everything you need about a visit is on its row; you never open anything to know its state.

Grounded in: EVID-003 (an owner covered a Monday visit from the school gate — one thumb, one row); docs/personas/marta.md; journey stage 3

Why

owners scan; they do not browse. A visit's state, who has the ask, and the one act belong on one line. Giving up: detail views, and the room they would give a designer.

Guideline

design the row first; the page is rows.

Rules

a visit's state is a chip on its row; the act is a ghost button on its row; a row never links to a detail page.

Wrong if

owners keep asking "where do I see the note?" — the row is too thin, and a second line earns its place.

grounded · EVID-003, docs/personas/marta, journey stage 33 rules descenddocs/design/STYLE_GUIDE.md · Design principles · 2

3 · Ask, don't assign

Kettlewick never puts a carer on a visit; it helps you ask the right three, and the first yes fills it.

Grounded in: EVID-002 (the owner's own words for what she does now: "I ring round"); DEC-003 (no league table — the three are not ranked, they are the three who could); docs/personas/priya.md; journey stage 4

Why

the relationship between owner and carer is the product's whole moat; automation that bypasses it wins the Monday and loses the carer. Giving up: the "smart fill" every rival ships.

Guideline

every act is an ask with a name on it, and a person answers it.

Rules

no auto-assign; the primary button on an uncovered visit is *Ask*, and it names who; a carer's *yes* is one tap, never a form; the owner confirms, always.

Wrong if

owners widen the ask to everyone every time — then asking is broadcasting, and the *three* needs a reason on the screen.

grounded · EVID-002, docs/personas/priya, journey stage 42 rules descenddocs/design/STYLE_GUIDE.md · Design principles · 3
5 · Colour

16 semantic names over 4 primitives.

Every swatch is its semantic name — what it is for, never its hue. Click a swatch, a name or a value to copy it in the form you want. A swatch with a reason under it is an argument someone has to answer; a swatch alone is a value someone will quietly change.

Semantic tokens · click any value to copy it

color.surface.ground#F3ECE1chosen · DEC-004
color.surface.paper#FFFBF4a bounded thing on the ground: a card, a row, a dialog
color.surface.raised#FFFFFFpaper with shadow.raised — used only by things that float
color.text.body#2A1E17chosen · DEC-004
color.text.muted#665247secondary text; still AA on ground and paper
color.text.on-primary#FFF7F0text on color.action.primary
color.text.on-danger#FFFFFFderived · no reason recorded
color.text.placeholder#9AA5A1retired 2026-08-30: failed AA on paper; use muted · deprecated → color.text.muted
color.action.primary#B84E12chosen · DEC-004
color.action.primary-hover#A74813primary mixed 12% with text.body
color.action.danger#A33A2Ethe confirm on a destructive dialog, nowhere else
color.signal.uncovered#955A12a visit nobody has — amber, never red (principle 1)
color.signal.asked#665247three carers have the ask; waiting is quiet
color.signal.covered#2E6B4Fdone — a settled green, not the accent: covered is finished, and the accent is for the act still to take
color.border.default#D9CCBBderived · no reason recorded
color.border.subtle#E8DFD2derived · no reason recorded

4 primitives underneath (color.<hue>.<step>) — referenced by these names, never by a screen.

3 chosen13 deriveddocs/design/tokens.json

Contrast — WCAG 2.x, computed for every declared text-on-surface pair

TextOnRatioReads
color.text.bodycolor.surface.ground13.80AA
color.text.bodycolor.surface.paper15.70AA
color.text.bodycolor.surface.raised16.20AA
color.text.mutedcolor.surface.ground6.25AA
color.text.mutedcolor.surface.paper7.11AA
color.text.mutedcolor.surface.raised7.34AA
color.text.on-primarycolor.action.primary4.79AA
color.text.on-dangercolor.action.danger6.56AA

Checks the pairs the tokens declare, not what the page renders — text over an image, a gradient or a translucent overlay composites at runtime and stays not checked. A pair that fails is a token-system finding: fix it in docs/design/tokens.json and every screen moves.

0 findings · token-level8 pairs · computed at render · relative luminance, WCAG 2.x
6 · Type

8 type tokens.

Roles, not sizes. A screen that needs a seventh role is asking a question the style guide should answer first.

Roles · click a role to copy its token or value

font.displayNewsreader, Georgia, serifthe promise — headings and the wordmark
The quick brown fox covers the Sunday shift.
font.bodyPublic Sans, Helvetica Neue, Arial, sans-serifthe work
The quick brown fox covers the Sunday shift.
font.monoJetBrains Mono, Menlo, monospacetimes and counts
The quick brown fox covers the Sunday shift.
type.size.display28px
type.size.heading20px
type.size.body15px
type.size.small13px
type.size.label11px
docs/design/tokens.json · font family
7 · Space & shape

6 spacing steps · 2 radii · 1 elevation.

Spacing

space.1 · 4px
space.2 · 8px
space.3 · 12px
space.4 · 16px
space.6 · 24px
space.8 · 32px
docs/design/tokens.json · space

Radius

6px · control
8px · surface
docs/design/tokens.json · radius

Elevation

raised
docs/design/tokens.json · elevation

Motion

No durations, no easings named. prefers-reduced-motion is honoured by default.wakes when a transition is designed twice · seed-that-scales: cheap to add when earned
deferred by ruleno motion tokens
8 · Icons & logo

6 icons from files · the mark from its file.

An icon set is a dependency and a logo is the one drawing everything else defers to. Both render from files or not at all: the page copies what exists as SVG and says what is missing, and never draws a stand-in that would quietly become the real thing.

The set · 6 icons · drawn from the files · click one to copy its SVG · SVG on this block copies the set as one sprite

ask24px · 234 B
asked24px · 278 B
clock24px · 239 B
covered24px · 276 B
kettle24px · 265 B
uncovered24px · 216 B

The sprite is one <svg> of <symbol id="icon-<name>"> — the honest download: paste it once into a design tool or a page and reference each icon by id.

6 filesdocs/design/icons/*.svg

The decision — the set is a dependency; decide it before the second icon

  • The set: our own, drawn to match the mark — 1.5px stroke, 24px grid, round caps; six today (docs/design/icons/)
  • Sizes that exist: 16 (inline with text), 20 (in a button), 24 (alone)
  • Icon-only is allowed when: it sits in a row beside its label, or on the phone toolbar with an accessible name
  • What an icon never does: carry a visit's state — that is the chip's job, and colour-plus-shape is the floor
4 of 4 decideddocs/design/STYLE_GUIDE.md · Icons
9 · Layout

A layout section exists.

The six sub-slots · 6 of 6 decided

  • Base unit: 4
  • The ramp: inside a control `space.2` · between controls `space.3` · between sections `space.6`
  • Grid anatomy: the laptop is one column of rows at 720px max with a 240px rail on the left; the phone is one column edge to edge with `space.4` margins; the day view is one column of visits in time order
  • Breakpoints: `breakpoint.phone` 480 · `breakpoint.laptop` 960 — two, because there are two surfaces that change shape
  • Responsive techniques: reflow (the visit row stacks its who under its when on the phone) · resize (the button fills the width on the phone) · keep (the chip never changes) · re-architect (the rail becomes the phone's bottom bar); hide is not allowed — nothing an owner needs is hidden on the phone
  • Density: one
surfacemustmaynever
Laptop (the owner, Monday morning)the day in one view; the raila second column for the notea modal that hides the day
Phone (the carer, between visits; the owner at the school gate)one act per screen; 44px targets (target.min); yes / no fill the widththe day as a lista hover-only affordance
Paper (one owner in eight prints the Monday)the three chip shapes in one colour; the day in time ordernothing elsea colour that carries meaning

breakpoint.* tokens: breakpoint.phone 480px · breakpoint.laptop 960px. target.min 44px.

6 of 6docs/design/STYLE_GUIDE.md · Layout
10 · Components

13 exist, 2 retired · 13 with a usage page · 1 asked for.

The index is the agent's reuse list — does something like this already exist? — and the founder's inventory. Reuse first, extend second, create last. Build the button, then use it on the page; don't build the page and leave the button inside it.

Asked for · 1 — a usage page written before the code is a request

  • QuietNotice — new — a line on the owner's day view after 8pm saying when the queued asks will go out ("3 asks go out at 7am"). No existing component says *when* something will happen; the disabled AskCard says it to the carer, and the owner has nothing. docs/design/components/QuietNotice.md

The next /design-review answers each: reuse, adjust, or new. A request that names a behaviour no existing part has is the only kind that becomes a file.

1 proposeddocs/design/components/*.md · status: proposed

Index — 13 components · 0 findings · authored · rev. 2026-09-09

ComponentPurposeVariantsUsed inStatusFindings
Buttonthe one act on a screenprimary · secondary · ghost · danger10·stable—
VisitRowone visit, its state, who has the ask, the act—1·stable—
StatusChipa visit's state, shape firstuncovered · asked · covered6·stable—
AskCardthe carer's yes / no, one thumb—1·stable—
EmptyStatewhat a day shows when nothing is uncovered—3·draft—
ConfirmDialogthe one destructive question — cancel a visit, remove a carer—2·stable—
Toastthe result of an act, said once and gonesuccess · error2·stable—
Railthe three places: Today · Carers · Settings — the laptop's rail, the phone's bottom bar—4·stable—
TextFieldone thing typed: a carer's name, a phone number, the pasted sheetsingle · multiline3·stable—
CarerRowone carer: name, phone, the days they usually can—1·stable—
ImportRowone parsed line of the pasted sheet, on the check screenread · couldn't-read1·stable—
RowSkeletonthe shape of a row while the day loads—1·stable—
PrintSheetthe printed Monday — one colour, the three shapes, time order—0·draft—
CoverButtonwas Button variant="primary" with the label "Ask" all along — merged · 2026-09-010retired—
Cardevery use was a VisitRow; the surface tokens do the rest · 2026-09-030retired—

One word per concept, in props too: the visual variation → variant (never type, kind, style) · the primary action handler → onAsk (never onClick, onPress) · the off state → disabled (never isDisabled, inactive) · the loading state → loading (never isLoading, pending)

Definition of done

earned per component, never asserted — mechanical where something on disk can answer, not checked where it can't

Not checked, for every component: reads to 320px · a screen reader announces it · every string is in the copy layer · a person has used it. Nothing on disk answers those; a rendered page and a person do. /design-review after says the same not checked rather than pass.

Componenton tokensfive statessource fresh
Buttonnot checked — the guard checks the write, the V1 manifest checks the tree✓authored — no hash
VisitRownot checked — the guard checks the write, the V1 manifest checks the tree✓authored — no hash
StatusChipnot checked — the guard checks the write, the V1 manifest checks the tree✓authored — no hash
AskCardnot checked — the guard checks the write, the V1 manifest checks the tree✓authored — no hash
EmptyStatenot checked — the guard checks the write, the V1 manifest checks the tree✓authored — no hash
ConfirmDialognot checked — the guard checks the write, the V1 manifest checks the tree✓authored — no hash
Toastnot checked — the guard checks the write, the V1 manifest checks the tree✓authored — no hash
Railnot checked — the guard checks the write, the V1 manifest checks the tree✓authored — no hash
TextFieldnot checked — the guard checks the write, the V1 manifest checks the tree✓authored — no hash
CarerRownot checked — the guard checks the write, the V1 manifest checks the tree✓authored — no hash
ImportRownot checked — the guard checks the write, the V1 manifest checks the tree✓authored — no hash
RowSkeletonnot checked — the guard checks the write, the V1 manifest checks the tree✓authored — no hash
PrintSheetnot checked — the guard checks the write, the V1 manifest checks the tree✓authored — no hash
0 findingsdocs/design/COMPONENTS.md · the V1 manifest adds a source hash and a usage count

Button · src/components/Button.tsx

the one act on a screen

Why it exists: new — the one act a screen exists for; nothing else commits the owner or the carer to anything.

When it applies

  • the one act a screen exists for — *Ask*, *Confirm Priya*, *Keep it*
  • the yes / no pair on the carer's ask card (two secondarys that fill the width, primary below)

When it doesn't

  • navigation — that is a link, and it is never in the accent
  • a state change that isn't an act — the chip is not a button
  • anything that appears on every row — that is ghost, and only one primary per view
VariantUse it forNever for
primarythe acta second act on the same view
secondarythe way out — *Not now*, *Keep it*a decoy that looks like a second primary
ghostan act on a row — *Ask*the only control on a screen
dangerthe confirm on a destructive dialoganywhere else; the label repeats the consequence

Content

  • verb first, two or three words, sentence case; no ellipsis unless a dialog follows
  • loading: the label becomes the participle and the button stays put — "Asking…"

Layout

  • last on the right in a dialog; first on a row; never two primaries in one line
  • on the phone the yes / no pair fills the width, primary below; 44px minimum (target.min)

Accessibility

  • the label is the accessible name — never an icon alone
  • focus: 2px color.action.primary outline, 2px offset
  • *not checked:* the loading state's announcement to a screen reader

Research

  • EVID-003 — an owner covered a visit from the school gate with one thumb; the ghost *Ask* on the row is why she could

8 of 8 written docs/design/components/Button.md

import { Button } from '@/components/Button'

Variants: primary · secondary · ghost · danger · Status: stable · used in 10

SVG copies the frame above — the name, the variants, the five states — in your tokens, as editable layers. It is a spec frame, not a render; nothing here runs the code. Code copies the import line or the source file.

stableresearch · EVID-003docs/design/COMPONENTS.md

VisitRow · src/components/VisitRow.tsx

one visit, its state, who has the ask, the act

Why it exists: new — the unit of the product: one visit, its state as a shape, who has the ask, and the one act, on one line. No other component holds a visit.

When it applies

  • every visit in the day view, on the laptop and the phone
  • the printed Monday (one colour, the same three shapes)

When it doesn't

  • the carer's side — a carer sees an AskCard, never a row of someone else's day
  • a summary count — that is text, not a row
VariantUse it forNever for
(none)one shape, three states via the chipa "compact" row — density is one

Content

  • time first, in mono; then the client's first name only; then the chip; then who has the ask; then the act
  • the note, if there is one, is a second line and never a link

Layout

  • laptop: one line; phone: the who stacks under the when (reflow), the act keeps its 44px
  • space.2 between rows; border.subtle under each; no card

Accessibility

  • the row is a list item; the chip's shape has a name; the act is the only control
  • *not checked:* reading order on the phone after reflow

Research

  • EVID-003 — watched an owner read the day row by row with a thumb; she never opened anything

8 of 8 written docs/design/components/VisitRow.md

import { VisitRow } from '@/components/VisitRow'

Status: stable · used in 1

SVG copies the frame above — the name, the variants, the five states — in your tokens, as editable layers. It is a spec frame, not a render; nothing here runs the code. Code copies the import line or the source file.

stableresearch · EVID-003docs/design/COMPONENTS.md

StatusChip · src/components/StatusChip.tsx

a visit's state, shape first

Why it exists: new — the signature: a visit's state as a shape before a colour, so it reads on paper, in one colour, and to anyone who cannot see amber.

When it applies

  • on every VisitRow; on the carer's AskCard once answered

When it doesn't

  • navigation — never as a badge, never a count
  • anything that is not a visit's state
VariantUse it forNever for
uncovereda hollow ring, signal.uncoveredred
askeda half-filled ring, signal.askeda spinner
covereda filled disc, signal.covered — the settled green, never the coppera tick — the disc is the tick

Content

  • the word beside the shape on the laptop; the shape alone on the phone row, with its accessible name

Layout

  • 16px on a row, 20px on the ask card; never scaled

Accessibility

  • shape carries meaning; colour is the second channel; the name is the third
  • *not checked:* the half-filled ring at 16px for low vision — a review with a person

Research

  • EVID-001 — owners described "ringing round" as a state they hold in their head; the chip is that state, outside the head

8 of 8 written docs/design/components/StatusChip.md

import { StatusChip } from '@/components/StatusChip'

Variants: uncovered · asked · covered · Status: stable · used in 6

SVG copies the frame above — the name, the variants, the five states — in your tokens, as editable layers. It is a spec frame, not a render; nothing here runs the code. Code copies the import line or the source file.

stableresearch · EVID-001docs/design/COMPONENTS.md

AskCard · src/components/AskCard.tsx

the carer's yes / no, one thumb

Why it exists: new — the carer's whole product: one visit, one question, yes or no with one thumb, between visits. Nothing else is the carer's.

When it applies

  • the carer's phone, when an ask arrives (7am–8pm only — DEC-003)
  • the carer's phone, after answering — the same card says who covered it

When it doesn't

  • the owner's side — the owner confirms on the VisitRow, never on a card
  • more than one visit — one card per ask, never a list to triage
VariantUse it forNever for
(none)one card; its states are the fivea "compact" version

Content

  • the time and the client's first name; who is asking; *Yes* / *Not this one* — never "Accept" / "Decline"
  • after 8pm the card is disabled and says "This ask goes out at 7am" (the disabled state says why)

Layout

  • fills the phone width; yes / no fill the width side by side, 44px each; nothing above the fold but the visit and the two buttons

Accessibility

  • the two buttons are the only controls; the card is announced as one region
  • *not checked:* the answered state's announcement

Research

  • EVID-002 — the owner's word for the ask today is "I ring round"; the card is what the ring becomes

8 of 8 written docs/design/components/AskCard.md

import { AskCard } from '@/components/AskCard'

Status: stable · used in 1

SVG copies the frame above — the name, the variants, the five states — in your tokens, as editable layers. It is a spec frame, not a render; nothing here runs the code. Code copies the import line or the source file.

stableresearch · EVID-002docs/design/COMPONENTS.md

EmptyState · src/components/EmptyState.tsx

what a day shows when nothing is uncovered

Why it exists: new — what a day shows when nothing is uncovered: the product's best case, which is also the one owners see least and trust least.

When it applies

  • the day view when no visit is uncovered
  • the carer's inbox when there is no ask

When it doesn't

  • loading — that is the skeleton of a row, not this
  • an error — that is copy on the row that failed
VariantUse it forNever for
(none)one message, one action at mostan illustration with no action

Content

  • "Nothing uncovered today." — and no action, because there is nothing to do; the carer's reads "No asks. Enjoy the gap."

Layout

  • centred in the list's space; the same margins as a row

Accessibility

  • plain text; nothing to operate

Research

  • EVID-004 — the owner who pre-paid said the empty day was "the first time it felt finished"; draft until a second owner says so

8 of 8 written docs/design/components/EmptyState.md

import { EmptyState } from '@/components/EmptyState'

Status: draft · used in 3

SVG copies the frame above — the name, the variants, the five states — in your tokens, as editable layers. It is a spec frame, not a render; nothing here runs the code. Code copies the import line or the source file.

draftresearch · EVID-004docs/design/COMPONENTS.md

ConfirmDialog · src/components/ConfirmDialog.tsx

the one destructive question — cancel a visit, remove a carer

Why it exists: new — the one place a destructive act is confirmed; no other component asks a question and closes.

When it applies

  • cancelling a visit; removing a carer — the two acts that tell someone something they can't un-hear

When it doesn't

  • anything reversible — an ask can be withdrawn from the row, no dialog
  • anything the owner works *in* — that is a page, never a modal
VariantUse it forNever for
(none)one questiona dialog with tabs, a form, or a second dialog

Content

  • the title is the consequence: "Cancel the 9 o'clock. Priya will be told."; the danger button repeats it; the way out is *Keep it*

Layout

  • surface.raised with shadow.raised — the only thing that floats (PAT-5); centred, 360px, the two buttons in a row with the danger last

Accessibility

  • focus moves in and returns to the row's control on close (overlays prompt); Esc keeps it
  • *not checked:* the screen reader's announcement of the title

Research

  • EVID-002 — the owner's fear of "telling the wrong person the wrong thing" is why the consequence is the title

8 of 8 written docs/design/components/ConfirmDialog.md

import { ConfirmDialog } from '@/components/ConfirmDialog'

Status: stable · used in 2

SVG copies the frame above — the name, the variants, the five states — in your tokens, as editable layers. It is a spec frame, not a render; nothing here runs the code. Code copies the import line or the source file.

stabledocs/design/COMPONENTS.md

Toast · src/components/Toast.tsx

the result of an act, said once and gone

Why it exists: new — the result of an act said once and gone; the only component that speaks without being on a row.

When it applies

  • after a confirm — "Covered. Priya's on the 9 o'clock."
  • after an ask fails to send — "Couldn't reach anyone — try again, or ask three more."

When it doesn't

  • a standing fact ("3 asks go out at 7am") — that is the proposed QuietNotice, not a toast
  • anything with a question in it — a toast never asks
VariantUse it forNever for
successthe act workeda count or a celebration
errorthe act didn't, and what to do nexta stack trace

Content

  • one sentence from the tone table; no exclamation marks; an *Undo* only when the act is reversible

Layout

  • bottom of the phone, above the bar; bottom-left on the laptop; 4s, paused on hover; z-index.toast

Accessibility

  • role="status" — announced without stealing focus; the error variant stays until dismissed
  • *not checked:* the timing for a slow reader

Research

  • EVID-003 — the owner covered a visit from the school gate and "didn't wait to see"; the toast is what she'd have seen

8 of 8 written docs/design/components/Toast.md

import { Toast } from '@/components/Toast'

Variants: success · error · Status: stable · used in 2

SVG copies the frame above — the name, the variants, the five states — in your tokens, as editable layers. It is a spec frame, not a render; nothing here runs the code. Code copies the import line or the source file.

stabledocs/design/COMPONENTS.md

Rail · src/components/Rail.tsx

the three places: Today · Carers · Settings — the laptop's rail, the phone's bottom bar

Why it exists: new — the three places, in one component that is a rail on the laptop and a bottom bar on the phone; nothing else navigates.

When it applies

  • every owner screen: Today · Carers · Settings

When it doesn't

  • the carer's side — the ask card is the carer's whole product; no rail
  • the printed Monday
VariantUse it forNever for
(none)one component, two surfaces by re-architectinga hamburger; a fourth place

Content

  • the three words, never icons alone; the current place is marked by weight, not the accent

Layout

  • laptop: 240px left rail; phone: a bottom bar with three 44px targets (target.min); the re-architect technique from the Layout slot

Accessibility

  • nav with an accessible name; the current place has aria-current
  • *not checked:* the bottom bar under a phone's home indicator

Research

  • EVID-001 — owners named three things they do: today's cover, "who I've got", and "the settings I never touch"; the rail is those three

8 of 8 written docs/design/components/Rail.md

import { Rail } from '@/components/Rail'

Status: stable · used in 4

SVG copies the frame above — the name, the variants, the five states — in your tokens, as editable layers. It is a spec frame, not a render; nothing here runs the code. Code copies the import line or the source file.

stabledocs/design/COMPONENTS.md

TextField · src/components/TextField.tsx

one thing typed: a carer's name, a phone number, the pasted sheet

Why it exists: new — one thing typed; the only input in the product, in two heights.

When it applies

  • a carer's name and phone; the pasted week (multiline); the visit note

When it doesn't

  • a choice — there are none to make (no picker, no select: principle 3)
  • a search — the day is short enough to read
VariantUse it forNever for
singlea name, a numbera paragraph
multilinethe pasted sheet, the notea name

Content

  • a visible label that stays; the placeholder is an example ("07700 900123"), never the label; the error sits under the field and names the fix

Layout

  • full width on the phone; 360px on the laptop; label above; space.2 inside

Accessibility

  • label bound to the field; the error is announced; 44px on the phone
  • *not checked:* autofill behaviour on the phone number field

Research

  • EVID-003 — an owner pasted her whole sheet into the first field she saw; the multiline variant is that field

8 of 8 written docs/design/components/TextField.md

import { TextField } from '@/components/TextField'

Variants: single · multiline · Status: stable · used in 3

SVG copies the frame above — the name, the variants, the five states — in your tokens, as editable layers. It is a spec frame, not a render; nothing here runs the code. Code copies the import line or the source file.

stabledocs/design/COMPONENTS.md

CarerRow · src/components/CarerRow.tsx

one carer: name, phone, the days they usually can

Why it exists: adjust — widened from VisitRow's shape for a different job: one carer, not one visit. Same rhythm, no chip, no act on the row.

When it applies

  • the Carers list; the three names on an *asked* visit

When it doesn't

  • ranking — there is no order but the alphabet (DEC-003)
  • a profile page — there is none; the row is all there is
VariantUse it forNever for
(none)one shapea "compact" list

Content

  • name, phone, the days they usually can — in the owner's words ("most mornings"), never a percentage

Layout

  • the same rhythm as VisitRow: space.2 between rows, border.subtle under each

Accessibility

  • a list item; the phone number is a link on the phone
  • *not checked:* the reading order of "the days they usually can"

Research

  • EVID-002 — the owner keeps carers "in her head, not in a system"; the row is deliberately as thin as her head's version

8 of 8 written docs/design/components/CarerRow.md

import { CarerRow } from '@/components/CarerRow'

Status: stable · used in 1

SVG copies the frame above — the name, the variants, the five states — in your tokens, as editable layers. It is a spec frame, not a render; nothing here runs the code. Code copies the import line or the source file.

stabledocs/design/COMPONENTS.md

ImportRow · src/components/ImportRow.tsx

one parsed line of the pasted sheet, on the check screen

Why it exists: adjust — a VisitRow before it is a visit: the parsed line, as read, so the owner checks it before it becomes the day.

When it applies

  • the check screen after a paste — every line of the sheet, read or not

When it doesn't

  • the day view — once confirmed, it is a VisitRow
VariantUse it forNever for
readthe parse is confident: time · client · carerhiding what it guessed
couldn't-readthe line as typed, markeddropping the line

Content

  • read: the three parts in the row's order; couldn't-read: the raw text and "couldn't read this one — the rest will import"

Layout

  • the same rhythm as VisitRow; couldn't-read rows carry an amber left rule, never red

Accessibility

  • the state is in the text, not only the rule colour
  • *not checked:* forty rows on the phone

Research

  • EVID-003 — the parse got a row wrong in front of the owner; the check screen exists because she didn't trust the rest until she saw it

8 of 8 written docs/design/components/ImportRow.md

import { ImportRow } from '@/components/ImportRow'

Variants: read · couldn't-read · Status: stable · used in 1

SVG copies the frame above — the name, the variants, the five states — in your tokens, as editable layers. It is a spec frame, not a render; nothing here runs the code. Code copies the import line or the source file.

stabledocs/design/COMPONENTS.md

RowSkeleton · src/components/RowSkeleton.tsx

the shape of a row while the day loads

Why it exists: new — the shape of a row while the day loads; the product has no spinner anywhere.

When it applies

  • the day view and the carers list, for the first second

When it doesn't

  • anything past a few seconds — that is a sentence ("Still loading the day…"), not a shape
VariantUse it forNever for
(none)the row's exact shapea generic grey block

Content

  • none; three rows of the row's shape

Layout

  • identical box to VisitRow so nothing jumps when rows arrive (PAT-9)

Accessibility

  • aria-busy on the list; the skeleton is hidden from the reader; prefers-reduced-motion stops the shimmer
  • *not checked:* nothing

Research

  • (none — a craft floor, not a finding; retire the row if a person ever mentions it)

8 of 8 written docs/design/components/RowSkeleton.md

import { RowSkeleton } from '@/components/RowSkeleton'

Status: stable · used in 1

SVG copies the frame above — the name, the variants, the five states — in your tokens, as editable layers. It is a spec frame, not a render; nothing here runs the code. Code copies the import line or the source file.

stabledocs/design/COMPONENTS.md

PrintSheet · src/components/PrintSheet.tsx

the printed Monday — one colour, the three shapes, time order

Why it exists: new — the printed Monday for the one owner in eight who works from paper; the only surface with no interaction.

When it applies

  • print, from the day view; one page, time order, one colour

When it doesn't

  • the screen — it is never rendered on screen except as the print preview
VariantUse it forNever for
(none)one sheeta colour version

Content

  • the day's visits with the three chip shapes; the two acts (*ring* and *cover by*) at equal weight — the exception of 2026-08-28, kept

Layout

  • A4 and Letter; 6mm margins; the mark at 6mm minimum; a raw hex for the rule colour — the exception of 2026-09-08, until the print family has tokens

Accessibility

  • one colour, shape carries state; 11pt minimum
  • *not checked:* a monochrome laser at draft quality

Research

  • EVID-001 — one of the three owners prints; draft until a second does

8 of 8 written docs/design/components/PrintSheet.md

import { PrintSheet } from '@/components/PrintSheet'

Status: draft · used in 0

SVG copies the frame above — the name, the variants, the five states — in your tokens, as editable layers. It is a spec frame, not a render; nothing here runs the code. Code copies the import line or the source file.

draftdocs/design/COMPONENTS.md
11 · Patterns

9 decisions of the product's own · 7 families in use · 3 refused.

A pattern is a recurring decision with a rule attached — one level above a component, one below a flow. Only the product's own decisions are decisions here; what BOSS seeded is a prompt, shown only under a family the product actually uses. The rule and the anti-pattern sit side by side because a rule you can see is one you stop arguing about.

Ours — the decisions this product made · 9

IDPatternDoDon'tFirst seen
PAT-1Shape before colour
a visit's state anywhere — row, card, paper
descends from · 1 · Calm over urgent
a hollow ring, a half-filled ring, a filled disc; colour is the second channela coloured dot; a tick icon2026-08-26
PAT-2The act on the row
a visit needs cover; the owner is scanning
descends from · 2 · The row is the unit
the ghost *Ask* sits on the row; it names who will be asked; the row never opens a pagea detail page with the act at the bottom2026-09-02
PAT-3Ask three, first yes fills
an uncovered visit; the owner taps Ask
descends from · 3 · Ask, don't assign research · EVID-002
the three who could — no ranking, no league table (DEC-003); the first yes fills; the owner confirmsbroadcast to everyone; auto-assign; a "best match" score2026-09-02
PAT-4Say when, after 8pm
anything queued by the 8pm rule (DEC-003)
descends from · 1 · Calm over urgent
the screen says the time it will go — "goes out at 7am" — on the carer's card and the owner's daya silent queue; a disabled control with no reason2026-09-08
PAT-6One question, then it closes
any destructive act — cancel a visit, remove a carer
descends from · 1 · Calm over urgent
the ConfirmDialog asks one question with the consequence in its title, the danger button repeats the consequence, and it closes; the day is never hidden behind two dialogs"Are you sure?"; a dialog with a form in it2026-09-03
PAT-7Three places, one rail
any screen — the owner is always in Today, Carers or Settings
descends from · 2 · The row is the unit
the Rail is the laptop's left rail and the phone's bottom bar, the same three places in the same order; the current one is marked; nothing else navigatesa hamburger menu; a back button that goes somewhere else2026-08-30
PAT-8Paste, don't map
the owner brings the week from her spreadsheet
descends from · 3 · Ask, don't assign
one multiline TextField, paste as-is; the parse guesses the columns and the check screen catches it — never a column-mapping forma wizard that asks which column is the time2026-09-06
PAT-9The shape while it loads
the day or the carers list is loading
descends from · 1 · Calm over urgent
RowSkeleton in the row's exact shape; never a spinner; the page does not jump when rows arrivea centred spinner2026-09-04
PAT-5Rows sit, the card floats
any surface that holds a visit
descends from · 2 · The row is the unit
a row is paper on ground with a bottom rule; only the confirm dialog and the carer's card take shadow.raised — border for things that sit, shadow for things that floata card inside a card; a shadow on every row2026-09-05

A review finding can say violates PAT-n instead of re-arguing the rule. These are the ones worth showing a designer — and the ones the agent is handed at the write.

9 decideddocs/design/PATTERNS.md · Ours

Do / Don't — the rule rung, from the style guide · 4

DoDon'tBecause
one primary action per viewtwo buttons competing for the same weighta second primary means the view has two jobs
amber for an uncovered visitred for anything but the destructive confirmred is a siren, and Monday is loud enough (principle 1)
the act on the row — *Ask*a detail page with the act at the bottomthe row is the unit (principle 2)
disabled controls say why — "goes out at 7am"a greyed-out button with no explanationthe owner can't act on a dead end she can't diagnose

The only rung an agent can act on. A principle above that never produced a row here isn't steering anything yet.

4 pairsdocs/design/STYLE_GUIDE.md · Do / Don't

Inputs · in use: TextField · 1 decided

IDPatternDoDon'tFirst seen
PAT-8Paste, don't map
the owner brings the week from her spreadsheet
descends from · 3 · Ask, don't assign
one multiline TextField, paste as-is; the parse guesses the columns and the check screen catches it — never a column-mapping forma wizard that asks which column is the time2026-09-06
2 prompts BOSS seeded for this family — questions, not decisions
PromptDoDon't
Label, not placeholder
any field
a visible label that stays visibleplaceholder-as-label — it vanishes on focus
Never lose what they typed
a submit that fails
the values survive the round tripa cleared form after a server error

A prompt becomes the product's rule only when it is adopted into Ours with a PAT-n.

1 decideddocs/design/PATTERNS.md · 2 seeded

Data display · in use: VisitRow · CarerRow · ImportRow · RowSkeleton · 1 decided

IDPatternDoDon'tFirst seen
PAT-2The act on the row
a visit needs cover; the owner is scanning
descends from · 2 · The row is the unit
the ghost *Ask* sits on the row; it names who will be asked; the row never opens a pagea detail page with the act at the bottom2026-09-02
2 prompts BOSS seeded for this family — questions, not decisions
PromptDoDon't
Zero / one / many / far too many
any list of visits
all four are designed, not just "many"a day view that's beautiful at 6 visits and unusable at 40
One sort by default, and say which
the day view
time order, statedan order the owner has to reverse-engineer

A prompt becomes the product's rule only when it is adopted into Ours with a PAT-n.

1 decideddocs/design/PATTERNS.md · 2 seeded

Waiting · in use: RowSkeleton · 1 decided

IDPatternDoDon'tFirst seen
PAT-9The shape while it loads
the day or the carers list is loading
descends from · 1 · Calm over urgent
RowSkeleton in the row's exact shape; never a spinner; the page does not jump when rows arrivea centred spinner2026-09-04
2 prompts BOSS seeded for this family — questions, not decisions
PromptDoDon't
Skeleton over spinner
you know the shape of what's coming
show the shape; the page stops jumping when it arrivesa centred spinner that reflows everything on resolve
Name the wait past ~10s
anything genuinely slow
say what is happening and roughly how longan indeterminate bar with no end

A prompt becomes the product's rule only when it is adopted into Ours with a PAT-n.

1 decideddocs/design/PATTERNS.md · 2 seeded

Navigation · decided; nothing named in the tree · 1 decided

IDPatternDoDon'tFirst seen
PAT-7Three places, one rail
any screen — the owner is always in Today, Carers or Settings
descends from · 2 · The row is the unit
the Rail is the laptop's left rail and the phone's bottom bar, the same three places in the same order; the current one is marked; nothing else navigatesa hamburger menu; a back button that goes somewhere else2026-08-30
2 prompts BOSS seeded for this family — questions, not decisions
PromptDoDon't
One way to get anywhere
the product has more than three places
one primary navigation, the same on every screena sidebar on one screen, tabs on the next
Where am I
any screen in a hierarchy
the current place is marked, and the way back is one stepa breadcrumb that is decoration

A prompt becomes the product's rule only when it is adopted into Ours with a PAT-n.

1 decideddocs/design/PATTERNS.md · 2 seeded

Overlays · in use: ConfirmDialog · Toast · PrintSheet · 1 decided

IDPatternDoDon'tFirst seen
PAT-6One question, then it closes
any destructive act — cancel a visit, remove a carer
descends from · 1 · Calm over urgent
the ConfirmDialog asks one question with the consequence in its title, the danger button repeats the consequence, and it closes; the day is never hidden behind two dialogs"Are you sure?"; a dialog with a form in it2026-09-03
3 prompts BOSS seeded for this family — questions, not decisions
PromptDoDon't
A dialog is a question, not a place
you are about to open a modal
one decision, then it closes; anything the owner works *in* is a pagea modal with tabs; a modal that opens a modal
A toast confirms; it never asks
a result to report
short, auto-dismissing, with one undo if the act was reversiblea toast with two buttons
Focus goes in and comes back
any overlay opens
focus moves into it and returns to what opened it on closefocus left on the page behind

A prompt becomes the product's rule only when it is adopted into Ours with a PAT-n.

1 decideddocs/design/PATTERNS.md · 3 seeded

Feedback · in use: StatusChip · 3 decided

IDPatternDoDon'tFirst seen
PAT-1Shape before colour
a visit's state anywhere — row, card, paper
descends from · 1 · Calm over urgent
a hollow ring, a half-filled ring, a filled disc; colour is the second channela coloured dot; a tick icon2026-08-26
PAT-3Ask three, first yes fills
an uncovered visit; the owner taps Ask
descends from · 3 · Ask, don't assign research · EVID-002
the three who could — no ranking, no league table (DEC-003); the first yes fills; the owner confirmsbroadcast to everyone; auto-assign; a "best match" score2026-09-02
PAT-4Say when, after 8pm
anything queued by the 8pm rule (DEC-003)
descends from · 1 · Calm over urgent
the screen says the time it will go — "goes out at 7am" — on the carer's card and the owner's daya silent queue; a disabled control with no reason2026-09-08
2 prompts BOSS seeded for this family — questions, not decisions
PromptDoDon't
Severity has four levels and one is enough
any message to the owner
amber for uncovered, a settled green for covered, the copper only on the act, danger only on the confirm; colour *and* a shapefive shades of alert; red for everything
A badge is a number someone will act on
a count on navigation
never — principle 1; the day view is the countan unread count that never clears

A prompt becomes the product's rule only when it is adopted into Ours with a PAT-n.

3 decideddocs/design/PATTERNS.md · 2 seeded

Layout primitives · in use: AskCard · 1 decided

IDPatternDoDon'tFirst seen
PAT-5Rows sit, the card floats
any surface that holds a visit
descends from · 2 · The row is the unit
a row is paper on ground with a bottom rule; only the confirm dialog and the carer's card take shadow.raised — border for things that sit, shadow for things that floata card inside a card; a shadow on every row2026-09-05
1 decideddocs/design/PATTERNS.md

Refused — considered, rejected, kept so nobody proposes it again

PatternWhy refusedOn
streak counter for carersengagement-shaped, not value-shaped; a league table by another name (DEC-003)2026-08-20
"smart fill" — auto-assign the likeliest carerthe relationship is the product; principle 32026-08-26
a red badge on the day tabprinciple 1; the day view is the count2026-09-02
3 refuseddocs/design/PATTERNS.md · Refused
5 prompts BOSS seeded (Always) — questions the product has not answered as its own
PromptDoDon't
Five states
any interactive element
default / hover / active / disabled / empty+loading are named before code, not discovered aftershipping the happy path and meeting the rest in a bug report
Empty state
any list before there is data
say what to do next, and make the action reachable from here"Nothing here yet." · an illustration with no action
Error copy
any failure a user can see
say what to do next, not what failed"Oops! Something went wrong."
Destructive confirm
delete, revoke, cancel
name the consequence and whether it can be undone"Are you sure?"
Terminology
every user-facing string
one word per concept, from the table in STYLE_GUIDE.md*visit* here, *shift* there, *slot* in the text message

3 more families exist as options — icons · selection controls · forms as a whole — and appear only when the product uses one. /design-review seeds a family the first time a screen has it.

12 · Flows

3 flows, each with three paths or a hole where one is missing.

The one layer no checker can give you: the sequence is a judgment a person made and wrote down. A flow is entry → steps → exit, plus the first-run path (the one that ships broken) and the failure path (where they land, what they keep). Each step says why it is needed now — the step that can't is the step to cut.

Index

FlowEntryStepsEnds atOwned byPaths
Find cover for a visitthe uncovered row3the visit is covered and the owner has confirmedFEAT-0013 of 3
Answer an askthe carer's phone1a yes or a not-this-one, one thumbFEAT-0023 of 3
Import the weekthe owner's spreadsheet2Monday's visits on the day viewFEAT-0043 of 3
docs/design/FLOWS.md · rev. 2026-09-08

Find cover for a visit · the uncovered row → the visit is covered and the owner has confirmed · FEAT-001

#StepAsks the user forWhy it's needed now
1tap *Ask* on the rownothing — the three are chosenthe visit is uncovered and the owner is looking at it
2wait for the first yesnothingthe carers answer; the row's chip says *asked*
3confirm the yesone tapthe owner stays the one who decides (principle 3)

Cut, and kept: a note to the carers — asked for in the first version; nobody wrote one — the visit's time and client are the note · pick which three — the owner ranked carers in her head, which is the league table DEC-003 refuses; the three are the ones who could

happyfirst-runfailuredocs/design/FLOWS.md · FEAT-001

Answer an ask · the carer's phone → a yes or a not-this-one, one thumb · FEAT-002

#StepAsks the user forWhy it's needed now
1yes / not this oneone tapthe ask is the whole screen; anything else is the owner's

Cut, and kept: a reason for no — a carer's reason is theirs; the owner never sees it, so it was never needed

happyfirst-runfailuredocs/design/FLOWS.md · FEAT-002

Import the week · the owner's spreadsheet → Monday's visits on the day view · FEAT-004

#StepAsks the user forWhy it's needed now
1paste the week from the spreadsheetthe sheet as it isthe schedule stays the spreadsheet (DEC-002); this is the only way in
2check the daya glancethe parse is shown as rows before it becomes the day

Cut, and kept: map the columns by hand — the first nine sheets all had time · client · carer in some order; the parse guesses and step 2 catches it

happyfirst-runfailuredocs/design/FLOWS.md · FEAT-004
13 · Content

5 terms · 6 real strings.

The words are half the interface. Voice is constant; tone shifts by context; both get down to real strings, because an agent can act on “Delete 14 records. This can't be undone.” and cannot act on “confident”. The brand's how it sounds line is the source; this is where it becomes rules.

Terminology — one word per concept · 5

UseNeverBecause
visitshift, slot, booking, appointmentthe owner's word; a *shift* is the carer's whole day
carercaregiver, worker, staff, the helpwhat they call themselves (EVID-002)
coverfill, assign, bookthe product's whole job, in the owner's mouth: *find cover*
askrequest, send, notify, pingthe product's stance (principle 3)
coveredfilled, booked, done, sortedthe word on the chip and in the owner's mouth

The one content rule that is mechanically checkable. content-terminology-guard is on — a refused word in a string is caught at the write.

5 termsdocs/design/STYLE_GUIDE.md · Terminology

Voice, and how it shifts — the real strings

  • plain over clever — giving up: personality in the microcopy
  • brief over complete — giving up: the reassurance a longer message would give
  • the owner's words over ours — giving up: consistency with what the industry calls things
ContextHow the voice shiftsReal string
Successbrief, no confetti“"Covered. Priya's on the 9 o'clock."”
Errorwhat to do next, not what failed“"Couldn't reach anyone — try again, or ask three more."”
Warningname the thing, no exclamation mark“"The 9 o'clock is still uncovered."”
Destructive confirmname the consequence, not "are you sure"“"Cancel the 9 o'clock. Priya will be told."”
Empty statewhat to do next, not "nothing here"“"Nothing uncovered today."”
Loadingthe participle of the act“"Asking Priya, Sam and Jo…"”

Buttons: verb first, two or three words, sentence case — *Ask*, *Confirm Priya*, *Keep it* · Errors: what the owner should do, not what the system failed at · Empty states: what to do next, not "nothing here"

6 of 6 contexts have a real stringdocs/design/STYLE_GUIDE.md · Voice in the interface
14 · Accessibility

8 pairs computed, 0 findings; everything else needs a person.

One check here is arithmetic — contrast over two declared numbers — and it is computed. Every other check is a judgment or needs a rendered page, and the honest word for those is not checked, said once, never mistaken for a pass.

The floor — not negotiable, not a phase

  • Contrast: body text ≥ 4.5:1, large text ≥ 3:1 — check the *token pairs*, not screenshots
  • Every interactive element has a visible focus state (2px `color.action.primary` outline, 2px offset)
  • `prefers-reduced-motion` honored by every animation
  • Nothing communicated by color alone — the chip is a shape first
docs/design/STYLE_GUIDE.md · Accessibility floor

Computed — contrast, once, where the tokens are

8 declared text-on-surface pairs, 0 under AA — the table is in Colour → Contrast, once. A pair that fails is a token finding: fix it in docs/design/tokens.json and every screen moves.

GuardWhat it holdsState
contrast-guardcontrast, per declared pair, at every tokens changeon
design-tokens-guarda raw colour caught at the writeon
component-reuse-guarda second Button asked about before it existson
content-terminology-guarda refused word caught in a stringon
0 findings · token-level4 of 4 guards on.claude/settings.json · docs/design/tokens.json

Not checked — said once, for everything below

  • Focus — every interactive element has a visible focus state
  • Keyboard — every flow completes without a pointer
  • Screen reader — names, roles, the order things are announced
  • Colour alone — nothing is communicated only by hue
  • Motion — prefers-reduced-motion honoured by every animation
  • Text over an image, a gradient, a translucent overlay — composites at runtime; the token pairs cannot see it
  • 320px — reads and works at the narrowest width

Each needs a rendered page or a person. /design-review after walks them against shipped UI and writes not checked where it cannot see — the same word as here, never a pass by omission.

7 · not checkedneeds a render or a person · /design-review after
15 · Resources

Take it with you — the one file, and what derives from it.

Everything here is a file on disk or derived from one at render. The tokens file is the seam between code and a design tool: on any plan a tokens plugin reads it; an enterprise API can read and write it; native import without a plugin is unverified. The page says which, and never promises a round trip.

The tokens file · W3C DTCG · the source of every value on this page

43 tokens · 6415 bytes. Code copies the file, or the :root { --… } block derived from it. Download tokens.json

:root {
  --color-surface-ground: #F3ECE1;
  --color-surface-paper: #FFFBF4;
  --color-surface-raised: #FFFFFF;
  --color-text-body: #2A1E17;
  --color-text-muted: #665247;
  --color-text-on-primary: #FFF7F0;
  --color-text-on-danger: #FFFFFF;
  …

Your design tool: a tokens plugin on any plan reads this file; the enterprise API reads and writes it; native import is unverified — check before planning around it. The return trip is the same file, exported back and diffed.

DTCG 2025.10docs/design/tokens.json

The import lines · 13 · the reuse list, as one paste

import { Button } from '@/components/Button'
import { VisitRow } from '@/components/VisitRow'
import { StatusChip } from '@/components/StatusChip'
import { AskCard } from '@/components/AskCard'
import { EmptyState } from '@/components/EmptyState'
import { ConfirmDialog } from '@/components/ConfirmDialog'
import { Toast } from '@/components/Toast'
import { Rail } from '@/components/Rail'
import { TextField } from '@/components/TextField'
import { CarerRow } from '@/components/CarerRow'
import { ImportRow } from '@/components/ImportRow'
import { RowSkeleton } from '@/components/RowSkeleton'
import { PrintSheet } from '@/components/PrintSheet'
docs/design/COMPONENTS.md

The icon sprite · 6 symbols · SVG copies it

One <svg> of <symbol>s. In a page: <svg><use href="#icon-ask"/></svg>. In a design tool: paste, and each symbol is a layer.

docs/design/icons/*.svg

How to ask — for a part, a token, a retirement; a person or an agent, the same three doors

  • A new part: write its usage page — docs/design/components/<Name>.md, status: proposed, with why it exists (the behaviour no existing part has) and when it applies. It shows under Asked for; the next /design-review answers reuse, adjust, or new.
  • A token's value: a decision — /decide writes the DEC that names the token; the swatch shows it. Never edit the hex in a component.
  • Retiring a part: mark the row deprecated → Successor; the guard turns the next import toward the successor; the row moves to Retired when the last import is gone.

The check that keeps the library a library: nothing new lands unless it names a behaviour nothing else has. Same look, different need — widen the existing one. Same job, different look — a variant, not a file.

component-reuse-guard · /design-review · /decide

Kit coverage — which components have a counterpart in your design tool

0 of 13 components carry a design: link.

  • Button — no link
  • VisitRow — no link
  • StatusChip — no link
  • AskCard — no link
  • EmptyState — no link
  • ConfirmDialog — no link
  • Toast — no link
  • Rail — no link
  • TextField — no link
  • CarerRow — no link
  • ImportRow — no link
  • RowSkeleton — no link
  • PrintSheet — no link

A link is a URL into your own file and node: a design field on the component in the manifest, or a Design column in COMPONENTS.md. Coverage is a fact about links, never about whether the two match — a component is stable when the code, the docs and the kit agree, and this page can only show which of the three exist.

0 of 13 linkedmanifest.json · design: · COMPONENTS.md · Design column
16 · Exceptions & divergence

3 recorded, against 3 rules.

Two ways a system drifts, both counted here. A deliberate departure, dated, is a decision; an unrecorded one is drift that reads as precedent — grouped by the rule each departs from, because three against one rule is a rule being worked around. And the trace: how often the build was handed one of its own decisions at the write, and whether every new-component question got an answer.

Divergence — the last 30 days, from the trace · did the build follow its own decisions?

Decisions handed at the write

4 times across 3 files — PAT-3 ×1 · PAT-1 ×1 · dodont-1 ×1 · PAT-4 ×1 · exception-2026-09-04 ×1.

New component asked about

2 questions: 1 became a row · 1 reused (the name is gone) · 0 unanswered (in the tree, no row).

Counts, not verdicts: a decision handed is a screen that stayed on-system without anyone opening this page; an unanswered question is a fork nobody justified. What this cannot see is a write that diverged in a situation nothing ever decided — that is a hole above, not a number here.

0 unansweredsince 2026-09-04.boss/trace.jsonl · design-decision · component-new

two primary-weight actions on one sheet · 1 · an exception

DateWhereWhatWhy
2026-08-28the printed Mondaytwo primary-weight actions on one sheetpaper has no hover; both acts must read at a glance — reviewed, kept
1 · an exceptiondocs/design/STYLE_GUIDE.md · Exceptions

two primary-weight actions · 1 · an exception

DateWhereWhatWhy
2026-09-04the day view on the phonetwo primary-weight actions*Ask* and *Confirm* both needed on an answered visit — second of its kind, worth noticing
1 · an exceptiondocs/design/STYLE_GUIDE.md · Exceptions

a raw hex for the print rule · 1 · an exception

DateWhereWhatWhy
2026-09-08PrintSheeta raw hex for the print rulethe print stylesheet has no tokens yet — the next token family
1 · an exceptiondocs/design/STYLE_GUIDE.md · Exceptions
17 · Research

2 observed · 2 stated · the personas are 70% inferred.

Research is everything that tells you whether the design is right — what people did, what they said, what the field does, what a reviewer can see. The same records the playbook keeps by date, cut here by the rung they reach and by the method that produced them. Grades, dates and methods only; never a quote. Coverage is a fact; readiness is a verdict.

By rung — observed beats stated beats inferred

RungnRecords
Observed — someone was watched, or paid, or showed up2EVID-003 · 2026-08-30 · EVID-004 · 2026-09-05
Stated — someone said something that bears on a design choice2EVID-001 · 2026-05-20 · EVID-002 · 2026-06-04
Inferred — the founder's assumptions, marked as such2Marta · synthetic 40% · Priya · synthetic 100%
4 records · newest 2026-09-05docs/evidence/ · grades and dates only

By method — what has been used, and what never has

MethodRung it reachesUsedVerb
Interviewsstatedused · 2/interview · /evidence
Watching someone use itobservedused · 1/interview → the last question: "could I sit with you?"
Product events · drop-offobservednever/health — a few events, not ten
A pretotype (fake door · concierge)observednever/pretotype
A commitment asked forobservedused · 1/money · the ask at the end of a call
Desk · competitiveinferredused · 1/comp-eval
Heuristic reviewinferredused · 1/design-review

Read it by rung: every observed method that says never is a design decision resting on a guess. The EVID record's method: field is what this reads.

5 of 7 methods useddocs/evidence/*.md · method: · docs/competition/README.md · docs/design/ux-check-*.md