@erclx/canon 4.98.0 → 4.100.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 (38) hide show
  1. package/README.md +24 -6
  2. package/claude/.claude-plugin/plugin.json +1 -1
  3. package/claude/skills/auto-ship/SKILL.md +6 -4
  4. package/claude/skills/draft-and-pick/REQUIREMENT.md +1 -1
  5. package/claude/skills/draft-and-pick/SKILL.md +1 -1
  6. package/claude/skills/draft-ready/SKILL.md +3 -3
  7. package/claude/skills/git-pr/SKILL.md +41 -21
  8. package/claude/skills/test-craft/REQUIREMENT.md +1 -1
  9. package/claude/skills/test-first/REQUIREMENT.md +1 -1
  10. package/claude/skills/ui-checklist/REQUIREMENT.md +49 -0
  11. package/claude/skills/ui-checklist/SKILL.md +71 -0
  12. package/claude/skills/ux-audit/REQUIREMENT.md +1 -1
  13. package/claude/skills/ux-measure/REQUIREMENT.md +1 -1
  14. package/docs/agents/capture.md +6 -2
  15. package/docs/agents/demo.md +3 -1
  16. package/docs/agents/pr-evidence.md +34 -11
  17. package/docs/agents/pr-preview.md +4 -3
  18. package/docs/agents/tasks.md +3 -1
  19. package/docs/workflow/ai-workflow.md +4 -4
  20. package/docs/workflow/visual-design-workflow.md +2 -2
  21. package/package.json +1 -1
  22. package/scripts/core/regen-hero.sh +36 -3
  23. package/src/claude/cases/workflow.ts +3 -2
  24. package/src/commands/demo.ts +4 -3
  25. package/src/commands/pr.ts +53 -1
  26. package/src/commands/tasks.ts +16 -0
  27. package/src/demo/compile.ts +20 -0
  28. package/src/demo/drive.ts +1 -0
  29. package/src/migrate/skill-names.ts +14 -6
  30. package/src/pr/evidence.ts +36 -1
  31. package/src/sandbox/expect.ts +2 -1
  32. package/src/tasks/archive.ts +115 -3
  33. package/standards/ready.md +2 -2
  34. package/standards/tasks.md +4 -2
  35. package/tooling/web/configs/e2e/screenshot.ts +155 -11
  36. package/tooling/web/reference.md +2 -2
  37. package/claude/skills/ui-test/REQUIREMENT.md +0 -48
  38. package/claude/skills/ui-test/SKILL.md +0 -87
package/README.md CHANGED
@@ -1,6 +1,9 @@
1
1
  <div align="center">
2
2
 
3
- <img src="assets/brand/mark-accent.svg" alt="canon" width="56" height="56">
3
+ <picture>
4
+ <source media="(prefers-color-scheme: dark)" srcset="assets/brand/mark-accent.svg">
5
+ <img src="assets/brand/mark-accent-light.svg" alt="canon" width="56" height="56">
6
+ </picture>
4
7
 
5
8
  # canon
6
9
 
@@ -14,7 +17,10 @@ canon is a CLI and Claude Code plugin that stops your AI conventions drifting ap
14
17
 
15
18
  </div>
16
19
 
17
- ![The first screen of the canon landing page, a project before and after one canon init, with the file counts each step lays down](assets/evidence/arrival.png)
20
+ <picture>
21
+ <source media="(prefers-color-scheme: dark)" srcset="assets/evidence/arrival.png">
22
+ <img src="assets/evidence/arrival-light.png" alt="The first screen of the canon landing page, a project before and after one canon init, with the file counts each step lays down">
23
+ </picture>
18
24
 
19
25
  The page is one real session the toolkit ran on itself, and every count on it is read from the repo when the page builds.
20
26
 
@@ -39,7 +45,10 @@ claude plugin marketplace add https://github.com/erclx/canon
39
45
  claude plugin install canon@canon
