trykeyline 0.0.0-stage → 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (96) hide show
  1. package/.claude-plugin/marketplace.json +18 -0
  2. package/LICENSE +37 -0
  3. package/README.md +117 -2
  4. package/bin/keyline.mjs +280 -0
  5. package/package.json +38 -4
  6. package/plugins/keyline/.claude-plugin/plugin.json +14 -0
  7. package/plugins/keyline/skills/keyline/SKILL.md +57 -0
  8. package/plugins/keyline/skills/keyline/commands/audit.md +25 -0
  9. package/plugins/keyline/skills/keyline/commands/color.md +7 -0
  10. package/plugins/keyline/skills/keyline/commands/critique.md +43 -0
  11. package/plugins/keyline/skills/keyline/commands/depth.md +7 -0
  12. package/plugins/keyline/skills/keyline/commands/init.md +15 -0
  13. package/plugins/keyline/skills/keyline/commands/layout.md +7 -0
  14. package/plugins/keyline/skills/keyline/commands/motion.md +7 -0
  15. package/plugins/keyline/skills/keyline/commands/patterns.md +7 -0
  16. package/plugins/keyline/skills/keyline/commands/polish.md +19 -0
  17. package/plugins/keyline/skills/keyline/commands/refine.md +29 -0
  18. package/plugins/keyline/skills/keyline/commands/sync.md +17 -0
  19. package/plugins/keyline/skills/keyline/commands/type.md +7 -0
  20. package/plugins/keyline/skills/keyline/domains/color/deriving-a-ramp.md +144 -0
  21. package/plugins/keyline/skills/keyline/domains/color/guide.md +243 -0
  22. package/plugins/keyline/skills/keyline/domains/color/status-and-exceptions.md +174 -0
  23. package/plugins/keyline/skills/keyline/domains/color/theming-mechanics.md +121 -0
  24. package/plugins/keyline/skills/keyline/domains/depth/elevation-recipes.md +191 -0
  25. package/plugins/keyline/skills/keyline/domains/depth/fallbacks-and-performance.md +132 -0
  26. package/plugins/keyline/skills/keyline/domains/depth/guide.md +255 -0
  27. package/plugins/keyline/skills/keyline/domains/depth/translucent-materials.md +159 -0
  28. package/plugins/keyline/skills/keyline/domains/layout/forks-gating-and-disclosures.md +201 -0
  29. package/plugins/keyline/skills/keyline/domains/layout/guide.md +284 -0
  30. package/plugins/keyline/skills/keyline/domains/layout/measure-and-rhythm.md +193 -0
  31. package/plugins/keyline/skills/keyline/domains/layout/radius-lines-and-layers.md +122 -0
  32. package/plugins/keyline/skills/keyline/domains/motion/guards.md +162 -0
  33. package/plugins/keyline/skills/keyline/domains/motion/guide.md +234 -0
  34. package/plugins/keyline/skills/keyline/domains/motion/ladder-and-curves.md +139 -0
  35. package/plugins/keyline/skills/keyline/domains/motion/patterns.md +196 -0
  36. package/plugins/keyline/skills/keyline/domains/motion/performance.md +121 -0
  37. package/plugins/keyline/skills/keyline/domains/patterns/actions-and-forms.md +204 -0
  38. package/plugins/keyline/skills/keyline/domains/patterns/agents-and-live-states.md +111 -0
  39. package/plugins/keyline/skills/keyline/domains/patterns/collections-and-dashboards.md +131 -0
  40. package/plugins/keyline/skills/keyline/domains/patterns/copy.md +123 -0
  41. package/plugins/keyline/skills/keyline/domains/patterns/guide.md +216 -0
  42. package/plugins/keyline/skills/keyline/domains/patterns/iteration.md +78 -0
  43. package/plugins/keyline/skills/keyline/domains/type/display-tier.md +108 -0
  44. package/plugins/keyline/skills/keyline/domains/type/guide.md +251 -0
  45. package/plugins/keyline/skills/keyline/domains/type/ladders.md +169 -0
  46. package/plugins/keyline/skills/keyline/domains/type/numerals-and-loading.md +107 -0
  47. package/plugins/keyline/skills/keyline/domains/type/prose-and-emphasis.md +125 -0
  48. package/plugins/keyline/skills/keyline/domains/type/type-audit.md +90 -0
  49. package/plugins/keyline/skills/keyline/glass/color/assets/color-tokens.css +319 -0
  50. package/plugins/keyline/skills/keyline/glass/color/assets/theme.ts +119 -0
  51. package/plugins/keyline/skills/keyline/glass/color/guide.md +222 -0
  52. package/plugins/keyline/skills/keyline/glass/color/intent-ramp.md +136 -0
  53. package/plugins/keyline/skills/keyline/glass/color/theming.md +112 -0
  54. package/plugins/keyline/skills/keyline/glass/depth/assets/GlassPanel.tsx +171 -0
  55. package/plugins/keyline/skills/keyline/glass/depth/assets/glass.css +322 -0
  56. package/plugins/keyline/skills/keyline/glass/depth/blur-and-tint.md +50 -0
  57. package/plugins/keyline/skills/keyline/glass/depth/depth-cues.md +87 -0
  58. package/plugins/keyline/skills/keyline/glass/depth/guide.md +216 -0
  59. package/plugins/keyline/skills/keyline/glass/depth/performance-and-fallbacks.md +37 -0
  60. package/plugins/keyline/skills/keyline/glass/layout/adaptive-presentations.md +231 -0
  61. package/plugins/keyline/skills/keyline/glass/layout/assets/SectionNav.tsx +244 -0
  62. package/plugins/keyline/skills/keyline/glass/layout/assets/layout-tokens.css +199 -0
  63. package/plugins/keyline/skills/keyline/glass/layout/guide.md +258 -0
  64. package/plugins/keyline/skills/keyline/glass/layout/measure-and-rhythm.md +134 -0
  65. package/plugins/keyline/skills/keyline/glass/layout/radius-and-elevation.md +102 -0
  66. package/plugins/keyline/skills/keyline/glass/motion/assets/DropMenu.tsx +106 -0
  67. package/plugins/keyline/skills/keyline/glass/motion/assets/motion-presets.ts +210 -0
  68. package/plugins/keyline/skills/keyline/glass/motion/assets/motion-tokens.css +176 -0
  69. package/plugins/keyline/skills/keyline/glass/motion/assets/useReveal.ts +65 -0
  70. package/plugins/keyline/skills/keyline/glass/motion/assets/useScrollTick.ts +183 -0
  71. package/plugins/keyline/skills/keyline/glass/motion/easing-and-duration.md +108 -0
  72. package/plugins/keyline/skills/keyline/glass/motion/guide.md +286 -0
  73. package/plugins/keyline/skills/keyline/glass/motion/reduced-motion.md +81 -0
  74. package/plugins/keyline/skills/keyline/glass/motion/scroll-performance.md +58 -0
  75. package/plugins/keyline/skills/keyline/glass/motion/shared-element.md +161 -0
  76. package/plugins/keyline/skills/keyline/glass/motion/sound.md +192 -0
  77. package/plugins/keyline/skills/keyline/glass/motion/springs.md +51 -0
  78. package/plugins/keyline/skills/keyline/glass/type/assets/prose.tsx +268 -0
  79. package/plugins/keyline/skills/keyline/glass/type/assets/type-tokens.css +414 -0
  80. package/plugins/keyline/skills/keyline/glass/type/guide.md +222 -0
  81. package/plugins/keyline/skills/keyline/glass/type/long-form-prose.md +151 -0
  82. package/plugins/keyline/skills/keyline/glass/type/scale.md +69 -0
  83. package/plugins/keyline/skills/keyline/glass/type/weight-and-tracking.md +61 -0
  84. package/plugins/keyline/skills/keyline/scripts/check/css.mjs +112 -0
  85. package/plugins/keyline/skills/keyline/scripts/check/design.mjs +67 -0
  86. package/plugins/keyline/skills/keyline/scripts/check/files.mjs +44 -0
  87. package/plugins/keyline/skills/keyline/scripts/check/markup.mjs +149 -0
  88. package/plugins/keyline/skills/keyline/scripts/check/rules.mjs +584 -0
  89. package/plugins/keyline/skills/keyline/scripts/check.mjs +294 -0
  90. package/plugins/keyline/skills/keyline/scripts/contrast.mjs +153 -0
  91. package/plugins/keyline/skills/keyline/scripts/scan.mjs +340 -0
  92. package/plugins/keyline/skills/keyline/system/design-md.md +136 -0
  93. package/plugins/keyline/skills/keyline/system/detection.md +53 -0
  94. package/plugins/keyline/skills/keyline/system/guide.md +101 -0
  95. package/plugins/keyline/skills/keyline/system/presets.md +58 -0
  96. package/plugins/keyline/skills/keyline/system/roles.md +150 -0
