Technical guide

Design

Rules and checks

Every design rule by name, and what holds it: the kit that’s its only implementation, the check that measures it, or the audit that looks at it.

Checked against the code on 2026-10-31.

On this page
  1. Every rule, and what holds it
  2. The checks, and what they measure
  3. Every finding names its rule
  4. Exceptions

Every rule in Principles is held in at least one of three ways (how a rule is held): the kit is its only implementation, a check measures it, or the design audit looks at it before a release. This chapter lists them all, by name.

Every rule, and what holds it#

“By eye” means the /design-audit skill’s look over every game’s screens (.claude/skills/design-audit/SKILL.md). A check kind in brackets is report-only for now: it’s printed, but doesn’t fail the run.

Rule The kit The checks By eye
Same place, same word sheet(), result(), openOptions(), backArrow() check:house yes
Never stranded result() fixes the actions for each ending check:house (each ending’s buttons and order) that every ending reaches one
Progress stays put keep, prefs() each game’s own tests yes
The way up backArrow() check:house; check:layout (one 44px arrow, its name and where it goes)
Undo or ask toast() with Undo; Slot.set({ confirm }) yes
Helpers on openOptions() check:house
Keys and focus sheet(), shortcuts(), keyCap() check:house (Escape, focus, Tab, keys while typing); check:details: stray (a key cap showing for a hidden control) with a screen reader
A finger’s reach .house-reach check:details: tap, taken
Every screen fits the safe-area gutter check:details: offscreen; check:layout (no sideways scroll)
Errors say what’s next yes
Type roles the scale in house.css check:tokens (every root’s sizes); check:layout (the sizes drawn)
Readable check:tokens (soft ink and accent ink on their surfaces); check:room (each table style’s words and marks); check:details: [readable]
Words fit check:details: clip, overflow
Room to breathe check:details: [room]
Centered by ink .house-glyph, closeIcon(), MID check:details: center, icon, and the painted pass; check:details:self
Rows in line check:details: align
Plain words the kit’s own words yes
The glance .house-figure yes
Never only color or motion seat shapes; reducedMotion() check:house (reduced motion) yes
One fill a sheet’s foot and the result sheet take one primary check:details: alike (a fill from where something sits) yes
Three weights configure() yes
Choosing isn’t doing seg() check:tokens (every root declares a pick)
Look follows meaning check:details: alike yes
Colors have jobs check:tokens (every table game declares the jobs) yes
One skeleton the layout tokens in house.css check:tokens (the rhythm; no game sets a layout token); check:layout (gutter, bar, column, kicker)
One setting pattern setting(), openOptions() check:house (Options’ order)
Nothing moves under you slots that keep their space; presses ignored for 0.4s check:house (a sheet’s first 400ms) yes
Nothing over anything check:details: overlap
The quietest signal toast(), banner(), announce(), celebrate() check:house (where each goes, and its queue) which one a game picks
Fair clocks onSheets(), setSheetClock() check:house
Motion explains MOTION and the --m-* tokens check:house (the two agree; reduced motion) yes
Your move prompt(), beckon() check:house; check:room (the turn ring stands out in every table style)
See others’ moves the room’s Cues check:room (beats and holds)
Done to you announce(); the room’s onyours.js check:room yes
Celebrate a victory celebrate(), result({ celebrate }) check:house
The game’s own look check:tokens (what a game may set) yes

The checks, and what they measure#

Check Needs Measures
npm run check:tokens nothing Every stylesheet, read as text: each root’s tokens and their sizes, contrast on each surface, no colors in the structure files, nothing set that’s the house’s
npm run check:house, npm run check:room Node The kits’ behavior, on a fake page: sheets, keys, the result sheet, Options, motion, cues; and each table style’s marks against its felt
npm run check:layout the site and Chrome Every family’s screens at four sizes: gutters, the bar, the column, the type roles, buttons, panels and sheets
npm run check:details the site and Chrome (or WebKit) Every game’s screens at seven sizes: each kind in the table above
npm run check:details:self Chrome and WebKit That check:details still finds what it should, from planted mistakes

How to run each one, and when, is in Testing and checks.

Every finding names its rule#

check:tokens, check:layout and check:details end each finding with the rule it breaks, by name, as [one-skeleton], so a finding leads straight to its principle (/guide/design/principles/#one-skeleton). The names come from tools/house/rulebook.mjs, which reads them from the Principles chapter’s headings, and a name that isn’t a rule throws:

tools/house/rulebook.mjsrule · lines 32–37
// A finding's rule, by name, for a check's output: "[one-skeleton]". An
// unknown name throws, so a check can't cite a rule that isn't one.
export function rule(name) {
  if (!NAMES.has(name)) throw new Error(`no design rule named ${name} in ${PRINCIPLES}`);
  return `[${name}]`;
}

check:details maps each kind it finds to its rule:

tools/house/rulebook.mjsDETAILS · lines 39–45
// Each kind check:details finds, and the rule it holds.
export const DETAILS = {
  center: 'centered-by-ink', icon: 'centered-by-ink', overlap: 'nothing-over-anything',
  clip: 'words-fit', overflow: 'words-fit', offscreen: 'every-screen-fits', align: 'rows-in-line',
  tap: 'a-fingers-reach', taken: 'a-fingers-reach', stray: 'keys-and-focus',
  alike: 'look-follows-meaning', room: 'room-to-breathe', readable: 'readable',
};

Nothing goes unheld. tools/house/rulebook.test.mjs, part of npm run check:house, fails when:

  • a rule in Principles is missing from the table above, or the table names one that isn’t a rule;
  • a rule’s row has nothing in it: no kit, no check and not by eye;
  • a kind check:details finds has no rule, or its rule’s row doesn’t list it (in brackets while it’s report-only);
  • check:tokens or check:layout cites a rule that doesn’t exist.

So a new rule comes with what holds it, and renaming one means renaming it everywhere it’s cited.

Exceptions#

A finding that’s meant is listed with why, and every other finding is fixed. The lists are in the checks themselves:

  • ALLOW in tools/house/details.mjs: a badge on a corner, a fanned hand under 44px, a seat’s name ellipsized, and the rest.
  • EXCEPTIONS in tools/house/layout.mjs: The Register’s masthead, the one title not on the scale.

An exception names the screen and the reason a player would recognize. “The code put it there” isn’t a reason.