40
46
  ```
41
47
 
42
- ![Adding the canon marketplace and installing the plugin in Claude Code](assets/evidence/install.png)
48
+ <picture>
49
+ <source media="(prefers-color-scheme: dark)" srcset="assets/evidence/install.png">
50
+ <img src="assets/evidence/install-light.png" alt="Adding the canon marketplace and installing the plugin in Claude Code">
51
+ </picture>
43
52
 
44
53
  The skills land as `/canon:<name>`. If your session was already open, run `/reload-plugins` to pick them up.
45
54
 
@@ -67,15 +76,24 @@ Each domain has a canonical source in this repo and a thin install or sync CLI o
67
76
  | Transcripts | A YouTube transcript with metadata frontmatter | `canon transcripts <url>` writes it into any repo |
68
77
  | Sandbox | Scenario scaffolds that provision an isolated project state for verifying each domain flow | `canon sandbox` |
69
78
 
70
- ![The canon catalog as a ledger of five rows, with the count each ships and a few real names: skills, rules, standards, commands, and stacks](assets/evidence/catalog.png)
79
+ <picture>
80
+ <source media="(prefers-color-scheme: dark)" srcset="assets/evidence/catalog.png">
81
+ <img src="assets/evidence/catalog-light.png" alt="The canon catalog as a ledger of five rows, with the count each ships and a few real names: skills, rules, standards, commands, and stacks">
82
+ </picture>
71
83
 
72
84
  Every count in that image is read from the catalogs when it is built, so they're what the repo ships today. A tooling stack lands as real files under version control, because a config is something your build reads and your project owns. A standard stays here and is opened by name, so there is no copy in your repo to drift from this one. Governance is the third shape. A rule with a path glob loads only when a matching path is edited, and a rule with none loads every session. Run `canon gov list` to see the glob beside each rule.
73
85
 
74
86
  Two of the outputs are documents you open rather than files you run. A `SLIDES.md` renders to a deck, and a learning workspace renders to a small site.
75
87
 
76
- ![A slide from the showcase deck rendered by canon slides render, a bulleted list under a heading on a dark warm background](examples/slides/images/showcase-dark-04.png)
88
+ <picture>
89
+ <source media="(prefers-color-scheme: dark)" srcset="examples/slides/evidence/showcase-dark-04.png">
90
+ <img src="examples/slides/evidence/showcase-light-04.png" alt="A slide from the showcase deck rendered by canon slides render, a bulleted list under a heading on a warm background">
91
+ </picture>
77
92
 
78
- ![The learning workspace listing rendered by canon teach, three workspaces with their lesson and term counts, one still a stub](examples/teach/evidence/root-listing.png)
93
+ <picture>
94
+ <source media="(prefers-color-scheme: dark)" srcset="examples/teach/evidence/root-listing.png">
95
+ <img src="examples/teach/evidence/root-listing-light.png" alt="The learning workspace listing rendered by canon teach, three workspaces with their lesson and term counts, one still a stub">
96
+ </picture>
79
97
 
80
98
  ## It runs on itself
81
99
 
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "canon",
3
3
  "description": "Automated governance, versioning, and discovery tools for Claude Code.",
4
- "version": "4.98.0",
4
+ "version": "4.100.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -117,13 +117,15 @@ The verb reads git history rather than the working tree, so a branch with nothin
117
117
 
118
118
  The verb ships with the CLI and this body ships with the plugin, matching Step 6's own fallback for the classify verb. Report that the check did not run rather than reading a missing subcommand as clean, and continue to Step 5.
119
119
 
120
- ## Step 5: UI test (conditional)
120
+ ## Step 5: UI checklist (conditional)
121
121
 
122
- If the diff touches UI files (JSX, TSX, Vue, Svelte, HTML, or CSS under `src/`), invoke `canon:ui-test`.
122
+ If the diff touches UI files (JSX, TSX, Vue, Svelte, HTML, or CSS under `src/`), invoke `canon:ui-checklist`.
123
123
 
124
- If `ui-test` produces a manual checklist, stop: `❌ UI requires visual verification. Checklist at .canon/tmp/handoff/ui-checklist/<slug>.md, which reaches the pull request once /git-ship runs. Verify manually, then run /git-ship.`
124
+ If `ui-checklist` produces a checklist, stop: `❌ UI requires visual verification. Checklist at .canon/tmp/handoff/ui-checklist/<slug>.md, which reaches the pull request once /git-ship runs. Verify manually, then run /git-ship.`
125
125
 
126
- If all UI changes are covered by tests, continue.
126
+ The stop is unchanged by that skill's shrink. The checklist is now the step's whole output rather than the visual remainder beside the tests it wrote, so the stop is the only thing that makes the operator look before `git-ship`.
127
+
128
+ If there is nothing to verify visually and nothing shipping untested, continue.
127
129
 
128
130
  ## Step 6: review
129
131
 
@@ -68,7 +68,7 @@ The refusal strings sit in the body, since the runtime loads that file and ignor
68
68
  - `plan-feature` plans the work once the answer is settled, and declares the pull request boundary its plan carries. This produces the answer to pick from and stops before the plan.
69
69
  - `ux-audit` reads source to find roughness and reports it. This takes its input from the operator and changes nothing until they pick.
70
70
  - `ux-measure` measures what a running interface costs to paint. This measures whatever a visual claim depends on, which is usually geometry or contrast rather than cost.
71
- - `ui-test` writes tests for a change already made. This runs before there is a change to test.
71
+ - `ui-checklist` writes what to look at on a change already made. This runs before there is a change to look at.
72
72
  - `draft-screencast` scripts a recording of something already built. This has nothing built yet.
73
73
  - `canon capture`, `canon serve`, and `canon drive` own the render, the address, and the probes, and are invoked rather than reimplemented.
74
74
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: draft-and-pick
3
- description: Drafts several candidates for a decision judged by looking, renders them side by side on one page, hands the operator the addresses, takes the pick through the structured question surface, and loops on the pick until they stop. Use when asked to "draft some options", "show me a few versions", "try a few variations", "mock up alternatives", "give me candidates for X", or when a choice is taste rather than correctness. Do NOT use when the request already names the answer and asks for it to be built, which is `plan-feature`. Do NOT use to read source for roughness, which is `ux-audit`, to measure what a running interface costs to paint, which is `ux-measure`, to write tests for a change already made, which is `ui-test`, to script a recording, which is `draft-screencast`, or to inspect a running app across many findings, which is `ux-walkthrough`.
3
+ description: Drafts several candidates for a decision judged by looking, renders them side by side on one page, hands the operator the addresses, takes the pick through the structured question surface, and loops on the pick until they stop. Use when asked to "draft some options", "show me a few versions", "try a few variations", "mock up alternatives", "give me candidates for X", or when a choice is taste rather than correctness. Do NOT use when the request already names the answer and asks for it to be built, which is `plan-feature`. Do NOT use to read source for roughness, which is `ux-audit`, to measure what a running interface costs to paint, which is `ux-measure`, to write what to look at on a change already made, which is `ui-checklist`, to script a recording, which is `draft-screencast`, or to inspect a running app across many findings, which is `ux-walkthrough`.
4
4
  ---
5
5
 
6
6
  # Draft and pick
@@ -73,7 +73,7 @@ Write `<main-root>/.canon/tasks/<label>-<slug>.md` by heredoc, from the label St
73
73
  canon tasks plan-link <task-stem> .canon/plans/feature-<slug>.md --json
74
74
  ```
75
75
 
76
- Branch on the record rather than on the exit. `Ready:` is defined nowhere yet, so the line orients a worker reading the task and no check reads it.
76
+ Branch on the record rather than on the exit. The `Ready:` line orients a worker reading the task, and `canon tasks archive` reads it to move the folder on ship.
77
77
 
78
78
  A task file with no row is a dropped task, so place the row in the same pass. The plan exists, so the row takes `## Run now` when `canon tasks plan-reach` reports nothing claimed and `## Up next` otherwise, with the held path in its `Waiting on` cell, per the tests in `${CLAUDE_SKILL_DIR}/../../standards/tasks.md`. Check the roster the way `canon:task-board` Step 4 does, reading `canon sessions list --self --json` for this session and `canon sessions list --json` for a row from the same repository, under another `sessionId`, whose `name` starts with `orchestrator-`. Treat a refused read as a roster read that failed.