@@ -0,0 +1,216 @@
1
+ # Product Patterns
2
+
3
+ The other domains decide how an interface looks: its type, colour, layout, surfaces and motion. This one decides how it behaves and what it says — the label on a button, what a confirmation asks, how an agent shows its work, what happens when a save fails. **A pattern is a promise the product keeps everywhere**: once delete asks one way, every delete must, or the reader has to read every dialog to learn what it wants. Most product bugs a reviewer can't name are a pattern kept in one place and broken in another.
4
+
5
+ The laws hold under every preset. How much keyboard, how much is remembered and how much copy a screen carries are dials.
6
+
7
+ ## Before you start
8
+
9
+ Read `DESIGN.md` at the project root: `## Profile` for the preset and any areas on another one, `## Patterns` for the conventions already chosen — the product's nouns, its destructive pattern, its shortcut map, which views are remembered — and `## Decisions`. Write copy in the project's nouns.
10
+
11
+ - **No `DESIGN.md`:** run `/keyline init` for the profile and preset only, do the task, then add a `## Patterns` section and recommend the full setup.
12
+ - **A pattern you need has no entry:** find the nearest one already in the code and reuse it; otherwise define it once and record it under `## Patterns`.
13
+ - **The task would break a law or move a dial off its preset:** say so and ask; if the user confirms, record it with its reason under `## Decisions`.
14
+ - **Preset `glass`:** it carries no pattern values; use the dials of the preset nearest the reader.
15
+
16
+ Boundaries: how a label or figure is set is the type guide's; what colour danger, brand and status take, the colour guide's; where things sit and which views fork, the layout guide's; what a dialog or card is built from, the depth guide's; how long anything takes to move, the motion guide's. This guide decides the flow and the words, and names the owner where the rest is someone else's.
17
+
18
+ ## Laws
19
+
20
+ ### 1. A label is as short as its action, in sentence case
21
+
22
+ **A label names the action in the fewest words that still say it: a button reads its verb — "Send", "Save changes", "Record" — not the sentence around it ("Record a voice note").** Labels are scanned, not read; every extra word moves the verb away from where the eye lands, and the tail is the first thing to truncate. Short is not narrow: a label covers everything behind it, so an item that takes questions, bug reports and requests is "Help & feedback", not "Request a feature". Add an object only where two verbs on one screen would otherwise collide ("Save draft" beside "Publish"); a confirm button repeats its dialog's verb ("Delete project"), never "OK" or "Yes".
23
+
24
+ **Sentence case everywhere** — buttons, menu items, dialog titles, field and column names, tabs, toasts, and labels the code generates from keys (`dueDate` → "Due date"). Title case marks every word as a name, and uppercase strips word shapes; acronyms, proper nouns and third-party product names stay as written.
25
+
26
+ ### 2. A row's second line says what it is and where it's from
27
+
28
+ **Under a title, the second line names the record's type and origin in the product's own nouns — "Task from Riverside", "Note from site walk" — never an event ("Issue added to Riverside").** The title says which one; the reader's next questions are what kind and from where, and those let them triage without opening it. Event phrasing describes something that happened to the record once, in a verb nobody filters by, and often swaps the noun, so the reader wonders whether an "issue" here is a "task" everywhere else. **One noun per kind of thing across the product**, listed in `DESIGN.md`. Time, owner and status take their own slots — a column, a meta line, a glyph — not the sentence. The title stays the strongest line in the row (the type guide, the colour guide).
29
+
30
+ → Before-and-after labels, button verbs, generated labels, toasts and error copy: `domains/patterns/copy.md`
31
+
32
+ ### 3. Destructive and session-ending actions look alike, and come last
33
+
34
+ **Every action that destroys data or ends the session — delete, remove, clear, revoke, sign out — takes one treatment and one place: set apart at the end of its menu, after a divider, with no icon.** Position and treatment are recognised before words are read; when delete has a bin, clear has none and sign out sits mid-menu, the reader learns three severities that don't exist. This is **similarity** at work — things that look alike are read as one kind — and **serial position**: the first and last items of a list are the ones remembered, so the end of a menu is a place readers learn to find, and to avoid. The colour is ground, not decoration (the colour guide). Such an action is never the default focus, never beside the item clicked most, and never the primary button of a dialog that isn't about it. The reverse holds too: **nothing that isn't destructive borrows its look** — a recording or live state is not danger.
35
+
36
+ ### 4. A confirmation says what is lost, in the system's own dialog
37
+
38
+ **A confirmation states in plain words what will be lost, uses the product's standard dialog, and asks for proof of intent in proportion to the loss.** The dialog is the system's own (depth law 1). Proportion:
39
+
40
+ - **Reversible** — no dialog; do it, and offer Undo in the toast.
41
+ - **Irreversible, one item** — a dialog whose button repeats the verb.
42
+ - **Irreversible and large** — a project, an account, a workspace — "Type DELETE to confirm", compared case-insensitively, the input focused on open (not Cancel), the action disabled until it matches, "Deleting…" while it runs, and the dialog held open until it succeeds or says why it failed.
43
+
44
+ **One pattern everywhere**: deleting a project and deleting an account read the same. On close, focus goes back to what opened it (depth law 12).
45
+
46
+ ### 5. An agent's work is shown step by step, and approved where it happens
47
+
48
+ **Every tool call an AI agent makes appears as a collapsible card — the tool's name in sentence case, its arguments, its result — never as a lone spinner; a step that needs approval asks inline, under its own card.** A spinner asks for trust; a card lets the reader check what was done and catch it going wrong. A modal approval hides the card and the conversation the decision depends on. The approve button names the action and takes the brand action colour; reject is muted.
49
+
50
+ The conversation carries no avatar per message: the reader's turns sit right-aligned on a tint, the agent's run full width with no fill, because the agent's output is the content. Mark AI output with one small mark in brand, not a robot face or a coloured bubble (the colour guide). The panel is non-modal, so the reader keeps working beside it.
51
+
52
+ ### 6. Live output separates the settled from the forming
53
+
54
+ **A streaming state — a live transcript, an answer being generated — shows committed text in ink and the phrase still forming muted, with a caret; keeps the newest line in view under a top fade; and lets the reader scroll back without being dragged down.** Words that may still change shouldn't look final; ink that rewrites itself reads as a glitch. Follow the bottom while the reader is there; once they scroll up, stop following and offer "Jump to latest". Screen readers hear committed segments only.
55
+
56
+ **Paused means still** (motion law 1), and the paused state offers what the reader wants next — the text so far, Copy, Resume. One "working now" signal per screen; how a live control turns into Stop is the motion guide's.
57
+
58
+ → Tool-call cards, inline approvals, conversation layout, follow-the-bottom and announcing: `domains/patterns/agents-and-live-states.md`
59
+
60
+ ### 7. Dense tools are keyboard-first, and Escape closes one layer
61
+
62
+ **In a tool used all day, every frequent action has a key — ⌘K or Ctrl+K for a command palette, J and K to move through a list, a single key to filter, Enter to send and Shift+Enter for a new line — and Escape closes only the innermost layer.** The pointer is the slow path for an action repeated a hundred times a day, and an Escape that closes the dialog and the panel under it throws away context. Single-letter keys never fire while focus is in a text field. **Every drag has a keyboard path** ("Move to…" in the row menu), so dragging is a shortcut, never the only way. A shortcut is shown where its action lives — menu item, tooltip, palette — from one map recorded in `DESIGN.md`.
63
+
64
+ ### 8. Few choices up front; the rest one step away
65
+
66
+ **A launcher offers only the choices the task has, and a toolbar or a long list holds back what isn't needed every time.** Every option on the path of the most frequent action is something to read before starting: a record launcher asks one question with two answers — which project, or a personal note — not a form. Toolbar filters are icons with tooltips, not a text label on every control. A long admin list groups into collapsed bars, closed by default, each with a summary of what the reader would open it to check ("Admins · 3 of 12 with access"), and a search opens every group it matches.
67
+
68
+ **Hick's law** is why: the time to choose grows with the number of choices, so every option shown up front taxes the choice the reader came to make. **Progressive disclosure** is the cure — the common path visible, the rest one step away (layout law 11 keeps it in the markup). **Miller's law** says why grouping helps: people hold only a few chunks in mind at once, so a flat run of a dozen nav items or a 16-digit code reads as noise, where three groups of four, or `4242 4242 4242 4242`, read at a glance. Chunk long lists and long strings; don't treat "seven" as a cap that cuts what the product needs.
69
+
70
+ ### 9. An empty state offers the first action
71
+
72
+ **Where the user's own data is empty, say in one line what goes here and offer the one action that creates the first item — never "No items found."** "No … found" reports a failure and sends the reader looking for the create button elsewhere. Empty because filtered is a different state: name the filter and offer to clear it. Owner-curated collections don't get an empty state at all; they disappear (layout law 10).
73
+
74
+ ### 10. Work is shown the way it moves
75
+
76
+ **Items that move through stages get a board whose columns run in the order work moves, each headed by its status glyph; items placed in time get a week board with Week, 3 days and Day spans; the list stays as the second view of the same items.** The board makes the state of the work the layout; a list makes it a column to sort by. Both share data, filters and rows (layout law 8).
77
+
78
+ Whether the view and span are remembered is layout law 9; list each page that remembers under `## Patterns`. Status changes by drag (law 11) and by the keyboard path (law 7).
79
+
80
+ ### 11. A move happens now and rolls back visibly
81
+
82
+ **A drag, toggle, rename or reorder updates the screen at once, saves in the background, and on failure puts the item back where it was with a toast that says what failed and offers Retry.** Waiting on the network for a move the reader can already see makes every action feel broken. The rollback has to be seen: a card that silently snaps back reads as a missed drop, and one that silently stays reads as saved. Keep the previous state until the save confirms, and drop responses older than the latest change. **Not for the irreversible** (law 4) **or for results the server decides** — sending, paying, anything another person sees at once.
83
+
84
+ → Board and list, remembered views, rollback code, the shortcut map, launchers and grouped lists: `domains/patterns/collections-and-dashboards.md`
85
+
86
+ ### 12. A dashboard is overview, trends and action items, from one pipeline
87
+
88
+ **A dashboard answers three questions in order — where things stand (overview), which way they're moving (trends), what needs the reader (action items) — and drops anything nobody acts on.** A recent-activity feed nobody clicks is a column of noise next to the items that need a decision. **Wireframe it first and share the wireframe**: arguing about boxes is cheaper than arguing about a built page. A long page is fine; a page that hides what needs action to stay short is not. **A scoped overview runs the global overview's pipeline with its sources filtered**, never a parallel query, so the two can't disagree. Figures under a header can double as the table's filters (stat rows: layout law 3; figure colours: the colour guide). A panel the product or its brief requires stays; make it earn its place, don't cut it (`commands/refine.md`).
89
+
90
+ ### 13. A form leads with the field that decides the rest
91
+
92
+ **The field whose answer changes what follows comes first and full width — a category before the free text — and every field has its label above, its hint below, and its error in the hint's place.** Asking for the free text first makes the reader write before they know what kind of thing they're writing, then reshuffles the form under them. An error replaces the hint rather than adding a line, so nothing jumps; a failure of the whole form is one line with an icon above the button; the one primary button reads the verb.
93
+
94
+ **A sign-in screen is a form with nothing else**: unboxed in its own column of about 360px — title, one muted line, fields, one brand button — with an art panel taking the other half from a wide breakpoint, and the form alone on a phone. List the auth routes as an `expressive` area in `DESIGN.md`'s `## Profile`, since that is the only place the art may move; elsewhere it holds one still frame (the panel: the depth guide's art-panel recipe; its motion: the motion guide's ambient class; 16px inputs on phones: the type guide).
95
+
96
+ → The confirmation dialog, undo toasts, field anatomy and the sign-in layout in code: `domains/patterns/actions-and-forms.md`
97
+
98
+ ### 14. Every element keeps the promise its form makes
99
+
100
+ **What an element looks like says what it does, and it does that: a play icon opens a video, a link goes somewhere, and anything that doesn't act doesn't react.** Landing pages and articles are where these promises break, because they are built to look a certain way before anyone decides what each piece does, and a reader who clicks a dead logo or a silent play icon stops trusting the rest. A logo that isn't a link takes no hover state; an email address is a `mailto:` link; "Copy link" is a button, not a link to the page already open; link text names its destination ("Pricing", not "Learn more"). A call to action names what happens next — "Start free", "Book a demo" — so two on one page can't be mistaken for each other, as "Get started" beside "Start free" can. A subscribe or sign-up field has a visible label and says what will arrive and how often.
101
+
102
+ **Conventions are promises too** (Jakob's law): readers spend most of their time in other products and arrive expecting those to hold here. The logo goes home, search sits behind ⌘K or a magnifier, the account is at the end of the navigation, a link is underlined or obviously a link. Break a convention only for a gain the reader will feel on first use, and never for novelty alone.
103
+
104
+ → Calls to action, link text and sign-up lines: `domains/patterns/copy.md`
105
+
106
+ ### 15. Every action answers at once, and anything slow says what it's doing
107
+
108
+ **An input is acknowledged within 100ms — a press state, a toggle flipping, a row moving — and any result that takes longer than about 400ms shows what is happening in the place it was asked for.** Under 400ms (the Doherty threshold) the reader and the product keep pace and attention holds; past it, people start to wonder whether the click took, and click again. So the acknowledgement is instant and local (press feedback is the motion guide's), the slow part runs in the background where it can (law 11), and the wait is honest where it can't:
109
+
110
+ - **Past ≈ 400ms**, the control says it's working — "Saving…" in the button's own label, the button held at its width.
111
+ - **Past ≈ 1s**, the region that will change shows its shape — a skeleton of the rows coming, not a centred spinner over a blank page.
112
+ - **Past ≈ 10s**, progress is determinate — a count, a step, a bar — with a way to cancel or leave and be told when it's done.
113
+
114
+ **The loop closes where it opened**: the result appears where the action was taken, and what changed is visible without reading a toast. A save that succeeds silently, a filter that applies off screen, or an error reported in a corner the reader isn't looking at breaks the loop, and readers repeat the action to find out what happened.
115
+
116
+ ### 16. Input is forgiving, and the product does the remembering
117
+
118
+ **Accept what people type in any reasonable form and store one canonical form; show choices rather than ask for things from memory; and let the product carry the complexity the reader would otherwise carry.** Three laws meet here:
119
+
120
+ - **Postel's law** — be liberal in what you accept. A phone number with spaces, a date as "12 Mar", an email with a trailing space or capitals, a pasted card number with dashes: all are valid input, normalised on blur, never rejected for format. Errors are for what's actually wrong, and say how to fix it.
121
+ - **Recognition over recall** — seeing beats remembering. Recent and frequent items lead a picker; a command palette lists what it can do before the reader types; labels stay visible above fields (a placeholder that vanishes on typing is recall); a filter shows its current value, not just its name.
122
+ - **Tesler's law** — every task carries some complexity that can't be removed, only moved. Move it into the product: default to the likely answer, infer what can be inferred (the country from the postcode, the time zone from the device), and never ask for what the product already knows.
123
+
124
+ ### 17. A flow shows how near the end is, and the end is designed
125
+
126
+ **A task of several steps shows where the reader is and how much is left, and its last screen is built as carefully as its first.** People speed up as a goal comes into view (the goal-gradient effect), so a visible "Step 2 of 3" or a checklist with items already ticked pulls them through, where an unmarked sequence of screens feels endless; count a step the reader has already done — the account they just made — as progress. **An experience is remembered by its most intense moment and its end** (the peak-end rule), not its average: a smooth setup that ends on a blank dashboard, or a purchase that ends on "Success", is remembered as flat. The end says what happened in the product's nouns, shows the result, and offers the next action; a failure at the peak — a payment declined, an upload lost — gets the most careful copy in the product, never a code.
127
+
128
+ → Pending states, skeletons, forgiving inputs and progress in code: `domains/patterns/actions-and-forms.md`
129
+
130
+ ## Dials
131
+
132
+ | Dial | editorial | product | expressive |
133
+ |---|---|---|---|
134
+ | Label length | 1–3 words | 1–2 words, verb first | A call to action may run to a short phrase |
135
+ | Keyboard | Tab order, skip link, Escape | Palette, list movement, single-key actions, shown in menus | Tab order and Escape |
136
+ | View memory | Reset on arrival | Remembered per page on daily working pages, switch in the header (layout law 9) | Reset on arrival |
137
+ | Optimistic updates | Rare | Default for moves, toggles, renames, reorders | Rare |
138
+ | Proof of intent for large deletes | Typed word | Typed word | Typed word |
139
+ | Empty-state copy | One sentence and the action | One line and the action | A headline, a line and the action |
140
+ | Agent steps | Collapsed to one line each | Collapsed when done; running and failed steps open | Collapsed to one line each |
141
+ | Toasts | Failures and Undo only | Failures, Undo, and results off screen | Failures and Undo only |
142
+
143
+ The `glass` preset carries no pattern dials; take them from the preset nearest the reader.
144
+
145
+ **Keyboard.** Product readers repeat; editorial and expressive readers arrive, read and leave, so a palette there is a feature nobody discovers. Every preset keeps the floor: everything reachable by Tab, Escape closing one layer.
146
+
147
+ **View memory.** A working reader returns to the same collection daily and set its view for a reason; a visiting reader didn't, and should see the collection as designed.
148
+
149
+ **Toasts.** A toast confirms what the reader can't see happen. A move they watched needs none; a save that failed, an undo window, or a result on another page does.
150
+
151
+ ## Iterating on feedback
152
+
153
+ Most product feedback is one step: "one step lighter", "25% smaller", "to 16px". The craft is making exactly that change, everywhere it applies, and writing it down.
154
+
155
+ 1. **Make the change asked, at the size asked.** Move one step on the existing ladder; don't re-pick the system. The count per row stays unless more per row was asked for.
156
+ 2. **After any change to a shared rule, sweep the product** — every route, every component on that rule — **and report what moved.** Partial updates are the commonest bug: some screens took the new value, some didn't. Ask before calling a page done, and never "fix" a page the owner has said is theirs.
157
+ 3. **Record the decision** under `## Decisions` (format: `system/design-md.md`).
158
+ 4. **Study reference products for how information is structured, not how it looks**: where icons are and aren't, where tags appear, how tables are built, what is boxed and what isn't, what aligns with what.
159
+ 5. **Wireframe a new page type before building it**, and share the wireframe.
160
+ 6. **Review on a preview harness**: the real app on mock data, every route reachable without a backend, so a whole-product sweep or audit takes minutes.
161
+
162
+ → The sweep, the report, the log entry, a reference-study worksheet and the harness: `domains/patterns/iteration.md`
163
+
164
+ ## Auditing existing code
165
+
166
+ | Law | Look for | Evidence |
167
+ |---|---|---|
168
+ | 1 | Title case or uppercase labels; buttons that are sentences; "OK", "Yes", "Submit"; generated labels shown raw | Label, file, count |
169
+ | 2 | Second lines built from event verbs; two nouns for one kind of thing | The template string; the noun pairs |
170
+ | 3 | Destructive items with icons, mid-menu or styled differently from each other | Menu, item, treatment |
171
+ | 4 | Bespoke confirm dialogs; `window.confirm`; Cancel focused for large deletes; no pending label; focus lost on close | Component, and which step is missing |
172
+ | 5 | Spinners standing in for agent steps; approvals in modals; per-message avatars | Component |
173
+ | 6 | Interim text styled as final; auto-scroll that fights the reader; ambient motion while paused; interim text in a live region | Component and the behaviour |
174
+ | 7 | Frequent actions with no key; drag with no keyboard path; Escape handlers that close more than one layer; single keys firing in inputs | Action, and its key or "none" |
175
+ | 8 | Launchers with more than two or three choices; text-labelled filter rows; long flat admin lists | Screen, choice count |
176
+ | 9 | "No … found" / "No … yet" on user data; empty states with no action | Route, and which case |
177
+ | 10 | Status work with only a list; columns out of workflow order; view memory unrecorded | Page and view key |
178
+ | 11 | Moves awaiting the server; silent rollbacks; optimistic sends or payments | Mutation and its failure path |
179
+ | 12 | Panels nobody acts on; scoped views with their own query | Panel; the two queries |
180
+ | 13 | Free text before the field that decides it; errors added beside hints; several primary buttons | Form, field order |
181
+ | 14 | Play icons with no video; hover on logos that aren't links; plain-text email addresses; copy controls built as links; "Learn more", "Click here", "Get started" beside a second call to action; sign-up fields with a placeholder for a label or no line on what arrives; conventions broken for novelty — a logo that doesn't go home, search hidden in a menu | Element, and what it promises against what it does |
182
+ | 15 | Async actions with no pending label; centred spinners over blank regions; long jobs with no progress or cancel; results shown away from where they were asked for; silent saves | Action, its wait, and what the reader sees meanwhile |
183
+ | 16 | Inputs that reject spaces, dashes, case or a trailing space; placeholders as labels; pickers with no recent items; questions the product could answer itself | Field, the input it rejects or the question it needn't ask |
184
+ | 17 | Multi-step flows with no position or count; a last screen that says only "Success" or "Done"; failure copy with a code and no next step | Flow, step, and its end state |
185
+
186
+ Click through every flow once with the keyboard alone, and once with the network throttled to failure. Report one line per finding with its evidence under `## Open issues`, and fix nothing unless asked.
187
+
188
+ ## Writing back to DESIGN.md
189
+
190
+ - **`## Patterns`**: the product's nouns, one per kind of thing; the second-line formula; the destructive items and the proof each needs; the shortcut map; which pages remember their view and which default wins; the agent-step conventions.
191
+ - **Decisions**, dated, marked by who decided, with the reason: any dial moved off the preset, a law knowingly broken, or a change a law forced that a later session might undo (marked *law*).
192
+ - **Open issues** for every law the code breaks, with evidence, removed when the fix lands.
193
+
194
+ ## Checklist
195
+
196
+ - [ ] Labels are as short as their action, read the verb, and are sentence case, generated ones included.
197
+ - [ ] Row second lines name type and origin in the product's nouns; one noun per kind of thing.
198
+ - [ ] Every destructive or session-ending item shares one treatment, last in its menu, with no icon; nothing else borrows it.
199
+ - [ ] Confirmations use the system dialog, say what is lost, scale proof to the loss, show progress and return focus.
200
+ - [ ] Agent steps are cards with name, arguments and result; approvals are inline; no per-message avatars; the panel is non-modal.
201
+ - [ ] Streaming text separates committed from forming, follows only while the reader is at the bottom, and is still when paused.
202
+ - [ ] Frequent actions have keys; every drag has a keyboard path; Escape closes one layer; single keys never fire in inputs.
203
+ - [ ] Launchers offer only the task's choices; toolbar filters are icons with tooltips; long lists group and collapse.
204
+ - [ ] Empty user data offers the first action; filtered-empty names the filter.
205
+ - [ ] Staged work has a board in workflow order beside its list; remembered views follow layout law 9 and are listed under `## Patterns`.
206
+ - [ ] Moves are optimistic with a visible rollback; sends, payments and deletes are not.
207
+ - [ ] Dashboards run overview, trends, action items, wireframed first, scoped views on the global pipeline.
208
+ - [ ] Forms lead with the deciding field; errors replace hints; sign-in is a form alone in its column, its route an `expressive` area if the art moves.
209
+ - [ ] Every element does what its form promises: play icons play, inert logos don't react, email addresses are `mailto:` links, copy controls are buttons, calls to action and link text name what happens next, sign-up fields are labelled and say what arrives and how often.
210
+ - [ ] Conventions readers know from other products hold, unless breaking one is a gain felt on first use.
211
+ - [ ] Every input is acknowledged at once; past ≈ 400ms the control says it's working, past ≈ 1s the region shows its shape, past ≈ 10s progress is determinate and cancellable; results appear where they were asked for.
212
+ - [ ] Inputs accept any reasonable form and normalise it; labels stay visible; pickers lead with recent items; the product never asks for what it already knows.
213
+ - [ ] Multi-step flows show position and what's left; the last screen names what happened, shows it and offers the next action; failures at the peak get the most careful copy.
214
+ - [ ] Long lists and long strings are chunked; launchers and toolbars show the common path and disclose the rest.
215
+ - [ ] Feedback was applied at the size asked, swept product-wide, reported, and recorded as a dated decision.
216
+ - [ ] Dials match the preset, or the difference is under `## Decisions`.
@@ -0,0 +1,78 @@
1
+ # Iterating on feedback
2
+
3
+ A product UI is mostly finished in review, not in the first build. The feedback that arrives is small and specific, and most of the work is applying it everywhere it reaches.
4
+
5
+ ## One step at a time
6
+
7
+ | Feedback | Change | Not |
8
+ |---|---|---|
9
+ | "One step lighter" | The next weight down on the existing ladder, in that context | Re-picking the weight scale |
10
+ | "Make it 25% smaller" | Scale that element by 0.75 and snap to the nearest step | Fitting more per row |
11
+ | "Bring the title to 16px" | That role to 16px, everywhere the role is used | That one instance |
12
+ | "Too cluttered" | Spacing and grouping first (the layout guide) | Colour or type |
13
+ | "Not symmetric" | Match the sibling that looks right | A third variant |
14
+ | "Looks heavy everywhere" | Step weights down one, context by context, and show the result | A new font |
15
+
16
+ When feedback is ambiguous in size, ask one question with a recommendation: "One step lighter (500 → 400), or only the card titles?"
17
+
18
+ ## The sweep
19
+
20
+ A change to a shared rule — a role's size, a weight, a colour job, a spacing step, a component's variant — isn't done when the screen in front of you looks right.
21
+
22
+ 1. **Find every use.** Grep the token, the class, the component and any raw value that should have been the token (`text-sm` where the role was meant). Raw values are where partial updates hide.
23
+ 2. **Migrate raw values to the role** while you're there, so the next change is one edit.
24
+ 3. **Walk every route** in the preview harness, light and dark, at a phone and a desktop width.
25
+ 4. **Report what moved**, one line each, and what you deliberately left:
26
+
27
+ ```
28
+ Card title weight 600 → 500 (role `type.card-title`):
29
+ - Board, list, inbox, search results: updated via the role
30
+ - Calendar event chips: were raw `font-semibold`, migrated to the role
31
+ - Dashboard figures: left at 600 — figures aren't titles (say if you want them lighter)
32
+ ```
33
+
34
+ 5. **Ask before calling a page done.** Pages the owner has said are theirs are left alone, even when the sweep would touch them; list them in the report instead.
35
+
36
+ ## The decision log
37
+
38
+ Every decision goes under `## Decisions` in `DESIGN.md`, in the format `system/design-md.md` sets.
39
+
40
+ ```markdown
41
+ - 2026-10-28 — Card titles at 500, not 600: "unnecessarily heavy" next to the pane title. (User decision.)
42
+ - 2026-10-21 — Boards remember their view per page; folders always open on folders. (User decision.)
43
+ - 2026-10-14 — More colour, each hue with one job: type, status, area, person. (Client feedback.)
44
+ ```
45
+
46
+ Read the log before changing anything it covers. A reviewer's instinct to "fix" a decided value is how a product oscillates between two versions.
47
+
48
+ ## Studying a reference
49
+
50
+ When the owner points at a product they like, study **how it structures information**, not how it looks:
51
+
52
+ | Look at | Note |
53
+ |---|---|
54
+ | Icons | Where they appear and where they don't — beside nav items but not menu items? On buttons or only in toolbars? |
55
+ | Tags and chips | What gets one and what is plain text |
56
+ | Tables | Frame or none; header treatment; row height; what's in the first column; where row actions live |
57
+ | Boxes | What is a card, what sits on the page, what is grouped by space alone |
58
+ | Alignment | What lines up with what — titles with table edges, icons with text |
59
+ | Density | Row heights, gaps, how much fits above the fold |
60
+ | Copy | Label length, case, how second lines and empty states read |
61
+
62
+ Then apply the structure in the project's own tokens. Copying the look imports a second system; copying the structure keeps one.
63
+
64
+ ## Wireframe first
65
+
66
+ A new page type — a dashboard, a settings area, a board — starts as a wireframe: boxes and words, no colour, shareable as a link or an image. It settles what's on the page and in what order, which is the expensive argument, before any styling makes it costly to change. Keep the wireframe beside the build until the page ships.
67
+
68
+ ## The preview harness
69
+
70
+ The real app, on mock data, with every route reachable and no backend:
71
+
72
+ - **A data layer the app already calls**, swapped at the boundary — a mock adapter, a request interceptor, or fixtures behind the same hooks — never a fork of the components.
73
+ - **Fixtures that cover the states**: empty, one item, many, very long names, every status, an error, a slow response. A switch per state (a query parameter or a dev menu) lets a review jump to it.
74
+ - **A route index**: one page listing every route and state, so a sweep can visit all of them in order.
75
+ - **Typed against the real models**, so the harness breaks at build time when the data shape changes rather than drifting quietly.
76
+ - **Signed in by default** with a demo identity, and sign-out returning to the sign-in screen, so the auth screens are reviewable too.
77
+
78
+ With it, a whole-product sweep or a rendered audit (the type guide, the layout guide) takes minutes instead of needing a staging account and real data.
@@ -0,0 +1,108 @@
1
+ # The display tier
2
+
3
+ Only `expressive` has one: the single oversized headline on a landing or campaign page, read first and perhaps alone. This file derives its size and covers leading, tracking, weight, wrapping and loading at that size.
4
+
5
+ ## Why it isn't body × ratio
6
+
7
+ Every other role is sized against its neighbours; a display headline is read against **the space it fills**, holding a line of a certain length across a certain column. Taken from the body by ratio, it is timid in a wide hero or five lines on a phone. The inputs are the column and the headline, not the ladder.
8
+
9
+ ## The derivation
10
+
11
+ **1. Decide characters per line.** Roughly 12–24, over two or three lines at most; under 12 every break is a design decision, over 24 it's a large paragraph. Pick a target for each end:
12
+
13
+ - widest: 18–24 characters (two lines of a 40-character headline)
14
+ - narrowest: 12–16 characters (three lines on a phone)
15
+
16
+ Use real headlines; from a CMS, the 90th-percentile length, not the average — the long ones break the hero.
17
+
18
+ **2. Measure the family's average advance**, in `em`, at the weight you will set. Grotesques run about 0.5–0.55em, condensed faces 0.4–0.45, wide faces 0.6 and over — but measure, with real headline copy for its real mix of spaces, capitals and round letters:
19
+
20
+ ```js
21
+ // After the font has loaded.
22
+ const ctx = document.createElement("canvas").getContext("2d");
23
+ ctx.font = `300 100px "Your Display Family"`; // the real weight; canvas can't read var()
24
+ const sample = "Invoices that chase themselves, so you don't have to";
25
+ const advance = ctx.measureText(sample).width / sample.length / 100; // em per character
26
+ ```
27
+
28
+ **3. Divide.** For each end of the range:
29
+
30
+ ```
31
+ size = column width ÷ (characters per line × advance)
32
+ ```
33
+
34
+ A 960px desktop column at 20 characters and 0.52em: 960 ÷ 10.4 ≈ 92px.
35
+ A 343px phone column (375px less two 16px gutters) at 14 characters: 343 ÷ 7.28 ≈ 47px.
36
+
37
+ **4. Interpolate, with a `rem` intercept.** Between the viewport where the narrow column applies (`v₁`, size `s₁`) and the one where the wide column is reached (`v₂`, size `s₂`):
38
+
39
+ ```
40
+ slope = (s₂ − s₁) ÷ (v₂ − v₁) → the vw coefficient × 100
41
+ intercept = s₁ − slope × v₁ → in px, then ÷ 16 for rem
42
+ ```
43
+
44
+ From 47px at 375 to 92px at 1280: slope 0.0497 (4.97vw), intercept 28.35px (1.772rem).
45
+
46
+ ```css
47
+ --text-display: clamp(2.9375rem, 1.772rem + 4.97vw, 5.75rem);
48
+ ```
49
+
50
+ The intercept is required: text sized in `vw` alone ignores zoom and fails WCAG 1.4.4. Keep the maximum within about 2.5 × the minimum, or a 200% zoom can't double the size inside the clamp.
51
+
52
+ **5. If the hero isn't the viewport, use container units.** In a card, split hero or sidebar layout, set `container-type: inline-size` on the hero and use `cqi` for `vw`, against container widths. In a split hero, derive from the text column, but the display must still clear 1.5 × `type.heading`; if the column can't hold that, widen the text column or stack the hero, rather than shrinking the display.
53
+
54
+ **6. Check the gap to the title.** At both ends display is at least 1.5 × `type.title`; closer, it reads as "the title got bigger". If the gap is too small, lower the title rather than raising display: display is derived, the title is a choice. On a page whose `h1` is the display — a landing page with no separate title — there is no `type.title`; check display against `type.heading` instead, at least 1.5× at every width.
55
+
56
+ ### The same headline in three families
57
+
58
+ Column and character targets held constant (960px at 20 characters, 343px at 14):
59
+
60
+ | Family | Advance | Phone | Desktop | `clamp()` (375 → 1280) |
61
+ |---|---|---|---|---|
62
+ | Condensed grotesque | 0.43em | 57px | 112px | `clamp(3.5625rem, 2.14rem + 6.08vw, 7rem)` |
63
+ | Neutral grotesque | 0.52em | 47px | 92px | `clamp(2.9375rem, 1.772rem + 4.97vw, 5.75rem)` |
64
+ | Wide serif | 0.60em | 41px | 80px | `clamp(2.5625rem, 1.55rem + 4.31vw, 5rem)` |
65
+
66
+ A condensed face earns a larger size for the same line, a wide one a smaller; a ratio off the body would have given all three one number and broken two.
67
+
68
+ ## Leading at display sizes
69
+
70
+ 0.95–1.05: the x-height makes a tight line box read generously, and at body leading a headline reads as separate lines, not one object. Before going below 1:
71
+
72
+ - **Accents and descenders.** Under 1, a line box can clip diacritics above and descenders below, especially with `overflow: hidden` on the hero. Test with "Ågj" and a ringed capital.
73
+ - **Mixed scripts.** A headline that may be translated into a script with taller marks takes the top of the range.
74
+
75
+ ## Tracking at display sizes
76
+
77
+ −0.03 to −0.045em, in `em` so it scales with the fluid size; looser families and larger sizes need more. A variable family with an optical-size (`opsz`) axis tightens and thins itself: set `font-optical-sizing: auto` and start at the gentle end.
78
+
79
+ Never give the title or heading display tracking "for consistency": tracking corrects for size, and the title is a different size.
80
+
81
+ ## Weight at display sizes
82
+
83
+ `weight.display` is its own role because the right weight at 90px is rarely right at 20. Above about 48px a 400 looks heavy, and a 300 — illegitimate wherever text is read at length — belongs here; a heavy weight as brand voice is also defensible. Either way it is one weight, one job, never reused below the tier.
84
+
85
+ A separate `family.display` is allowed in `expressive` for a register the text family can't carry — a wordmark's voice, a serif headline over sans text. One family, one role: in section headings, it makes two display tiers.
86
+
87
+ ## Wrapping
88
+
89
+ `text-wrap: balance` on display and title, so a headline doesn't end in an orphan. No manual `<br>`s: right at one width, wrong at every other. A phrase that must not break gets a non-breaking space.
90
+
91
+ ## Loading a display face
92
+
93
+ A display swap is the most visible there is: a two-line headline can become three and push the hero down. Everything in `numerals-and-loading.md` applies with more force:
94
+
95
+ - Preload the display file for the hero's weight; it is on the first frame's critical path.
96
+ - Metric-match its fallback on `size-adjust` **and** the vertical overrides; a width-only match still changes line height.
97
+ - Subset it. A dozen words need Latin at most, often less.
98
+
99
+ ## One display moment
100
+
101
+ Law 3: there is one loudest thing. Every section opener at display size flattens the hierarchy — all shout, none is heard. Openers are `type.heading`; for presence, give them space, not size.
102
+
103
+ ## Failures worth recognising
104
+
105
+ - **The big title.** Display at 1.2 × title: hero and page heading blur, and the page looks under-designed, not restrained.
106
+ - **The phone wall.** Derived at desktop only, clamped at 64px minimum: a 40-character headline fills a phone with five lines.
107
+ - **The zoom fail.** `font-size: 7vw`. Right at every width, unchanged at 200% zoom.
108
+ - **The reflow.** No metric-matched fallback: the headline swaps, gains a line, and the call to action jumps 90px as the visitor reaches for it.