@erclx/canon 5.60.0 → 5.62.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 (65) hide show
  1. package/CHANGELOG.md +23 -0
  2. package/README.md +1 -1
  3. package/claude/.claude-plugin/plugin.json +1 -1
  4. package/claude/skills/canon-cli/SKILL.md +2 -1
  5. package/claude/skills/design-identity/references/card-route.md +3 -3
  6. package/claude/skills/design-record/references/design.md +19 -0
  7. package/claude/skills/document-health/REQUIREMENT.md +1 -1
  8. package/claude/skills/document-health/SKILL.md +6 -5
  9. package/claude/skills/document-health/references/axes.md +3 -1
  10. package/claude/skills/review-ui/REQUIREMENT.md +1 -1
  11. package/claude/skills/review-ui/SKILL.md +3 -2
  12. package/claude/skills/review-ui/references/post.md +2 -2
  13. package/claude/skills/target-update/SKILL.md +9 -1
  14. package/claude/skills/ui-checklist/REQUIREMENT.md +1 -1
  15. package/claude/skills/ui-checklist/SKILL.md +12 -0
  16. package/claude/skills/ux-audit/SKILL.md +1 -1
  17. package/docs/agents/install-and-sync.md +45 -11
  18. package/docs/agents/records.md +1 -1
  19. package/docs/index.md +1 -1
  20. package/docs/target/migrations.md +7 -9
  21. package/docs/target/sync.md +20 -1
  22. package/docs/workflow/ai-workflow.md +1 -1
  23. package/docs/workflow/index.md +3 -3
  24. package/docs/workflow/parallel-features.md +1 -1
  25. package/docs/workflow/visual-design-workflow.md +58 -32
  26. package/governance/detect.toml +8 -0
  27. package/governance/ledger/ui/a11y.md +8 -6
  28. package/governance/ledger/ui/keyboard.md +20 -21
  29. package/governance/ledger/ui/pointer.md +24 -0
  30. package/governance/ledger/ui/surface-capture.md +12 -12
  31. package/governance/ledger/ui/ux-completeness.md +11 -23
  32. package/governance/ledger/ui/ux-controls.md +26 -0
  33. package/governance/rules/ui/a11y.md +2 -0
  34. package/governance/rules/ui/keyboard.md +4 -7
  35. package/governance/rules/ui/pointer.md +23 -0
  36. package/governance/rules/ui/surface-capture.md +5 -5
  37. package/governance/rules/ui/ux-completeness.md +2 -10
  38. package/governance/rules/ui/ux-controls.md +25 -0
  39. package/governance/splits.toml +15 -0
  40. package/package.json +2 -1
  41. package/src/commands/driver.ts +29 -3
  42. package/src/commands/migrate.ts +7 -0
  43. package/src/commands/records.ts +3 -2
  44. package/src/commands/sync.ts +3 -1
  45. package/src/commands/tooling.ts +329 -10
  46. package/src/design/audit.ts +25 -1
  47. package/src/design/budget.ts +2 -1
  48. package/src/design/document.ts +5 -1
  49. package/src/design/themes.ts +176 -0
  50. package/src/design/tokens.ts +3 -0
  51. package/src/driver/drive.ts +17 -1
  52. package/src/driver/probes/axe.ts +195 -0
  53. package/src/driver/steps.ts +20 -1
  54. package/src/gate/measures.ts +9 -3
  55. package/src/gov/adapter.ts +53 -4
  56. package/src/gov/splits.ts +36 -0
  57. package/src/markdown/structure.ts +4 -1
  58. package/src/migrate/records.ts +46 -7
  59. package/src/records/stale-canonical.ts +2 -2
  60. package/src/sync/check.ts +9 -1
  61. package/src/sync/stamp.ts +135 -11
  62. package/src/tooling/disposition.ts +167 -0
  63. package/src/tooling/inject.ts +17 -2
  64. package/src/tooling/scan.ts +86 -7
  65. package/src/tooling/stamp.ts +51 -7