79
79
 
@@ -106,10 +106,10 @@ Fix what the records name in the files this run wrote and re-run once. Report a
106
106
  Plan: .canon/plans/feature-<slug>.md
107
107
  Task: .canon/tasks/<label>-<slug>.md
108
108
  Row: <label> <title>, <written under ## Run now | reported to <orchestrator name> | left for the operator to place>, <reason>
109
- Archive on ship: move the folder to .canon/ready/archive/<nn>-<slug>/ by hand, then retarget the task's Ready: line
109
+ Archive on ship: canon tasks archive moves the folder to .canon/ready/archive/<nn>-<slug>/
110
110
  ```
111
111
 
112
- Name the archive move every time. `canon tasks archive` moves the task and its plan and leaves the folder live, so the move is the one step this run leaves for whoever ships.
112
+ Name the archive move every time, so whoever ships knows the verb carries the folder and needs no step of their own.
113
113
 
114
114
  ## Rules
115
115
 
@@ -152,54 +152,72 @@ rm -rf .canon/tmp/pr/body
152
152
  printf 'number=%s\nurl=%s\n' "$pr_number" "$pr_url"
153
153
  ```
154
154
 
155
- ### Post the UI checklist
155
+ ### Find the UI checklist
156
156
 
157
- `ui-test` writes a manual checklist to `.canon/tmp/handoff/ui-checklist/<slug>.md` at the main worktree root when a change needs visual verification, with `<slug>` derived per `${CLAUDE_SKILL_DIR}/../../standards/slug.md`. This step is the file's sole consumer. Resolve the main root the way `session-worktree` does (`git worktree list --porcelain | grep -m 1 '^worktree ' | cut -d' ' -f2-`, falling back to `pwd`) and check for the file there. A missing file means no checklist was produced, and there is nothing to post.
157
+ `ui-checklist` writes a visual checklist to `.canon/tmp/handoff/ui-checklist/<slug>.md` at the main worktree root when a change needs visual verification, with `<slug>` derived per `${CLAUDE_SKILL_DIR}/../../standards/slug.md`. This skill is the file's sole consumer. Resolve the main root the way `session-worktree` does (`git worktree list --porcelain | grep -m 1 '^worktree ' | cut -d' ' -f2-`, falling back to `pwd`) and check for the file there. A missing file means no checklist was produced, and the two steps below each skip their checklist half.
158
158
 
159
- When it exists, scan it against `${CLAUDE_SKILL_DIR}/../../standards/publish.md` before posting, the same as the pull request body above. Post it as its own comment on `<number>`, the number the final command above resolved, rather than folding it into the body, since a later push editing the body would overwrite checkboxes a reviewer already ticked:
159
+ When it exists, scan it against `${CLAUDE_SKILL_DIR}/../../standards/publish.md` before either step posts it, the same as the pull request body above.
160
+
161
+ Where it lands is decided by the evidence step below rather than here, since a checklist reads next to the screenshots it annotates and posting it on its own is the fallback for a branch that changed no screenshot.
162
+
163
+ ### Post the evidence comparison
164
+
165
+ Run the verb once against the number the pull request step above resolved, passing `--checklist` when the step above found a file and leaving it off when it did not:
160
166
 
161
167
  ```bash
162
- gh pr comment <number> --body-file <main-root>/.canon/tmp/handoff/ui-checklist/<slug>.md
168
+ canon pr evidence <number> --checklist <main-root>/.canon/tmp/handoff/ui-checklist/<slug>.md --json
163
169
  ```
164
170
 
165
- Run the cleanup below only once that call reports success. On a failure, stop and leave the file in place: a retry needs the checklist to still be there, and deleting it on a failed post loses the only copy with nothing landed on the pull request.
171
+ One call answers both questions because a checklist does not decide `no-evidence`. The verb reports `no-evidence` on a diff carrying no evidence image whether or not a checklist came with it, so the branch below reads the same `reason` it would have read without the flag, and the checklist is folded in only on the path that has a comparison to fold it into.
166
172
 
167
- From a linked worktree the file-editing tools refuse a main-root path, so the cleanup goes out through `Bash` as two plain commands, the file and then the folder, rather than joined by `&&`, which is refused as compound:
173
+ Pass `--checklist` only for a file that exists. The verb refuses as `unreadable-checklist` on a path it cannot read or one holding nothing, which is a caller bug rather than a transient failure, so stop and repair the path rather than posting a body with the checklist silently dropped.
174
+
175
+ Read `reason` on the record rather than the exit code.
176
+
177
+ - `no-evidence`: nothing changed under an `evidence/` segment, so there is no comparison to post and no body was rendered. Say nothing about the evidence and fall through to the checklist step below.
178
+ - `ok`: write `body` to `.canon/tmp/pr/evidence/body-<number>.md` at the main worktree root (resolved the way `session-worktree` does), then post or update the comment:
168
179
 
169
180
  ```bash
170
- rm <main-root>/.canon/tmp/handoff/ui-checklist/<slug>.md
181
+ gh pr comment <number> --body-file <main-root>/.canon/tmp/pr/evidence/body-<number>.md
171
182
  ```
172
183
 
184
+ When the record carries a `commentId`, edit that comment in place instead of posting a second one, reading the body field from the tmp file with `@`:
185
+
173
186
  ```bash
174
- rmdir <main-root>/.canon/tmp/handoff/ui-checklist 2>/dev/null || true
187
+ gh api -X PATCH repos/{owner}/{repo}/issues/comments/<commentId> -f body=@<main-root>/.canon/tmp/pr/evidence/body-<number>.md
175
188
  ```
176
189
 
177
- The `rmdir` is a no-op when another branch's pending checklist still sits in the folder, which keeps this step from deleting a handoff that is not its own.
190
+ Delete the handoff file once that call reports success, per the cleanup below, since the checklist now lives on the pull request. Clean up the tmp body file the same way.
178
191
 
179
- ### Post the evidence comparison
192
+ Any other `reason` is one of the mirrored git refusals (`gh-missing`, `gh-failed`, `no-base`, `unreadable-tree`, `unreadable-changes`). Report it and move on without stopping the chain: a branch that carries no evidence images most of the time should not fail here on a transient git or `gh` read. Fall through to the checklist step, which posts the checklist alone rather than losing it to a transient read.
180
193
 
181
- Run `canon pr evidence <number> --json` against the number the pull request step above resolved. Read `reason` on the record rather than the exit code.
194
+ ### Post the UI checklist alone
182
195
 
183
- - `no-evidence`: nothing changed under an `evidence/` segment. Say nothing and move on.
184
- - `ok`: write `body` to `.canon/tmp/pr/evidence/body-<number>.md` at the main worktree root (resolved the way `session-worktree` does), then post or update the comment:
196
+ Run this step only when the evidence step above did not carry the checklist, meaning it reported `no-evidence` or one of the git refusals, and a checklist file exists. Post it as its own comment on `<number>`:
185
197
 
186
198
  ```bash
187
- gh pr comment <number> --body-file <main-root>/.canon/tmp/pr/evidence/body-<number>.md
199
+ gh pr comment <number> --body-file <main-root>/.canon/tmp/handoff/ui-checklist/<slug>.md
188
200
  ```
189
201
 
190
- When the record carries a `commentId`, edit that comment in place instead of posting a second one, reading the body field from the tmp file with `@`:
202
+ Run the cleanup below only once the call that carried the checklist reports success, whichever of the two steps that was. On a failure, stop and leave the file in place: a retry needs the checklist to still be there, and deleting it on a failed post loses the only copy with nothing landed on the pull request.
203
+
204
+ From a linked worktree the file-editing tools refuse a main-root path, so the cleanup goes out through `Bash` as two plain commands, the file and then the folder, rather than joined by `&&`, which is refused as compound:
191
205
 
192
206
  ```bash
193
- gh api -X PATCH repos/{owner}/{repo}/issues/comments/<commentId> -f body=@<main-root>/.canon/tmp/pr/evidence/body-<number>.md
207
+ rm <main-root>/.canon/tmp/handoff/ui-checklist/<slug>.md
194
208
  ```
195
209
 
196
- Clean up the tmp file the way the UI-checklist step does, only after the call reports success.
210
+ ```bash
211
+ rmdir <main-root>/.canon/tmp/handoff/ui-checklist 2>/dev/null || true
212
+ ```
197
213
 
198
- Any other `reason` is one of the mirrored git refusals (`gh-missing`, `gh-failed`, `no-base`, `unreadable-tree`, `unreadable-changes`). Report it and move on without stopping the chain: a branch that carries no evidence images most of the time should not fail here on a transient git or `gh` read.
214
+ The `rmdir` is a no-op when another branch's pending checklist still sits in the folder, which keeps this step from deleting a handoff that is not its own.
215
+
216
+ Deleting the file is what makes the later re-render safe. `git-followup` re-runs `canon pr evidence` with no `--checklist`, and the verb carries the checklist forward out of the comment it is editing, so the boxes a reviewer already ticked survive the push.
199
217
 
200
218
  ### Post the preview address
201
219
 
202
- Run this step only when the evidence step above returned `ok` or the UI checklist step posted a comment. Either one means the pull request changes a rendered surface, and a reviewer holding a checklist with no screenshots needs the live page most. Otherwise skip it silently.
220
+ Run this step only when the evidence step above returned `ok` or a checklist was posted, by either step. Either one means the pull request changes a rendered surface, and a reviewer holding a checklist with no screenshots needs the live page most. Otherwise skip it silently.
203
221
 
204
222
  The evidence comment is already posted, so the reviewer has the screenshots while the deploy runs. Mint the preview against the same `<number>`:
205
223
 
@@ -250,9 +268,11 @@ Add a further line only when the labelling command printed its warning, quoting
250
268
 
251
269
  `⚠️ Labels not applied: <what gh reported>`
252
270
 
253
- Add a line when the UI checklist step posted a comment, naming the pull request it landed on:
271
+ Add a line when a checklist was posted, naming the pull request it landed on and which comment carries it:
272
+
273
+ `📋 Posted the UI checklist to <number>, in the evidence comment.`
254
274
 
255
- `📋 Posted the UI checklist to <number>.`
275
+ `📋 Posted the UI checklist to <number>, on its own.`
256
276
 
257
277
  Add a line when the preview step returned an address:
258
278
 
@@ -49,7 +49,7 @@ Moving the guidance into a path-scoped rule does not reach the session that need
49
49
  - Structure floors for every test: the core testing rule
50
50
  - Runner choice, file suffix, placement, and query conventions: the TypeScript and Python testing rules
51
51
  - Settle, guard, and run-scope rules inside an end to end file: the end to end reliability and test scope rules
52
- - Writing a UI change's tests and its visual checklist after implementation: `ui-test`
52
+ - Writing a UI change's visual checklist, and naming the behaviors it ships untested for this skill to place: `ui-checklist`
53
53
  - Grading the tests a pull request adds: `review-pr`
54
54
  - Whether the guidance moves where a session puts a test, which needs a measured with-and-without run rather than a rule here
55
55
 
@@ -32,5 +32,5 @@ Without this skill, a session implementing a planned change writes the test afte
32
32
 
33
33
  - Choosing the layer a test belongs at and judging whether it is a good test: `canon:test-craft`, loaded at step 1 rather than restated here
34
34
  - Finding the cause of an unexplained failure: `canon:systematic-debugging`
35
- - Confirming visual output after a change lands: `070-planning.md` states the order and `ui-test` covers it
35
+ - Confirming visual output after a change lands: `070-planning.md` states the order and `ui-checklist` writes the list to confirm against
36
36
  - The mechanical audit of whether an implementation reached history ahead of its test: `canon gov test-order`, invoked from `auto-ship`