package/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## [5.62.0](https://github.com/erclx/canon/compare/v5.61.0...v5.62.0) (2026-10-24)
4
+
5
+
6
+ ### Features
7
+
8
+ * **governance:** cover more WCAG criteria in the ui rules ([#2677](https://github.com/erclx/canon/issues/2677)) ([25b6190](https://github.com/erclx/canon/commit/25b619039aa3656e239385713f796c5e34810485))
9
+ * **governance:** name the partner of a split rule on sync ([#2681](https://github.com/erclx/canon/issues/2681)) ([1d6703e](https://github.com/erclx/canon/commit/1d6703e74deb7968b0121b5e3148e474ed836c89))
10
+ * **tooling:** let a project decline or customize one golden file ([#2675](https://github.com/erclx/canon/issues/2675)) ([41c5ec0](https://github.com/erclx/canon/commit/41c5ec042a8d50b968e853a175f45eac5a59703c))
11
+
12
+ ## [5.61.0](https://github.com/erclx/canon/compare/v5.60.0...v5.61.0) (2026-10-24)
13
+
14
+
15
+ ### Features
16
+
17
+ * **design:** check themes and add a review point ([#2673](https://github.com/erclx/canon/issues/2673)) ([8cdaa5c](https://github.com/erclx/canon/commit/8cdaa5c001b2d783108c5ce6d70da0b6cca0719c))
18
+ * **driver:** add an axe probe that gates the UI review ([#2672](https://github.com/erclx/canon/issues/2672)) ([785e474](https://github.com/erclx/canon/commit/785e474b76eae4f41a55fd588df0b3691756742d))
19
+ * **migrate:** drop a covered ignore line from the records sweep ([#2668](https://github.com/erclx/canon/issues/2668)) ([188bee8](https://github.com/erclx/canon/commit/188bee8c0061045e46e660da50422b5d5673cf14))
20
+
21
+
22
+ ### Bug Fixes
23
+
24
+ * **markdown:** measure a bullet without its list marker ([#2670](https://github.com/erclx/canon/issues/2670)) ([c7b9d70](https://github.com/erclx/canon/commit/c7b9d70fc522a91dc9b2b058481d1eb984abd3ad))
25
+
3
26
  ## [5.60.0](https://github.com/erclx/canon/compare/v5.59.0...v5.60.0) (2026-10-24)
4
27
 
5
28
 
package/README.md CHANGED
@@ -104,7 +104,7 @@ Working with an agent rather than reading this yourself? It loads `CLAUDE.md` at
104
104
  - [Operating model](docs/workflow/operating-model.md): orchestrator, planner, and worker roles for building across parallel sessions
105
105
  - [Skill map](docs/workflow/skill-map.md): plugin skills grouped by the moment a project meets them
106
106
  - [Skill map on request](docs/workflow/skill-map-on-request.md): plugin skills that serve a request rather than a moment
107
- - [Visual design workflow](docs/workflow/visual-design-workflow.md): tiered guide for design authoring
107
+ - [Visual design workflow](docs/workflow/visual-design-workflow.md): how canon plans, builds, and reviews a UI
108
108
  - [Target projects](docs/target/projects.md): scaffold, add a domain later, sync upstream drift
109
109
  - [Agents](docs/agents/index.md): CLI flags, exit codes, and JSON output shapes
110
110
  - [Docs index](docs/index.md): every reference doc in this repo
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "canon",
3
3
  "description": "Runs your agent's work and keeps its code good, on one setup for every repo.",
4
- "version": "5.60.0",
4
+ "version": "5.62.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -83,7 +83,8 @@ Read the doc each line names before running one of these. `canon docs install-an
83
83
 
84
84
  - `canon sync` runs every installed domain's sync, so it carries each risk below.
85
85
  - `canon gov sync` replaces a rule you edited and deletes a rule the toolkit retired, edited or not.
86
- - `canon tooling sync --write` replaces every golden config a stack ships. Seeds are copy-once. Nothing writes without the flag. See `canon docs sync`.
86
+ - `canon tooling sync --write` replaces a golden config canon changed and holds one you edited until `--replace <path>` names it. Nothing writes without the flag. See `canon docs sync`.
87
+ - `canon tooling inject` replaces every golden config a stack ships, skipping only a declined or customized path.
87
88
 
88
89
  ### Migrate and indexes
89
90
 
@@ -21,11 +21,11 @@ Check for a `card.config.mjs` with a `card-src/pages/og-card.astro` beside it. W
21
21
 
22
22
  ```plaintext
23
23
  No card route installed, so the card shipped as a PNG this run.
24
- `canon tooling sync astro . --write` installs the route, and it replaces every other
25
- golden config in the astro stack, so run it when you mean to sync the stack.
24
+ `canon tooling sync astro . --write` installs the route, and it writes the astro
25
+ stack's new and stale configs, so run it when you mean to sync the stack.
26
26
  ```
27
27
 
28
- Never run that sync to obtain the route, since it copies the whole stack over the project's own framework, lint, typescript, and test configs. Never hand-write the scaffold either, since a copy written here drifts from the one the stack maintains.
28
+ Never run that sync to obtain the route, since it writes the stack's configs beside the project's own framework, lint, typescript, and test configs, holding only the ones the project edited. Never hand-write the scaffold either, since a copy written here drifts from the one the stack maintains.
29
29
 
30
30
  ## Writing and capturing the route
31
31
 
@@ -14,6 +14,7 @@ Applies to `canon/DESIGN.md`. Captures visual intent and the decisions behind ho
14
14
  - [What goes in](#what-goes-in)
15
15
  - [What does not go in](#what-does-not-go-in)
16
16
  - [Format](#format)
17
+ - [Frontmatter](#frontmatter)
17
18
  - [Prose budget](#prose-budget)
18
19
  - [Sections](#sections)
19
20
  - [The uncertainty tag](#the-uncertainty-tag)
@@ -56,6 +57,17 @@ Everything below applies to both shapes unless it names one.
56
57
  - Plain English over technical notation. If a section could be removed and the developer would still build correctly from the stylesheet and code alone, remove it.
57
58
  - Keep table headers and role names intact so the parser can read the tables.
58
59
 
60
+ ## Frontmatter
61
+
62
+ Both fields are optional, and a record without them audits as it always did. Frontmatter never counts toward the prose budget.
63
+
64
+ - `themes:` lists each theme as the stylesheet prelude that opens it, such as `:root` or `@media (prefers-color-scheme: dark)`.
65
+ - `canon design audit` checks every color property `## Colors` names against each theme, and reports a theme that omits one or opens no block.
66
+ - `reviewed:` is the day the record was last read whole, as `YYYY-MM-DD`. `canon records stale canonical` returns it with the releases tagged since.
67
+ - No skill step sets `reviewed:`, so whoever reads the record whole updates it.
68
+
69
+ Quote style and spacing may differ between the record and the stylesheet. A declaration inside a declared media query counts for the query alone, and `:root` never matches `:root[data-theme='dark']` by containment. A values record renders `reviewed:` from its token module and declares no `themes:`.
70
+
59
71
  ## Prose budget
60
72
 
61
73
  The record is a lookup, so its prose stays short enough to scan. Why a rule was chosen belongs in the project's context entry for design, never here.
@@ -113,6 +125,13 @@ A prose section takes its uncertainty inline instead, in a sentence saying what
113
125
  The template is the intent record. The column headers are read by exact key, so they stay verbatim. Row names are not, which leaves a project free to rename a role, add one, or drop one it has no use for.
114
126
 
115
127
  ```markdown
128
+ ---
129
+ themes:
130
+ - ':root'
131
+ - ":root[data-theme='dark']"
132
+ reviewed: YYYY-MM-DD
133
+ ---
134
+
116
135
  # Design
117
136
 
118
137
  Values live in `<path to the stylesheet>`. This record holds the intent behind them.
@@ -17,7 +17,7 @@ Length, placement, and staleness answer to different evidence, and one list hand
17
17
  - Propose a split or an owner as a structure line for each overgrown entry and doubled or unnamed coverage finding, with the headings and the reason
18
18
  - Ship the staleness axis as a reading rather than cutting it for lacking a verb
19
19
  - Report a refusal, an absent key, or a missing subcommand as an unread axis naming the cause, and say which layer answered the classifier sweep
20
- - Run standalone, reading the requirements record for drift with its reminder, and the design record through `canon design audit --json`, routing its findings to `design-record`
20
+ - Run standalone, reading the requirements record for drift with its reminder, and the design record through `canon design audit --json` and its review reminder, routing findings to `design-record`
21
21
 
22
22
  ## Must not
23
23
 
@@ -68,7 +68,7 @@ A refusal, an absent key, and a subcommand the project's binary does not carry e
68
68
  canon design audit --json
69
69
  ```
70
70
 
71
- The record carries `prose`, the lines past the budget with their counts, and `tokens`, the custom properties the stylesheet does not declare. `${CLAUDE_SKILL_DIR}/references/axes.md` holds how each reads and where it routes. Skip the verb when the scope excludes the record or the record is `null`.
71
+ The record carries `prose`, the lines past the budget, and `tokens`, the custom properties the stylesheet lacks. Where the record declares `themes:`, `tokens.themes` adds one cell per theme. `${CLAUDE_SKILL_DIR}/references/axes.md` holds how each reads and where it routes. Skip the verb when the scope excludes the record or the record is `null`.
72
72
 
73
73
  ## Step 3: read the staleness axis
74
74
 
@@ -76,15 +76,15 @@ No verb answers whether a document still describes the tree it was written about
76
76
 
77
77
  Mark every staleness finding as a reading. It is this session's judgment rather than a measurement, and a report that sets it beside two verb-backed axes without marking it implies evidence the axis does not carry.
78
78
 
79
- ### The requirements record
79
+ ### The review points
80
80
 
81
- When the scope reaches `canon/REQUIREMENTS.md`, read it for identity drift as well as for stale paths, and take the review point from the verb:
81
+ When the scope reaches `canon/REQUIREMENTS.md` or `canon/DESIGN.md`, take the review point for each record the scope reaches from the verb:
82
82
 
83
83
  ```bash
84
84
  canon records stale canonical --json
85
85
  ```
86
86
 
87
- The doc in the record carries `reviewed` and `releasesSince`. `${CLAUDE_SKILL_DIR}/references/axes.md` holds the identity claim shapes to test and how the record reads. Skip both the read and the verb when the scope excludes the doc.
87
+ Each doc in the record carries `reviewed` and `releasesSince`. Skip the verb when the scope reaches neither record. When the scope reaches the requirements record, also read it for identity drift as well as stale paths, with the claim shapes in `${CLAUDE_SKILL_DIR}/references/axes.md`.
88
88
 
89
89
  Nothing triggers this review but a request. A finished review hands the rewrite to the operator, and its last edit sets `reviewed:` in the doc's frontmatter to the day the operator's answers landed. This run writes neither.
90
90
 
@@ -118,6 +118,7 @@ When Step 3 read the canonical records, add one reminder line per doc the verb r
118
118
 
119
119
  ```markdown
120
120
  `canon/REQUIREMENTS.md`: reviewed <date>, <n> releases since.
121
+ `canon/DESIGN.md`: reviewed <date>, <n> releases since.
121
122
  ```
122
123
 
123
124
  Write `reviewed value is not a date: <value>` for a doc carrying `invalidReviewed`, and `no release tags to count` in place of the count when `tagged` is false. Name the unread cause instead when the verb refused or the binary carries no `canonical` kind.
@@ -136,7 +137,7 @@ Keep the evidence line under the table on every run. A table read without it fla
136
137
 
137
138
  When Step 2 read the design record, add a row for `canon/DESIGN.md`, with both findings `measured`.
138
139
 
139
- - Put the `prose` findings in the Length cell and the `tokens` findings in the Placement cell
140
+ - Put the `prose` findings in the Length cell and the `tokens` findings in the Placement cell, naming the selector of each `tokens.themes` cell that omits a property or matched no block
140
141
  - Write `unread: <reason>` in the Placement cell when `tokens.status` reads `unchecked`, since a check that could not run is no pass
141
142
  - Write `n/a: <reason>` there when it reads `not-applicable`, a values record with no property to compare
142
143
  - Route every finding to `design-record`, never to `document-repair`
@@ -89,6 +89,8 @@ The record is exempt from the length and placement verbs above, so this is the o
89
89
  - `tokens.status: "not-applicable"`: a values record, which carries `Value` where an intent record carries `Property`. Report it as not applicable with the `reason`.
90
90
  - `record: null`: the project holds no design record, so there is nothing to report
91
91
 
92
+ `tokens.themes` is present only where the record declares `themes:`, with one cell per theme carrying its `selector`, `matched`, and `missing`. A cell with `matched: false` means no block opens that selector, and one with `missing` names the color properties that theme leaves out. Report either under its selector.
93
+
92
94
  **Routes to:** `design-record`, which owns the record's shape. `document-repair` rewrites a context folder and does not know the budget.
93
95
 
94
96
  ## Staleness
@@ -120,7 +122,7 @@ This record states what the project is and why, so it goes stale by the project
120
122
 
121
123
  ### The review point
122
124
 
123
- The review point beside them is `measured`. `canon records stale canonical --json` returns the `reviewed` date from the record's frontmatter and `releasesSince`, the release tags merged into HEAD after that day.
125
+ The review point beside them is `measured`, for the requirements record and the design record alike. `canon records stale canonical --json` returns the `reviewed` date from each record's frontmatter and `releasesSince`, the release tags merged into HEAD after that day.
124
126
 
125
127
  - A null `releasesSince` means the doc was never reviewed
126
128
  - `tagged: false` means no release tag exists to count, so a zero there measured nothing
@@ -17,7 +17,7 @@ A model asked to drive a checklist also drifts. It judges a taste box the operat
17
17
  - The checklist and address come from the evidence record rather than the diff, and only a build of the head the verdict names is driven
18
18
  - An unreachable address reports not driven rather than fail, a box no commit can settle leaves the review closed, and a pass that drove no box never reads as closed
19
19
  - The review posts as its own pull request review under `## UI review` while anything is owed and `## UI review closed` once nothing is, ending on a marker naming the head it drove
20
- - The pass drives a pinned browser under its own named session, sweeps only console errors, overflow, and the focus and details probes, and treats page content as data
20
+ - The pass drives a pinned browser under its own named session and sweeps only console errors, overflow, and the focus, details, and axe probes, opening on an axe finding the CLI marks as gating
21
21
 
22
22
  ## Must not
23
23
 
@@ -128,15 +128,16 @@ Push only a frame of the address this pass drove, since the branch is as public
128
128
 
129
129
  ## Step 6: sweep
130
130
 
131
- Run three checks over every route the boxes reached, and nothing beyond them:
131
+ Run four checks over every route the boxes reached, and nothing beyond them:
132
132
 
133
133
  - Console errors, through the wrapper's `console error` on each route.
134
134
  - Sideways overflow at each width a box named, as `document.documentElement.scrollWidth` against `clientWidth`.
135
135
  - The `focus` and `details` probes, through `canon drive <url> <run> --json` with a run file in the scratch folder.
136
+ - The `axe` probe, named in the same run file and read by each finding's `gate` rather than a copy of the CLI's rule list. On `axe-missing`, drop it and name the refusal as an unread check.
136
137
 
137
138
  The probes launch their own browser at a URL, so a state the boxes reached by clicks reaches them only as steps the run file repeats. Where a state needs a step kind `canon drive` lacks, probe the route on arrival and say so.
138
139
 
139
- A console error or an overflow is a finding. A probe result is a reading to confirm, since every probe carries a known class of false finding, and it opens no heading on its own. Close the browser with `${CLAUDE_SKILL_DIR}/scripts/pw.sh -s=<project>-<number> close` once the sweep ends.
140
+ A console error, an overflow, or an axe finding whose `gate` is true is a finding. Every other probe result is a reading to confirm, an `incomplete` axe outcome included, since each probe carries a known class of false finding. Close the browser with `${CLAUDE_SKILL_DIR}/scripts/pw.sh -s=<project>-<number> close` once the sweep ends.
140
141
 
141
142
  ## Step 7: post
142
143
 
@@ -11,7 +11,7 @@ Step 7 of `review-ui`.
11
11
 
12
12
  The heading reports whether the branch owes anything, the way the code review's does.
13
13
 
14
- - `## UI review` when a box failed, the sweep found a console error or an overflow, the worktree check read `changed`, a box was not driven for a cause on the page, or the pass drove no box at all.
14
+ - `## UI review` when a box failed, the sweep raised a finding Step 6 names, the worktree check read `changed`, a box was not driven for a cause on the page, or the pass drove no box at all.
15
15
  - `## UI review closed` otherwise.
16
16
 
17
17
  A needs-eyes box leaves the review closed, and so does a box not driven for a cause off the page, such as an address that did not answer or a `(local only)` box on a hosted preview, as long as something else was driven.
@@ -77,7 +77,7 @@ Driven at `<short-sha>` on <address>, rendered by <renderer>.
77
77
 
78
78
  **Sweep**
79
79
 
80
- - <route>, <width>: <console error, overflow, or probe reading to confirm>
80
+ - <route>, <width>: <console error, overflow, axe gate finding with its rule, or probe reading to confirm>
81
81
 
82
82
  **Guessed wording**
83
83
 
@@ -68,6 +68,8 @@ Target update: `<path>`, canon `<installed>` to `<running>`.
68
68
 
69
69
  **Drift:** <each non-empty cause from sync --check, with its count and paths>
70
70
 
71
+ **Tooling holds:** <each tooling config the write would hold, being a drifted entry whose `cause` is `edited` or `unattributed`, then each `dispositions` entry, declined ones by path and customized ones marked when `moved` is true, or omitted when none>
72
+
71
73
  **Where it lands:** <the linked worktree this run is already in, or the worktree `.claude/worktrees/<name>/` it will enter, or the branch it will create in place when the operator declines a worktree>
72
74
 
73
75
  **Calls for you:** <each judgment call below this run meets, with the default this skill takes>
@@ -122,7 +124,13 @@ The record tree is untracked, so a `record-tree` rewrite has no undo. Run `canon
122
124
 
123
125
  ## Step 5: sync
124
126
 
125
- Run `canon sync .`, then `canon tooling sync <stack> . --write` when the tooling diff named files to change. Treat each drift cause the way the preview stated:
127
+ Run `canon sync .`, then `canon tooling sync <stack> . --write` when the tooling diff named files to change. The write holds every config the preview listed under tooling holds.
128
+
129
+ - Add `--replace <path>` only for a held path the operator named in the reply, never for the whole list the write prints
130
+ - Offer `canon tooling customize <path>` for a held path the operator wants kept, and `canon tooling lift <path>` for a moved customized one they want to take
131
+ - Leave a declined path alone, since no write reaches it
132
+
133
+ Treat each governance drift cause the way the preview stated:
126
134
 
127
135
  | Cause | This skill |
128
136
  | --------------------------- | ------------------------------------------------------------------------------------------------ |
@@ -13,7 +13,7 @@ A file under a gitignored folder is unreadable to a pull request reviewer, and a
13
13
 
14
14
  ## Must
15
15
 
16
- - Every change is split into visual-only and automatable before anything is written
16
+ - Every change is split into visual-only and automatable before anything is written, and a behavior the review's engine cannot read, such as a keyboard trap, gets a box of its own
17
17
  - The checklist covers the visual-only half, each item an action and the result a person looks for, with its route, width, and a `(taste)` or `(local only)` marker where one applies
18
18
  - Every automatable change shipping with no test is named, with the layer `test-craft` would place it at, in the same file
19
19
  - The file is written to the `.canon/tmp/handoff/ui-checklist/<slug>.md` handoff at the main worktree root, overwriting
@@ -46,6 +46,18 @@ When the plan cites `canon/design/<surface>/` under `**Design references:**`, na
46
46
 
47
47
  The checkbox stays `- [ ]` followed by the action and the result, since the evidence comment reads boxes by that shape. Only the UI reviewer ticks a box, stamping it with the commit it passed at, and the comment carries that tick forward only while the head it names is current.
48
48
 
49
+ ## Boxes no engine reads
50
+
51
+ The UI review runs axe over every route it reaches, so a box restating what axe checks, such as contrast, a missing name, or a page title, adds a drive and catches nothing. Derive a box instead for each behavior below that the changed surface carries:
52
+
53
+ - **Hover content.** Content shown on hover or focus stays while the pointer moves onto it, and Escape dismisses it, per WCAG 1.4.13.
54
+ - **Keyboard trap.** Tab and Shift+Tab leave every widget the change added, per WCAG 2.1.2.
55
+ - **Outside press.** A menu, popover, or dialog the change added closes on a press outside it and when focus leaves it.
56
+ - **Change on input.** Choosing an option or typing changes no route and moves no focus until the person submits, per WCAG 3.2.2.
57
+ - **Clipped focus ring.** Tab to each control the change added and judge from the capture whether a parent clips its ring, per WCAG 2.4.7.
58
+
59
+ Write each in the box shape above, naming the control. A surface carrying none of these behaviors takes no derived box.
60
+
49
61
  ## The missing-test list
50
62
 
51
63
  Name every automatable change this branch ships with no test, one per line, with the layer `test-craft` would place it at. Do not write the test or read the project's test config to write one against.
@@ -36,7 +36,7 @@ For each surface, look for:
36
36
  3. **Inconsistencies**: spacing, tone of voice, interaction patterns, icon use, keyboard affordances
37
37
  4. **Roughness in daily use**: friction, redundant steps, unclear affordances, ambiguous labels
38
38
 
39
- Use `canon/DESIGN.md` as ground truth for intent where it exists. Observations only, no implementation suggestions or fixes.
39
+ Use `canon/DESIGN.md` as ground truth for intent where it exists. Observations only, no implementation suggestions or fixes. Take what consistent interaction means from the `ui` rules in `.claude/rules/`, and skip that when the project installed none.
40
40
 
41
41
  Where Step 1 found a committed capture for this surface, judge composed output (spacing, overlap, contrast, wrapping) against it rather than inferring layout from markup. Where none exists, judge from source and say so in that surface's findings, since a source read cannot see what only a render shows.
42
42
 
@@ -32,7 +32,7 @@ A flat installed-rule layout makes `gov sync` report "No governance surfaces fou
32
32
  - A retired `.claude/GOV.md` is removed
33
33
  - A binary older than the stamp holds every rule it has no source for and asks for an upgrade
34
34
 
35
- After a rename the new name shows as `missing` when the stamp records a stack, and `gov install <stack>` adds it.
35
+ After a rename the new name shows as `missing` when the stamp records a stack, and `gov install <stack>` adds it. A partner `governance/splits.toml` names shows as `missing` when the tree holds the split rule and lacks the partner, and `gov install --add <partner>` adds it.
36
36
 
37
37
  `canon gov regen [--root <path>]` runs against the toolkit root alone. It rewrites `.claude/rules/canon/` and `.claude/rules/internal/` from `internal/`, and exits 1 when the record names something that does not resolve.
38
38
 
@@ -68,6 +68,40 @@ After a rename the new name shows as `missing` when the stamp records a stack, a
68
68
  - `present` marks a copy an earlier sync left, which is reported and never deleted
69
69
  - `canon tooling sync <stack> [target]` writes only under `--write`. `--check` reports and exits 0, and a headless run with neither flag exits 1.
70
70
 
71
+ ### Drift cause and the write hold
72
+
73
+ A `drifted` entry in `configs` carries a `cause`, read against the golden hash the last write recorded for that path.
74
+
75
+ - `stale`: the target still hashes to the record, so canon moved and the project did not. `--write` replaces it.
76
+ - `edited`: the target no longer hashes to the record. `--write` holds it.
77
+ - `unattributed`: no hash is recorded, as on every target synced before hashes existed. `--write` holds it.
78
+
79
+ A config no stack in the recorded chain ships is a first install and is never held, so a fresh scaffold takes every golden config of a stack new to it, even after `canon init` recorded `base`.
80
+
81
+ A held path stays drifted, so the next headless run still exits 1. Pass `--replace <path>` once per path to take the golden copy, as the command the write prints does. A `--replace` naming no config the chain writes refuses with `unknown-replace`.
82
+
83
+ ### Dispositions
84
+
85
+ `decline <path> [target] [--reason <text>]`, `customize <path> [target]`, and `lift <path> [target]` set or clear one path's disposition, each with `--json`. The path is relative to the target and resolves against the recorded chain, never a stack argument.
86
+
87
+ - A success answers `{ ok: true, action, path, entry? }`, where `entry` is the recorded `{ disposition, base?, reason? }` and is absent after a lift
88
+ - A declined or customized path leaves `configs`, `seeds`, and `withheld` for `dispositions`, as `{ rel, stack, kind, disposition, present, moved? }`
89
+ - `moved` is set on a customized config alone, and counts toward the exit once the golden copy no longer matches `base`
90
+ - Sync and inject never write a path in `dispositions`, and nothing deletes one the project still holds
91
+
92
+ A refusal answers `{ ok: false, reason, message }` with exit 1:
93
+
94
+ | `reason` | Meaning |
95
+ | ----------------------- | --------------------------------------------------------- |
96
+ | `no-chain` | No tooling chain is recorded, so run `tooling sync` first |
97
+ | `bad-path` | The path leaves the target |
98
+ | `not-golden` | No stack in the recorded chain ships the path |
99
+ | `already-held` | The path is already declined or customized, so lift it |
100
+ | `seed-not-customizable` | The path is a seed, which sync never overwrites |
101
+ | `missing-file` | `customize` named a config the target does not hold |
102
+ | `not-held` | `lift` named a path with no disposition |
103
+ | `toolkit-root` | The target is the toolkit checkout, the source of truth |
104
+
71
105
  `canon tooling inject` and `prune-gitignore` apply one stack with no scan or prompt, and the latter prints the number removed on stdout. Use `sync` unless scripting provisioning, and pass `--nested` inside an open frame.
72
106
 
73
107
  ## Whole-project sync
@@ -78,16 +112,16 @@ After a rename the new name shows as `missing` when the stamp records a stack, a
78
112
 
79
113
  `canon sync --check` writes nothing and needs no clean tree. `--json` emits the record, `--exit-code` fails CI.
80
114
 
81
- | State | Meaning |
82
- | ------------ | ------------------------------------------------------------------ |
83
- | `stale` | Still matches what the toolkit installed |
84
- | `customized` | The project edited it |
85
- | `stranded` | Sits at a path the toolkit no longer installs to |
86
- | `orphaned` | The project authored it |
87
- | `drifted` | No stamp covers it |
88
- | `missing` | Governance only. The recorded stack lists it and the tree lacks it |
89
- | `retired` | Governance only. The toolkit no longer ships it |
90
- | `renamed` | Governance only. The toolkit ships it under a new name |
115
+ | State | Meaning |
116
+ | ------------ | ----------------------------------------------------------------------- |
117
+ | `stale` | Still matches what the toolkit installed |
118
+ | `customized` | The project edited it |
119
+ | `stranded` | Sits at a path the toolkit no longer installs to |
120
+ | `orphaned` | The project authored it |
121
+ | `drifted` | No stamp covers it |
122
+ | `missing` | Governance only. The recorded stack lists it, or a split rule leaves it |
123
+ | `retired` | Governance only. The toolkit no longer ships it |
124
+ | `renamed` | Governance only. The toolkit ships it under a new name |
91
125
 
92
126
  `retired` and `renamed` count toward `--exit-code`. `orphaned` and `missing` do not.
93
127
 
@@ -58,4 +58,4 @@ Branch on `reason` in the `--json` record, not the exit code.
58
58
 
59
59
  - `canon records ordinal <kind> <slug> --claim`: reserve the next intake or groundwork ordinal
60
60
  - `canon records validate <kind> [record] --help`: where a record disagrees with its standard, plus a `structure` report that never sets the exit
61
- - `canon records stale --help`: when the requirements record was last reviewed
61
+ - `canon records stale --help`: when the requirements and design records were last reviewed
package/docs/index.md CHANGED
@@ -9,4 +9,4 @@ One-line reference for each doc in this folder.
9
9
 
10
10
  - [Agents](agents/index.md): CLI catalog and invocation rules for agents, split by command domain. Start with overview.
11
11
  - [Target](target/index.md): Scaffold, sync, and migrate a toolkit-managed project, with worked setups for three project shapes.
12
- - [Workflow](workflow/index.md): How a person and their agents run a feature, from the design tier through the parallel-session loop to the shell that launches them.
12
+ - [Workflow](workflow/index.md): How a person and their agents run a feature, from the design loop through the parallel-session loop to the shell that launches them.
@@ -20,7 +20,7 @@ canon migrate records --json
20
20
  canon migrate records --write --json
21
21
  ```
22
22
 
23
- The first line takes the `.canon/` ignore entry the verb requires. The second reports the plan and the third applies it, repointing every tracked file that cites a moved folder. A line naming an old record path on purpose keeps it with `canon-keep-record-root` on that line or the nearest non-blank line above.
23
+ The first line takes the `.canon/` ignore entry. The second reports and the third applies, repointing files that cite a moved folder and deleting a `.gitignore` line `.canon/` covers, as `dropped`. A line naming an old path keeps it via `canon-keep-record-root` on that line or the nearest non-blank one above.
24
24
 
25
25
  Then sweep the citations inside the records, which the verb above never reaches.
26
26
 
@@ -75,7 +75,7 @@ The `memory` record kind left `canon records`, so `canon records validate memory
75
75
  - `canon repo metadata`, `canon autoship`, and the `120-bash` rule are gone, and `canon gov sync` removes the rule <!-- canon-keep-retired -->
76
76
  - `memory-capture`, the `603-memory` rule, and the `memory` standard are gone, so send a lesson to the context entry that owns the fact <!-- canon-keep-retired -->
77
77
 
78
- Two held files run, `.husky/post-merge` and `.claude/hooks/pr-create-log.sh`, and a stale copy calls a missing binary or names a removed skill. Resync what the toolkit owns before sweeping what the project wrote, and run the second line once per stack.
78
+ Two held files run, `.husky/post-merge` and `.claude/hooks/pr-create-log.sh`, and a stale copy calls a missing binary or names a removed skill. Resync what the toolkit owns before sweeping what the project wrote, and run the second line once per stack, naming a held hook with `--replace`.
79
79
 
80
80
  ```bash
81
81
  canon gov sync
@@ -181,20 +181,18 @@ The verb adds `Bash(canon pr merge:*)` and `Bash(gh pr merge:*)` so a session ca
181
181
 
182
182
  ## Move the capture cases out of the harness, once
183
183
 
184
- A web project's `e2e/screenshot.ts` is a config, so sync overwrites it. It now loads `CASES` from a seeded `e2e/capture/cases.ts` that sync copies once and never overwrites. Cases edited in the old harness leave the working tree on the first sync and stay in git history.
184
+ A web project's `e2e/screenshot.ts` is a config. It now loads `CASES` from a seeded `e2e/capture/cases.ts` that sync copies once and never overwrites. A harness you edited is held on `--write`, so the new one lands only once you pass `--replace e2e/screenshot.ts`.
185
185
 
186
- - Copy your cases from the old `e2e/screenshot.ts` into `e2e/capture/cases.ts` before the next sync.
186
+ - Copy your cases from the old `e2e/screenshot.ts` into `e2e/capture/cases.ts`, then sync with `--replace e2e/screenshot.ts`.
187
187
  - Evidence now defaults to `e2e/evidence/`. Set `export const EVIDENCE_ROOT = 'evidence'` in `e2e/capture/cases.ts` to keep a committed baseline at the old path.
188
188
  - A run from a folder with no `e2e/capture/cases.ts` exits 1 and names the file.
189
189
 
190
- ## Add the keyboard rule beside a held a11y rule, once
190
+ ## Add the rules a split left behind, once
191
191
 
192
- The `a11y` rule split in two. Focus styles, Escape and shortcuts, composite widgets, focus management, and pointer target size moved to a new `keyboard` rule. `canon gov sync` refreshes only the rules a project holds, so it rewrites `a11y.md` without those bullets and never adds `keyboard.md`.
193
-
194
- A project holding `.claude/rules/canon/ui/a11y.md` adds the new rule alone:
192
+ The splits created `pointer` and `ux-controls`, and sync refreshes only held rules. `canon gov sync` now names the missing partner of `keyboard.md` and `ux-completeness.md` on every run and adds nothing itself. `a11y.md` gained two bullets and lost none, so it needs no partner.
195
193
 
196
194
  ```bash
197
- canon gov install --add keyboard --yes .
195
+ canon gov install --add pointer,ux-controls --yes .
198
196
  ```
199
197
 
200
198
  ## Group a long paths section in `CLAUDE.md`, once
@@ -44,13 +44,32 @@ Add `--json` for a machine-readable report and `--exit-code` to fail CI when a t
44
44
 
45
45
  - Seed docs such as `CLAUDE.md` and `canon/REQUIREMENTS.md`: merge by hand, keeping the project's customizations
46
46
  - Installed governance rules: `canon gov sync <path>`, which adds no new rules and reports coverage afterward
47
- - Tooling configs and seeds: read differences with `canon tooling diff <stack> <path>`, then `canon tooling sync <stack> <path> --write`, which overwrites golden configs and copies each seed once
47
+ - Tooling configs and seeds: read differences with `canon tooling diff <stack> <path>`, then `canon tooling sync <stack> <path> --write`, which holds an edited config and copies each seed once
48
48
  - Index regeneration: `canon indexes regen`
49
49
 
50
50
  After `canon gov sync` applies, it lists each rule it copied with its globs and how many project files match. A `ui/` rule adds a pointer to `/canon:ux-audit`. `--json` writes `{ "coverage": [{ "rule", "paths", "files", "ui" }] }` to stdout, empty when nothing was copied, with `files` null outside git. The report never changes the exit code.
51
51
 
52
52
  Try a scenario from [sandbox](../../canon/context/development/sandbox.md) before syncing a real project.
53
53
 
54
+ ### Held, declined, and customized golden files
55
+
56
+ `--write` replaces a golden config only when canon changed it and the project did not. A config the project edited, or one no earlier write recorded, is held and listed with a `--replace` command that takes the golden copy. A config from a stack the target never synced is a first install and is never held.
57
+
58
+ A project can also opt out of one golden file without leaving the stack. Each verb below takes the path relative to the target, as `canon tooling diff` lists it.
59
+
60
+ ```bash
61
+ canon tooling decline .github/workflows/readme-screenshot.yml --reason "no screenshots here"
62
+ canon tooling customize e2e/home.spec.ts
63
+ canon tooling lift .github/workflows/readme-screenshot.yml
64
+ ```
65
+
66
+ - `decline` keeps sync and inject from writing the file again, and leaves a copy the project still holds in place
67
+ - `customize` keeps the project's copy of a config through every write, and `diff` exits 1 once the stack ships a newer golden copy
68
+ - A seed cannot be customized, since sync never overwrites one
69
+ - `lift` hands the file back, so the next sync writes it
70
+
71
+ These verbs are the only writers of the record in `canon/config/config.json`, so never edit it there by hand.
72
+
54
73
  ## Running sync from an agent session
55
74
 
56
75
  `canon sync .` applies every domain sync, then offers a commit and pull request that needs a terminal. With no terminal on stdin the syncs apply, the git step is refused, and the command exits 0. Commit yourself. Sync refuses a dirty working tree, and `canon sync --check .` does not.
@@ -66,7 +66,7 @@ An independent session invokes `canon:review-pr` once the pull request is open,
66
66
 
67
67
  ### UI-heavy project
68
68
 
69
- Pick a design tier before the first feature session. See [visual design workflow](visual-design-workflow.md).
69
+ A UI feature runs the same plan, build, and review loop at any size. See [visual design workflow](visual-design-workflow.md).
70
70
 
71
71
  ## Skills
72
72
 
@@ -1,11 +1,11 @@
1
1
  ---
2
2
  title: Workflow
3
- subtitle: How a person and their agents run a feature, from the design tier through the parallel-session loop to the shell that launches them.
3
+ subtitle: How a person and their agents run a feature, from the design loop through the parallel-session loop to the shell that launches them.
4
4
  ---
5
5
 
6
6
  # Workflow
7
7
 
8
- How a person and their agents run a feature, from the design tier through the parallel-session loop to the shell that launches them.
8
+ How a person and their agents run a feature, from the design loop through the parallel-session loop to the shell that launches them.
9
9
 
10
10
  ## Agent surface
11
11
 
@@ -17,5 +17,5 @@ How a person and their agents run a feature, from the design tier through the pa
17
17
  - [Parallel features](parallel-features.md): Run independent features in parallel worktrees, with the records and board every session shares
18
18
  - [Skill map on request](skill-map-on-request.md): When to reach for the plugin skills that serve a request rather than a moment in a project's life
19
19
  - [Skill map](skill-map.md): When to reach for each plugin skill, grouped by the moment a project meets it
20
- - [Visual design workflow](visual-design-workflow.md): Tiered guide for design authoring with Claude Code
20
+ - [Visual design workflow](visual-design-workflow.md): How canon plans, builds, and reviews a UI, from the design reference a plan cites to the browser pass that holds a pull request open
21
21
  - [Zshrc aliases for Claude Code](zshrc-aliases.md): Shell aliases that shorten common Claude Code invocations
@@ -34,7 +34,7 @@ Assert the orchestrator role in one warm session with `canon:role-orchestrator`.
34
34
 
35
35
  - `canon records validate plans [plan]` reports a plan breaking its standard, plus `structure` misses that never set the exit, and takes `groundwork` and `intake`
36
36
  - `canon records push` and `canon records pull` carry `.canon/` to and from a private repository. See [records](../agents/records.md).
37
- - `canon records size`, `canon records prune-tmp`, and `canon records stale canonical` report folder size, idle scratch, and releases since the requirements review
37
+ - `canon records size`, `canon records prune-tmp`, and `canon records stale canonical` report folder size, idle scratch, and releases since the requirements and design records were last read
38
38
  - `canon tasks archive` moves a shipped plan to `.canon/plans/archive/` and retargets the task's `Plan:` line
39
39
 
40
40
  ## Shipping a fan-out