@@ -0,0 +1,49 @@
1
+ ---
2
+ name: ui-checklist
3
+ description: Why the visual half of a UI change needs a written record a reviewer can open, why that record travels with the screenshots it annotates, and why writing the tests belongs elsewhere now
4
+ ---
5
+
6
+ # UI checklist requirement
7
+
8
+ ## Gap
9
+
10
+ Without this skill, UI work ships on the confidence of the session that wrote it. Nobody states which part of the change an eye has to judge, so a reviewer opens the diff and reads the automatable half, which the tests already cover, and never looks at the half that only rendering shows.
11
+
12
+ The visual half is where the record is lost. A checklist printed into chat scrolls away before anyone verifies it, so the ship step has nothing to gate on and the work reads as verified because a list was produced. Written from a linked worktree against `pwd`, the file lands where the caller does not look.
13
+
14
+ Writing the checklist correctly to disk does not close the gap either. The file sits under a gitignored folder on the machine that authored it, so a pull request reviewer, on that machine or another, never sees it. A record that only the author can open reads as verified for the same reason a record that never existed does.
15
+
16
+ Posting it as a comment of its own is the next failure. The screenshots the checklist annotates sit in a second comment, so a reviewer reads "the hero settles without a jump" with no picture of the hero beside it and scrolls looking for one. Two comments describing one change also drift: a later push re-renders the screenshots and leaves the checklist claiming what an older head showed.
17
+
18
+ The automatable half has its own gap, and it is no longer this skill's to close by writing the tests. `test-first` writes a behavior's test while the code is written, and `test-craft` owns which layer it belongs at. A skill writing tests after implementation duplicates both, and what it produces is the test the session already skipped rather than the test the behavior needed. What stays open is that nobody names the behaviors shipping untested, which is a list rather than a test.
19
+
20
+ ## Must
21
+
22
+ - Split every change into visual-only and automatable before writing anything
23
+ - Write the checklist for the visual-only half, each item an action and the result a person is looking for
24
+ - Name every automatable change shipping with no test, with the layer `test-craft` would place it at, as a section of the same file
25
+ - Write the file to the `.canon/tmp/handoff/ui-checklist/<slug>.md` handoff at the main worktree root, overwriting
26
+ - Report that there is nothing to verify rather than manufacturing a checklist to show work
27
+
28
+ ## Must not
29
+
30
+ - Write a test at any layer, which is `test-first` while the code is written and `test-craft` for where it goes
31
+ - Read a project's test config or runner in order to write against it
32
+ - Try to assert a visual property programmatically
33
+ - Repeat the full checklist in chat, which is what made it evaporate
34
+ - Stage or commit the checklist, which is gitignored scratch
35
+ - Talk to GitHub directly. Posting the checklist belongs to `git-pr`, the sole consumer of the handoff file
36
+ - Move the handoff path to match this skill's name, which strands a checklist an older binary wrote
37
+
38
+ ## Guards
39
+
40
+ - No implementation context in the session: stop, since there is nothing to split
41
+ - Nothing visual and nothing untested: skip the file write and report the coverage
42
+
43
+ ## Out of scope
44
+
45
+ - Judging whether the UI is any good, which `ux-audit` owns
46
+ - The rules for choosing a layer, which `test-craft` owns
47
+ - Writing the test itself, which is `test-first`
48
+ - Deciding whether an outstanding checklist blocks the ship, which the calling pipeline gates on
49
+ - Performing the visual verification, which needs a person
@@ -0,0 +1,71 @@
1
+ ---
2
+ name: ui-checklist
3
+ description: Writes the visual checklist a reviewer reads beside the evidence screenshots, and names each changed behavior shipping with no test so `test-craft` can place one. Use after implementing UI changes, or when asked "what should I look at", "what do I verify", or "give me a visual checklist". Do NOT use in empty sessions with no implementation context, and do NOT use to write the tests themselves.
4
+ ---
5
+
6
+ # UI checklist
7
+
8
+ ## Guards
9
+
10
+ - If no implementation context exists in the session, stop: `❌ No implementation context. Describe what you built first.`
11
+ - This skill writes no test. When a behavior needs one, name it and let `canon:test-craft` place it.
12
+
13
+ ## Analysis
14
+
15
+ Review the session to identify what was built or changed. Split each change two ways:
16
+
17
+ - **Visual-only:** spacing, alignment, color, typography, layout proportions, animation timing. These are what the checklist exists for, since nothing asserts them programmatically.
18
+ - **Automatable:** interactions, state transitions, form submissions, keyboard handling, conditional rendering, loading, empty, and error states, plus journeys across routes and behavior only a real browser renders.
19
+
20
+ An automatable change already covered by a test written during implementation is done. What is left is the missing-test list below.
21
+
22
+ ## The checklist
23
+
24
+ Group visual items by feature area. Use `- [ ]` checkbox syntax, and write each item as the action and the result a person is looking for, so a reviewer who did not build the change can run it.
25
+
26
+ ```markdown
27
+ **What to verify visually:**
28
+
29
+ **<Feature area>**
30
+
31
+ - [ ] <action> → <expected visual result>
32
+ ```
33
+
34
+ ## The missing-test list
35
+
36
+ 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 and do not read the project's test config to write one against.
37
+
38
+ Route each through the `test-craft` layer table: a journey or a browser-only behavior to end to end, a rendered state to a component test, and logic that renders nothing to a unit test. When the project has no runner at that layer, say so on the line rather than moving the item to a layer that does.
39
+
40
+ This list ships inside the checklist file, below the visual items, so a reviewer reads what is untested next to what to look at:
41
+
42
+ ```markdown
43
+ **Shipping without a test:**
44
+
45
+ - <behavior> → <layer> (<runner, or the runner that is missing>)
46
+ ```
47
+
48
+ Omit the heading when every automatable change is covered.
49
+
50
+ ## Persist the checklist
51
+
52
+ Derive `<slug>` per `${CLAUDE_SKILL_DIR}/../../standards/slug.md`. Fall back to `latest` on an empty result.
53
+
54
+ When a checklist is produced, write it directly to `.canon/tmp/handoff/ui-checklist/<slug>.md` at the main worktree root, not the current worktree. Resolve that root the way `session-worktree` does. Create the directory if it does not exist. Always overwrite. This is a handoff file rather than a deliverable: `git-pr` renders it into the evidence comment once a pull request opens, then removes it, and nothing here talks to `gh` directly.
55
+
56
+ The path names the artifact rather than this skill, so it does not move when the skill is renamed. A checklist written by an older binary still lands where the consumer looks.
57
+
58
+ From a linked worktree the file-editing tools refuse that path, so the checklist goes out through `Bash`. Send the `mkdir -p` and the heredoc as two plain commands rather than joining them with `&&`, which is refused as compound.
59
+
60
+ Skip the file write when there is nothing to verify visually and nothing shipping untested.
61
+
62
+ The `.canon/tmp/` directory is gitignored. Do not stage or commit the file.
63
+
64
+ ## Output order
65
+
66
+ 1. If a checklist was produced, write it to file, then output only the file path in chat:
67
+ `📝 Wrote .canon/tmp/handoff/ui-checklist/<slug>.md`
68
+ 2. Name the count of visual items and the count of behaviors shipping without a test, on one line each
69
+ 3. If nothing was produced: `✅ Nothing to verify visually and nothing shipping untested.`
70
+
71
+ Do not repeat the full checklist in chat.
@@ -35,6 +35,6 @@ The pass slides into fixing what it finds, and then the audit and the change lan
35
35
 
36
36
  - Fixing what it found, which is a separate change with its own review
37
37
  - Feature planning, which `plan-feature` owns
38
- - Verifying one specific change, which `ui-test` owns
38
+ - Verifying one specific change, which `ui-checklist` owns
39
39
  - Defining the intent it audits against, which `design-extract` and the wireframes own
40
40
  - Measuring what a running interface costs to paint, block, or shift, which `ux-measure` owns. Contrast stays here rather than going with it, being computable from two color values this skill already reads off the token table.
@@ -44,5 +44,5 @@ A session that does start one picks a runner on the spot. The reading then comes
44
44
  - Judging the interface against stated intent, which `ux-audit` owns
45
45
  - Contrast, which is computable from two color values in the token table `ux-audit` already reads. A contrast failure from a color computed at runtime stays invisible to that reader, and the cost is accepted rather than overlooked.
46
46
  - Network waterfall, bundle size, and accessibility, which no version of this measures
47
- - Writing behavioral tests against the interface, which `ui-test` owns
47
+ - Writing behavioral tests against the interface, which `test-first` and `test-craft` own
48
48
  - Fixing what the reading found
@@ -18,9 +18,13 @@ canon capture https://example.com --selector .window --out preview.png
18
18
 
19
19
  ## What this repository captures
20
20
 
21
- A capture set here spans two folders. `assets/captures/` holds three sources and the template each is written from, and `assets/evidence/` holds their images and the built-page capture a document points at with the stamp answering for each, under the segment `canon pr evidence` compares. One run over the source folder rebuilds every image, and it takes `--out assets/evidence` to put each PNG where its document points rather than beside the markup it rendered. None of the three sources is edited by hand. `scripts/core/regen-hero.sh` writes each `.html` from the template beside it, filling one shared value map into all of them, and `bun run check` regenerates them and fails on the difference.
21
+ A capture set here spans two folders. `assets/captures/` holds five sources and the three templates they are written from, and `assets/evidence/` holds their images and the built-page capture a document points at with the stamp answering for each, under the segment `canon pr evidence` compares. One run over the source folder rebuilds every image, and it takes `--out assets/evidence` to put each PNG where its document points rather than beside the markup it rendered. None of the sources is edited by hand. `scripts/core/regen-hero.sh` writes each `.html` from the template beside it, filling one shared value map into all of them, and `bun run check` regenerates them and fails on the difference.
22
22
 
23
- Two of the three take catalog data, so a stack gaining a rule moves the frame on the next run. `install.html` is the exception, since it holds terminal text from a real run in its template rather than derived from a live catalog at build time. `assets/evidence/arrival.png` is the one image with no source here: it is the built page's `.fold` element captured in dark mode against a local `web/` preview, since `canon capture` takes no color scheme and the page follows the browser's default of light, so it is retaken by hand when the page's first screen changes.
23
+ Two of the three templates take catalog data, so a stack gaining a rule moves the frame on the next run. `install.html` is the exception, since it holds terminal text from a real run in its template rather than derived from a live catalog at build time. `assets/evidence/arrival.png` is the one image with no source here: it is the built page's `.fold` element captured in dark mode against a local `web/` preview, since `canon capture` takes no color scheme and the page follows the browser's default of light, so it is retaken by hand when the page's first screen changes.
24
+
25
+ The README shows each still in two themes through `<picture>`, so a frame is a pair named `<name>` and `<name>-light`. `catalog` and `install` take the light half from the same template, which `regen-hero.sh` fills a second time with the plain color roles pointed at the light ones, and each half is its own capture set with its own stamp. `arrival-light.png` is the same `.fold` capture of the same address taken without forcing dark, so it is retaken by hand alongside `arrival.png`, and nothing catches one drifting from the other.
26
+
27
+ A stamp for a URL source digests the address string rather than the page it served, so two themes of one address carry one `source-sha256` and only `image-sha256` tells the frames apart. That holds for `examples/teach/evidence/root-listing-light.png`, which is retaken by serving a copy of the fixture with `data-theme="light"` on its `<html>` element at the same address, since the page follows the dark default and the command takes no scheme.
24
28
 
25
29
  `assets/captures/` is read flat and never descends, by the regeneration script, by this command, and by the drift stage alike. A source nested a further folder down is skipped by all three with nothing reported, so a new frame is a template dropped directly in rather than a folder of its own. The split is what retired the name prefix the flat layout used to need: a frame is named for itself now, since `assets/captures/` no longer mixes markup in with the images.
26
30
 
@@ -24,13 +24,15 @@ So `canon demo compile` writes a second artifact rather than adding fields to a
24
24
 
25
25
  The plan is committed, not scratch. Its timing is a starting point tuned by watching a recording, and the draft cannot reproduce a tuned value, so a recompile over an existing plan refuses and names `--force`.
26
26
 
27
+ A plan can carry an optional `colorScheme` of `light` or `dark`, which the recording context starts in so a page following the system preference paints that scheme with no click beat of its own. An absent field keeps the engine's default, which is light, and `compile` never seeds it. Any other value makes the plan unreadable. A page keyed on a stored theme rather than the preference ignores the field.
28
+
27
29
  `record-screencast` is the routed way to run both verbs from a draft path: it compiles only when no plan exists yet, then runs, stopping to report any field still unresolved rather than guessing one.
28
30
 
29
31
  | Option | Behavior |
30
32
  | --------------- | ----------------------------------------------------------------- |
31
33
  | `--out <dir>` | Directory the plan and its output paths point at, default `demos` |
32
34
  | `--slug <slug>` | Plan name, defaulting to the draft filename |
33
- | `--force` | Overwrite an existing plan, losing any timing tuned by hand |
35
+ | `--force` | Overwrite an existing plan, losing any field set by hand |
34
36
  | `--json` | Add a record on stdout carrying the beats and what is unfilled |
35
37
 
36
38
  ## What a run does
@@ -46,17 +46,18 @@ image or marks the case new.
46
46
 
47
47
  `reason` on the record is what a caller branches on, not the exit code:
48
48
 
49
- | Reason | What it means |
50
- | -------------------- | ----------------------------------------------------------------------------- |
51
- | `ok` | A body was rendered. `commentId` is set when a marked comment already exists. |
52
- | `no-evidence` | Nothing in the diff carries an `evidence/` segment. An ordinary silent no-op. |
53
- | `gh-missing` | `gh` is not on the path, so no pull request could be resolved. |
54
- | `gh-failed` | `gh` could not answer for this repository or branch, or read its comments. |
55
- | `no-branch` | The pull request carries no head branch name. |
56
- | `no-object-head` | The pull request object reported no head commit. |
57
- | `no-base` | No base resolves against the trunk. |
58
- | `unreadable-tree` | git could not read the tree at the base commit. |
59
- | `unreadable-changes` | git could not list what this branch changed. |
49
+ | Reason | What it means |
50
+ | ---------------------- | ----------------------------------------------------------------------------- |
51
+ | `ok` | A body was rendered. `commentId` is set when a marked comment already exists. |
52
+ | `no-evidence` | Nothing in the diff carries an `evidence/` segment. An ordinary silent no-op. |
53
+ | `unreadable-checklist` | `--checklist` named a path that could not be read, or one holding nothing. |
54
+ | `gh-missing` | `gh` is not on the path, so no pull request could be resolved. |
55
+ | `gh-failed` | `gh` could not answer for this repository or branch, or read its comments. |
56
+ | `no-branch` | The pull request carries no head branch name. |
57
+ | `no-object-head` | The pull request object reported no head commit. |
58
+ | `no-base` | No base resolves against the trunk. |
59
+ | `unreadable-tree` | git could not read the tree at the base commit. |
60
+ | `unreadable-changes` | git could not list what this branch changed. |
60
61
 
61
62
  `no-evidence` is not a refusal a caller reports. A project on a stack that
62
63
  carries no evidence path, such as `base` or `python`, hits this reason on
@@ -107,6 +108,28 @@ no evidence in the diff and an address in hand, the body is the address and the
107
108
  marker alone, reported as `ok`. `canon docs pr-preview` covers where the
108
109
  address comes from.
109
110
 
111
+ ## The checklist line
112
+
113
+ `--checklist <path>` closes the body with a visual checklist, below the
114
+ comparison it annotates, so one comment carries the preview address, the
115
+ screenshots, and what a reviewer has to look at. The file is what
116
+ `canon:ui-checklist` writes to `.canon/tmp/handoff/ui-checklist/<slug>.md`, and
117
+ `git-pr` passes it here rather than posting it as a second comment.
118
+
119
+ A run without the option carries forward whatever checklist the marked comment
120
+ already holds, the same way the preview address is carried. That is what keeps
121
+ `git-followup`'s re-render after a push from wiping boxes a reviewer has
122
+ already ticked, since `git-pr` deletes the handoff once the first post reports
123
+ success and no later call has a file to pass.
124
+
125
+ The option does not decide `no-evidence`. A branch with a checklist and no
126
+ changed evidence image still reports `no-evidence`, and the caller posts the
127
+ checklist on its own, which is what it did before this option existed. A path
128
+ that cannot be read, or one holding nothing, refuses as
129
+ `unreadable-checklist` rather than rendering without it: the caller deletes the
130
+ handoff once a post succeeds, so a silently dropped checklist is the only copy
131
+ gone.
132
+
110
133
  ## What a collapsed comment still leaves to GitHub
111
134
 
112
135
  GitHub already draws its own before-and-after comparison on the Files Changed
@@ -81,13 +81,14 @@ going, so the preview may still land after the verb has given up on it.
81
81
 
82
82
  `canon pr evidence --preview <url>` puts `**Preview:** <url>` on the first line
83
83
  of the evidence body. With no evidence image in the diff, the body is that line
84
- and the trailing marker alone, so a pull request with a checklist and no
85
- screenshots still gets one comment a later call can find and edit.
84
+ and the trailing marker alone, so a pull request whose screenshots did not
85
+ change still gets one comment a later call can find and edit.
86
86
 
87
87
  A later `canon pr evidence` run without `--preview` reads the address off the
88
88
  marked comment and carries it into the new body. `git-followup` re-renders the
89
89
  comment after every push, and without that carry the first push would delete
90
- the link.
90
+ the link. A visual checklist folded in through `--checklist` carries the same
91
+ way, which is what keeps that push from wiping boxes a reviewer already ticked.
91
92
 
92
93
  ## Removing a preview
93
94
 
@@ -54,7 +54,9 @@ An outcome whose body is struck reads as cut rather than open or closed, whateve
54
54
 
55
55
  The task carries its plan with it. When the closing task is the last live one whose `Plan:` line resolves onto that file, the plan moves to `.canon/plans/archive/` under its own name and the archived task's line is rewritten as `Plan: [feature-<slug>](../../plans/archive/feature-<slug>.md)`, a folder deeper than the live task wrote it. The `plan` field on the success record carries that `from` and `to`, and is `null` when nothing moved.
56
56
 
57
- A plan several tasks share stays where it is, and the task archives anyway. Moving it on the first task to close strands every sibling's pointer at a path that has gone, and `.canon/plans/` is gitignored so no history recovers the target. A `Plan:` line resolving to no file leaves the plan alone too, since a pointer somebody typed wrong is not a plan to move and holding the whole archive over it would park the board behind a repair the merge cannot make.
57
+ The ready folder the task's `Ready:` line names moves with the plan to `.canon/ready/archive/`, falling back to a `.canon/ready/<nn>-<slug>/` path in the plan when the line is absent. The `Ready:` line and the plan's literal path to the folder are retargeted, and the `ready` field on the success record carries `from` and `to`, or `null` when nothing moved. A folder that resolves to nothing, sits already archived, or meets a taken destination moves nothing and refuses nothing.
58
+
59
+ A plan several tasks share stays where it is, and its ready folder with it, and the task archives anyway. Moving it on the first task to close strands every sibling's pointer at a path that has gone, and `.canon/plans/` is gitignored so no history recovers the target. A `Plan:` line resolving to no file leaves the plan alone too, since a pointer somebody typed wrong is not a plan to move and holding the whole archive over it would park the board behind a repair the merge cannot make.
58
60
 
59
61
  The merge is what settles a plan. `.husky/post-merge` reads the pull request number off the squash subject and calls this verb, so both halves close in one act with nobody naming a file. That is why the move sits inside this verb rather than in a second call the hook would make after it, which could leave the task archived and the plan live.
60
62
 
@@ -58,7 +58,7 @@ Work in Claude Code directly. It reads `CLAUDE.md` automatically and has full fi
58
58
  - When the current state is unmeasured and more than one approach is live, invoke `canon:plan-groundwork` first. It opens a track folder under `.canon/groundwork/<nn>-<slug>/` and ends in a decision, which may be to do nothing. Skip it when the approach is already settled.
59
59
  - Invoke `canon:plan-feature` to scan for code-level conflicts and ambiguities, confirm approach before proceeding
60
60
  - Implement the feature, then Claude Code runs the commands defined in `CLAUDE.md`, fixes failures, and iterates until all pass
61
- - For UI changes, invoke `canon:ui-test` to route each change to its test layer and run the tests it writes, with Playwright kept for journeys
61
+ - For UI changes, invoke `canon:ui-checklist` to write what a reviewer has to look at and name any behavior shipping without a test
62
62
  End the session once the feature works and tests pass. Invoke `canon:docs-fold` to capture any decisions made during implementation before closing.
63
63
 
64
64
  The routing test is whether the repository can answer an item today. A session grepping handles the yes, and a groundwork track handles the no.
@@ -81,7 +81,7 @@ When features are independent, run them in parallel instead of sequentially. Use
81
81
 
82
82
  - Create a worktree per feature, then start a Claude Code session in each
83
83
  - Invoke `canon:plan-feature` in each session. Plans land at the main worktree root as `.canon/plans/feature-<slug>.md`, one per feature, no collisions. Small features stay in chat and skip the file.
84
- - Implement, verify, and review each feature independently. `review-branch` writes a per-branch report at the main worktree root (`review/branch-<slug>.md`), and `ui-test` writes a per-branch checklist handoff there too (`tmp/handoff/ui-checklist/<slug>.md`) that `git-pr` posts to the pull request and removes, so parallel sessions do not overwrite each other. The slug is the branch name with any leading type segment dropped, so `feat/jwt-expiration` and the plan at `feature-jwt-expiration.md` meet on one name
84
+ - Implement, verify, and review each feature independently. `review-branch` writes a per-branch report at the main worktree root (`review/branch-<slug>.md`), and `ui-checklist` writes a per-branch checklist handoff there too (`tmp/handoff/ui-checklist/<slug>.md`) that `git-pr` posts to the pull request and removes, so parallel sessions do not overwrite each other. The slug is the branch name with any leading type segment dropped, so `feat/jwt-expiration` and the plan at `feature-jwt-expiration.md` meet on one name
85
85
  - Ship each worktree separately with `canon:git-ship`
86
86
  - For full autonomy per worktree, invoke `canon:auto-ship` instead of the manual chain. Approve the plan, walk away, come back to a pull request the chain marked as a draft and then read the flag back on. The mark says the work has had no review yet, and it holds no window, since readying a pull request to merge lifts it directly, an act reserved to the operator or to the controlling session that closed the review.
87
87
 
@@ -174,7 +174,7 @@ The receipt is collected once every item on it has been decided, and it survives
174
174
 
175
175
  ### UI polish
176
176
 
177
- Verify the change manually in the browser. Invoke `canon:ui-test` if you need tests at the right layer and a visual verification checklist for the session. For the fix itself, describe the change in Claude Code directly.
177
+ Verify the change manually in the browser. Invoke `canon:ui-checklist` if you need a written list of what to look at. For the fix itself, describe the change in Claude Code directly.
178
178
 
179
179
  ### Quick fix
180
180
 
@@ -235,7 +235,7 @@ This section is the corpus the coverage claim is measured against: every name `c
235
235
  | `canon:test-first` | Before implementing a planned change, to run its test red, green, then refactor |
236
236
  | `canon:test-craft` | When writing or changing any test, to pick its layer and filter what it asserts |
237
237
  | `canon:systematic-debugging` | When a test fails or a bug surfaces, to force root cause first |
238
- | `canon:ui-test` | After a UI change, to generate layered tests and a visual checklist |
238
+ | `canon:ui-checklist` | After a UI change, to write what to look at and name what ships untested |
239
239
 
240
240
  ### Check the work before it leaves the branch
241
241