@erclx/canon 4.90.0 → 4.93.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 (63) hide show
  1. package/README.md +5 -5
  2. package/claude/.claude-plugin/plugin.json +1 -1
  3. package/claude/skills/design-taste/REQUIREMENT.md +78 -0
  4. package/claude/skills/design-taste/SKILL.md +148 -0
  5. package/claude/skills/design-taste/references/craft.md +58 -0
  6. package/claude/skills/design-taste/references/kinds.md +52 -0
  7. package/claude/skills/design-taste/references/preflight.md +77 -0
  8. package/claude/skills/design-taste/references/systems.md +49 -0
  9. package/claude/skills/design-taste/references/tells.md +101 -0
  10. package/claude/skills/design-taste/references/vocabulary.md +62 -0
  11. package/claude/skills/draft-and-pick/REQUIREMENT.md +8 -2
  12. package/claude/skills/draft-and-pick/SKILL.md +13 -5
  13. package/claude/skills/draft-ready/REQUIREMENT.md +53 -0
  14. package/claude/skills/draft-ready/SKILL.md +118 -0
  15. package/claude/skills/draft-ready/references/assembly.md +67 -0
  16. package/claude/skills/session-compact/REQUIREMENT.md +44 -0
  17. package/claude/skills/session-compact/SKILL.md +60 -0
  18. package/claude/skills/session-compact/references/handoff-note.md +68 -0
  19. package/claude/skills/session-map/REQUIREMENT.md +3 -2
  20. package/claude/skills/session-map/SKILL.md +2 -2
  21. package/claude/skills/session-resume/SKILL.md +3 -3
  22. package/claude/skills/teach-workspace/references/lesson-craft.md +1 -1
  23. package/docs/agents/capture.md +12 -5
  24. package/docs/agents/commands.md +3 -3
  25. package/docs/agents/rule-citations.md +1 -1
  26. package/docs/agents/teach.md +2 -2
  27. package/docs/workflow/ai-workflow.md +3 -1
  28. package/governance/rules/claude/563-ready.md +4 -0
  29. package/governance/rules/lib/305-e2e-reliability.md +3 -2
  30. package/governance/rules/lib/306-test-scope.md +1 -2
  31. package/governance/rules/ui/410-a11y.md +9 -0
  32. package/governance/rules/ui/420-forms.md +9 -0
  33. package/governance/rules/ui/460-design-taste.md +29 -0
  34. package/governance/rules/ui/470-motion.md +23 -0
  35. package/governance/stacks/astro.toml +1 -1
  36. package/governance/stacks/react.toml +1 -1
  37. package/package.json +3 -2
  38. package/scripts/core/regen-hero.sh +4 -3
  39. package/src/capture/render.ts +15 -1
  40. package/src/claude/cases/authoring.ts +5 -0
  41. package/src/claude/cases/misc.ts +10 -0
  42. package/src/cli.ts +1 -1
  43. package/src/commands/capture.ts +26 -1
  44. package/src/commands/design.ts +5 -0
  45. package/src/commands/feedback.ts +1 -0
  46. package/src/commands/serve.ts +11 -1
  47. package/src/commands/slides.ts +3 -0
  48. package/src/commands/transcripts.ts +1 -0
  49. package/src/design/base.css +25 -19
  50. package/src/design/css.ts +4 -0
  51. package/src/design/fonts.ts +17 -0
  52. package/src/design/tokens.ts +45 -30
  53. package/src/gate/measures.ts +5 -4
  54. package/src/gate/stages.ts +1 -1
  55. package/src/serve/static.ts +90 -6
  56. package/src/teach/render.ts +2 -2
  57. package/src/teach/workspace.ts +1 -0
  58. package/standards/ready.md +2 -0
  59. package/standards/session.md +1 -0
  60. package/tooling/astro/configs/playwright.config.ts +2 -0
  61. package/tooling/nextjs/configs/playwright.config.ts +2 -0
  62. package/tooling/vite-react/configs/playwright.config.ts +2 -0
  63. package/src/teach/render-fixture.tsx +0 -95
@@ -0,0 +1,68 @@
1
+ ---
2
+ title: Handoff note
3
+ description: What each section of a compact handoff carries, what stays out, and the test every line passes
4
+ ---
5
+
6
+ # Handoff note
7
+
8
+ The shape `session-compact` writes. It lives here rather than in `standards/` because one skill reads it and a standard nothing else cites has no second reader to correct it.
9
+
10
+ ## The test every line passes
11
+
12
+ Would a compaction destroy this, and can no other artifact give it back?
13
+
14
+ A line failing either half comes out. The tree, the git log, a groundwork record and the task board all survive a compaction, so restating any of them spends the reader's trust on something they already had.
15
+
16
+ ## Frontmatter
17
+
18
+ ```markdown
19
+ ---
20
+ title: <what the session settled, as a sentence>
21
+ description: <what it decided, what it measured, what it left open, and the date>
22
+ category: Compact
23
+ ---
24
+ ```
25
+
26
+ ## The sections
27
+
28
+ ### The opening line
29
+
30
+ One sentence naming this as throwaway and pointing at whatever durable record holds the detail. A reader who knows the note is disposable reads it differently from one who thinks it is the record.
31
+
32
+ ### `## Where the work is`
33
+
34
+ The paths, and how to run what is there. A later session's first cost is finding the thing, and a wrong guess about which folder is current wastes more than this section costs.
35
+
36
+ Name what was not touched too. A session that changed no source says so, since a reader otherwise opens `git status` expecting a diff.
37
+
38
+ ### `## What was decided`
39
+
40
+ One bullet per decision, each naming what it beat. A decision with no rejected alternative reads as arbitrary and gets reopened by the next session that dislikes it.
41
+
42
+ Where a decision rests on a measurement, give the number rather than the conclusion. `27 spacing values became 7` survives a reader disagreeing with it, where `the spacing was tidied` does not.
43
+
44
+ ### `## What is open`
45
+
46
+ Numbered, because a reader picks one. Each carries what is unresolved and what would settle it.
47
+
48
+ A thing deliberately not done belongs here, marked as such. An open question and a declined option look identical to a later session unless the note separates them.
49
+
50
+ ### `## Where to pick up`
51
+
52
+ One instruction. Not a list, not a menu, not a recommendation with alternatives.
53
+
54
+ Where the next move is a measurement rather than a decision, say so, since a session handed an open question reaches for a pick by default and spends a round on a question nobody needed answered.
55
+
56
+ ### `## Cautions`
57
+
58
+ Only what cost this session time and would cost the next one the same. A command whose output goes to stderr, a tool reading something other than what its name suggests, a generator that deletes more than it wrote.
59
+
60
+ Not general advice. A caution a reader could have guessed is noise around the two that matter.
61
+
62
+ ## What stays out
63
+
64
+ - A `## State` section. A session that committed nothing has no state, and one that did has it in the log.
65
+ - The tree, the file counts, and the folder listing. All survive a compaction.
66
+ - The session's narrative. What was tried and abandoned belongs in the record the track keeps, or in memory if it generalizes, or nowhere.
67
+ - A lesson already written to `.canon/memory/`. Cite the entry by name rather than restating it.
68
+ - Anything the reader would have to take on trust. Every claim carries a file, a commit, or a record.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: session-map
3
- description: Why the write procedure needs a route any session can take, why the door carries the drift step and its ref recovery, and why it states none of the shape the standard already fixes
3
+ description: Why the write procedure needs a board-side route for the orchestrator and for a request naming the map, why the door carries the drift step and its ref recovery, and why it states none of the shape the standard already fixes
4
4
  ---
5
5
 
6
6
  # Session map requirement
@@ -23,7 +23,7 @@ A body that restates the sections, the frontmatter, or the numbered steps become
23
23
 
24
24
  ## Must
25
25
 
26
- - Write from any session whatever role it holds, without asserting one
26
+ - Write for an orchestrating session, or on a request naming the session map or the board, without asserting a role the session does not hold
27
27
  - Cite the standard for the filename, the frontmatter, the sections, the numbered procedure, and the citation rule rather than restating any of them
28
28
  - Run the drift step and state how to recover the ref it reads from how long the session has run
29
29
  - Record what the drift verb names, and read a refusal as the boundary of what the verb can read
@@ -50,6 +50,7 @@ A body that restates the sections, the frontmatter, or the numbered steps become
50
50
 
51
51
  ## Out of scope
52
52
 
53
+ - The handoff a plain session writes before a compaction, which is a note outside the board with no drift step and no `## State`: `session-compact`. Both skills once claimed the phrases "write the handoff" and "about to compact", so this description narrows to the orchestrator and to a request naming the map or the board, and a plain session belongs to `session-compact`.
53
54
  - Reading a handoff back at the start of the next session: `session-resume`
54
55
  - Routing a session fact to the context entry that owns it, and writing what no entry owns to the memory folder: `memory-capture`
55
56
  - The sections a role adds over the core three, which belong to that role's own surface. `role-orchestrator` owns the orchestrator's and cites this route for the generic half.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: session-map
3
- description: Writes the session map, the pre-compaction handoff at `.canon/tasks/session-<slug>.md`, from any session whatever role it holds, running the skill-drift step the write procedure opens with. Use when asked to "write the handoff", "write the session map", "save the session before it compacts", "we are about to compact", "hand off to the next session", or "leave a note for whoever picks this up". Do NOT use to route session facts to a context entry or the memory folder, which is `memory-capture` and writes a different artifact, and do NOT use to read a handoff back, which is `session-resume`.
3
+ description: Writes the session map, the board-side handoff at `.canon/tasks/session-<slug>.md`, for an orchestrating session or on a request naming the session map or the task board, running the skill-drift step the write procedure opens with. Use when asked to "write the session map", "write the board handoff", or when a session holding `canon:role-orchestrator` hands off before a compaction. Do NOT use for a plain session about to compact, which is `session-compact` and writes a note outside the board. Do NOT use to route session facts to a context entry or the memory folder, which is `memory-capture` and writes a different artifact, and do NOT use to read a handoff back, which is `session-resume`.
4
4
  ---
5
5
 
6
6
  # Session map
@@ -13,7 +13,7 @@ Run every step rather than judging in advance that one has nothing to act on. Th
13
13
 
14
14
  ## What fires this
15
15
 
16
- Being asked is one route. The other is a `PreCompact` hook registered against the `manual` matcher, which a project may install and which blocks the first `/compact` of a session with a message naming this skill. The block fires once, so the next `/compact` proceeds whether or not a map was written, and an automatic compaction is never blocked. A project that registered no such hook reaches this skill by being asked alone.
16
+ Being asked is the route for a plain session that wants a board-side map, and the orchestrator's handoff runbook is the route for that role. A plain session about to compact belongs to `session-compact`, which is what the `PreCompact` hook on the `manual` matcher names. That hook's message carries one sentence sending a session holding `canon:role-orchestrator` here.
17
17
 
18
18
  Decline where the session holds no reasoning a reader could not get faster from git. Say so in one line and write nothing. The standard already names a `## State` filled from the tree as non-conforming, and the file that carries three such sections is worse than an absent one, because a reader who finds a map trusts it. Declining is a result the caller can act on, where a padded map is a result that misleads.
19
19
 
@@ -7,11 +7,11 @@ description: Resumes a previous session by reading the handoff it left behind, t
7
7
 
8
8
  ## Step 1: read tracked work
9
9
 
10
- Resolve `.canon/plans/`, `.canon/memory/`, and `.canon/tasks/` at the main worktree root the way `session-worktree` does.
10
+ Resolve `.canon/plans/`, `.canon/memory/`, `.canon/compact/`, and `.canon/tasks/` at the main worktree root the way `session-worktree` does.
11
11
 
12
12
  Read these in parallel, skipping any that do not exist:
13
13
 
14
- - the newest `.canon/tasks/session-*.md`: the handoff a previous session wrote before a compaction, per `${CLAUDE_SKILL_DIR}/../../standards/session.md`. It leads the report rather than the reads.
14
+ - the newest `.canon/compact/*.md`, then the newest `.canon/tasks/session-*.md`: the handoff a previous session wrote before a compaction. The first is the note `session-compact` writes, and the second is the board-side map per `${CLAUDE_SKILL_DIR}/../../standards/session.md`, which stays readable. Whichever exists leads the report rather than the reads, and both lead it when both exist.
15
15
  - `.canon/tasks/index.md`: the folder catalog. Read this before any individual task file, and take the task list from it by dropping the `index`, `priority`, `backlog`, and `session-` rows, which are siblings rather than tasks.
16
16
  - `.canon/plans/*.md`: execution detail for in-progress tasks
17
17
  - `.canon/memory/index.md` and any memory files relevant to the top backlog item
@@ -48,4 +48,4 @@ Memory is updated only when a recorded fact becomes wrong, never on resume. A do
48
48
 
49
49
  This skill reads a handoff and never writes one. Reading and writing are two jobs, and the write happens at the close of a session rather than at its start.
50
50
 
51
- Name the standard when the session asks how to leave a handoff behind, and let the session follow it directly. Any session may write one, whatever role it holds, so nothing here routes the request to another skill. A role carrying sections of its own adds them over the core per that role's own runbook.
51
+ Route a session asking how to leave a handoff behind to `session-compact`, which writes a note under `.canon/compact/`. A session holding the orchestrator role takes `session-map` instead, which writes the board-side map the standard governs, and a role carrying sections of its own adds them over the core per that role's own runbook.
@@ -73,7 +73,7 @@ The stepper hides and shows and sets nothing else, so how a selected option look
73
73
 
74
74
  ## The block list
75
75
 
76
- `canon teach render` takes a JSON array of blocks and renders it through the same components the fixture lesson is generated from. Four types, and every structural body composes from them:
76
+ `canon teach render` takes a JSON array of blocks and renders it through the same components a lesson body composes. Four types, and every structural body composes from them:
77
77
 
78
78
  - `{"type":"heading","level":1|2,"text":"<text>"}`: an `<h1>` or `<h2>`.
79
79
  - `{"type":"paragraph","text":"<text>","lede":true}`: a `<p>`, marked `lede` for the dek that opens the lesson.
@@ -5,11 +5,11 @@ description: Rendering HTML sources to PNG, what the command asserts about fonts
5
5
 
6
6
  # Capture
7
7
 
8
- `canon capture [source] --selector <sel>` renders HTML sources to PNG, which is how a generated documentation image is rebuilt from the markup it was generated out of. The source defaults to `assets/`, where a directory expands to every `.html` directly inside it, so adding a capture means dropping a file beside the first one and running the same command. That default is a generic starting point rather than a convention every target shares, and it stays because a missing folder still refuses loud, naming the argument, rather than failing silently. This repository no longer takes it: its own sources moved to `assets/captures/` and its images stayed in `assets/`, so a run here names the source explicitly and sends the output where that run wants it, `--out assets` to rebuild the committed images and somewhere disposable to preview them.
8
+ `canon capture [source] --selector <sel>` renders HTML sources to PNG, which is how a generated documentation image is rebuilt from the markup it was generated out of. The source defaults to `assets/`, where a directory expands to every `.html` directly inside it, so adding a capture means dropping a file beside the first one and running the same command. That default is a generic starting point rather than a convention every target shares, and it stays because a missing folder still refuses loud, naming the argument, rather than failing silently. This repository no longer takes it: its own sources moved to `assets/captures/` and its images moved to `assets/evidence/`, so a run here names the source explicitly and sends the output where that run wants it, `--out assets/evidence` to rebuild the committed images and somewhere disposable to preview them.
9
9
 
10
10
  ```bash
11
11
  canon capture --selector .window
12
- canon capture assets/captures/install.html --selector .window --out assets
12
+ canon capture assets/captures/install.html --selector .window --out assets/evidence
13
13
  canon capture assets/captures --selector .window --out .canon/review/captures
14
14
  canon capture https://example.com --selector .window --out preview.png
15
15
  ```
@@ -18,13 +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 five sources and the template each is written from, and `assets/` holds the five images a document points at with the stamp answering for each. One run over the source folder rebuilds every image, and it takes `--out assets` to put each PNG where its document points rather than beside the markup it rendered. None of the five 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 template each is written from, and `assets/evidence/` holds the five images 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 five 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
23
  Three of the five take catalog data, so a stack gaining a rule moves the frame on the next run. `install.html` and `task-board.html` are the two exceptions: the first holds terminal text from a real run and the second holds a hand-frozen snapshot of a gitignored board, each in its template rather than derived from a live catalog at build time.
24
24
 
25
- `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/` no longer mixes markup in with the images.
25
+ `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
26
 
27
- Only the HTML is asserted for drift. The PNG is a chromium render whose bytes move with the browser version, so rebuild it with `canon capture assets/captures --selector .window --out assets` when the check reports the HTML changed.
27
+ Only the HTML is asserted for drift. The PNG is a chromium render whose bytes move with the browser version, so rebuild it with `canon capture assets/captures --selector .window --out assets/evidence` when the check reports the HTML changed.
28
28
 
29
29
  Every render writes a stamp beside its PNG, `hero.png` next to `hero.stamp`, holding the source filename, a `source-sha256` over the markup bytes it read, and an `image-sha256` over the image bytes it wrote. Both digests are what `bun run check` compares, so a markup edit committed without a capture and a PNG swapped under unchanged markup each fail. The stamp is tracked and commits alongside the pair. A capture that cannot write it reports that source as failed and exits 1, so an image whose stamp never landed is reported rather than passed over.
30
30
 
@@ -34,6 +34,7 @@ Neither digest is ever written by hand. A digest is what the gate compares, so a
34
34
  | ------------------ | ------------------------------------------------------------------------------------------------------ |
35
35
  | `--out <dir>` | Write every PNG here instead of beside its source. For a URL source, names the destination PNG itself. |
36
36
  | `--selector <sel>` | Element to capture, required and never defaulted |
37
+ | `--width <px>` | Render at this viewport width so a media query resolves there. One whole number of 1 or more. |
37
38
 
38
39
  ## What the command asserts
39
40
 
@@ -50,3 +51,9 @@ The command ships to targets, alongside `demo`, `inventory`, and `drive`. It was
50
51
  Shipping it also fixed what the exclusion was hiding. The render module imported the `@playwright/test` development dependency, which no published tarball carries. Every browser reference still sits behind a dynamic import, so a browser loads for this command rather than in front of every other one.
51
52
 
52
53
  `demo.md` and `driver.md` cover two of the other three browser commands. What separates this one is that a capture renders a single state, from a file on disk or a named `http(s)://` URL, where the rest drive a running application.
54
+
55
+ ## Capturing at a width
56
+
57
+ `--width` sets the browser viewport and leaves the page alone. A media query answers to the viewport, so a wrapper narrowed to 390px renders a thin column of the desktop layout and proves nothing about the mobile one. The height stays at Playwright's default of 720, which keeps `vh` units where they are without the flag. A run that passes no `--width` passes no viewport option at all, so every committed image renders as before and no stamp digest moves.
58
+
59
+ The flag takes one integer and no list. A sweep across breakpoints is one run per width, and the caller picks `--out` for each, since two widths written into one directory overwrite each other under the source's name. A value that is not a whole number of 1 or more refuses ahead of the browser import, naming the flag.
@@ -103,8 +103,8 @@ Full help: `canon <command> --help`. Behavior notes for the install and sync ver
103
103
  | `canon gate run` | Run every stage that guards a branch here, scoping shell, types, and tests to the changed set (`--all`, `--no-write`, `--nested`, `--json`) |
104
104
  | `canon inventory [subject]` | Walk every route a project declares and group its elements by the property each computes, as a listing rather than a gate (`--json`) |
105
105
  | `canon drive <url> <run>` | Walk a page through named interactions and measure each state it reaches, reporting findings rather than gating (`--json`) |
106
- | `canon capture [source]` | Render an HTML source or an `http(s)://` URL to PNG and prove the font each one declares resolved (`--selector` required, `--out`) |
107
- | `canon serve [dir]` | Serve a directory on the loopback interface and print the link that opens it, running until interrupted (`--port`, `--entry`, `--json`) |
106
+ | `canon capture [source]` | Render an HTML source or an `http(s)://` URL to PNG and prove the font each one declares resolved (`--selector` required, `--out`, `--width`) |
107
+ | `canon serve [dir]` | Serve a directory on the loopback interface and print the link that opens it, running until interrupted (`--port`, `--entry`, `--index`, `--json`) |
108
108
  | `canon upgrade` | Reinstall the CLI globally with the package manager the install path names (`--json`) |
109
109
 
110
110
  `canon serve` drives no browser, which is what separates it from the four that do. All four ship now that `capture` does, so the line between them is the engine rather than the package. A generated page loses its script to an editor preview and to a `file://` open, so the link is the delivery rather than a convenience, and every generated surface here reaches a reader through one. A teach lesson's stylesheet is embedded rather than linked, so only its script still needs a server. It binds `127.0.0.1` and never a wildcard, because what it is pointed at is routinely a gitignored record tree. It sends `cache-control: no-store`, since a preview exists to be edited and reloaded and a cached stylesheet reads as a fix that did not work.
@@ -113,7 +113,7 @@ A port already in use is the ordinary case rather than a refusal, so it walks fo
113
113
 
114
114
  A request naming a directory is redirected to its trailing-slash form rather than answered in place. A browser resolves a relative asset against the last slash of the URL it is on, so answering `/lesson` directly leaves the page asking for `/course.css` instead of `/lesson/course.css`, and it renders unstyled through the server that exists to prevent exactly that.
115
115
 
116
- Containment is tested after symlinks are followed rather than on the path as written, and the test sits immediately before the read rather than beside the request that produced it. Resolving a request lexically clears a link pointing outside the served root, and this repository is a live instance of that shape, since `claude/standards` and `claude/snippets` are links out of `claude/`. Position is what makes the property hold: a directory request appends its index after the request path has been checked, so a check placed earlier leaves that index untested. An `--entry` that escapes the root refuses with `no-entry` before a port is taken, because `url` is the field a caller hands to a reader.
116
+ Containment is tested after symlinks are followed rather than on the path as written, and the test sits immediately before the read rather than beside the request that produced it. Resolving a request lexically clears a link pointing outside the served root, and this repository is a live instance of that shape, since `claude/standards` and `claude/snippets` are links out of `claude/`. Position is what makes the property hold: a directory request appends its index after the request path has been checked, so a check placed earlier leaves that index untested. `--index` answers a directory holding no `index.html` with a listing read per request, where the default stays a 404. Every entry the listing names takes the same containment test a request for it would, so a link out of the root is absent rather than greyed, and dotfiles are hidden. With no `--entry` and no root `index.html`, the printed link is `/` and `entryExists` reads true. An `--entry` that escapes the root refuses with `no-entry` before a port is taken, because `url` is the field a caller hands to a reader.
117
117
 
118
118
  `canon demo` is the second browser command and the one that ships, since its purpose is running in a target rather than regenerating what this repository commits. It needs a browser binary the package does not carry, installed once with `bunx playwright install chromium`.
119
119
 
@@ -73,7 +73,7 @@ A rule under `governance/rules/` installs into a target, and its `paths:` entrie
73
73
 
74
74
  `internal/rules/` ships nowhere. The tree it governs is the tree present, which makes the question answerable, and all 14 of its globs across 7 rules match at the commit this shipped on.
75
75
 
76
- What that leaves unreached is a glob that matches real files and still reaches none of the work it was scoped at. `governance/rules/lib/305-e2e-reliability.md` scopes itself at `e2e/*.ts` and `e2e/**/*.ts`, and no probe in this repository is written under `e2e/`, so the rule asking a session to watch a new guard fail never fired for the session writing guards. Resolution is mechanical and reach is a judgment about where the work happens, so only the first is here.
76
+ What that leaves unreached is a glob that matches real files and still reaches none of the work it was scoped at. `governance/rules/lib/305-e2e-reliability.md` scoped itself at `e2e/*.ts` and `e2e/**/*.ts` and now reads `**/e2e/**/*.ts`. No probe in this repository is written under `e2e/`, so the rule asking a session to watch a new guard fail never fired for the session writing guards. Resolution is mechanical and reach is a judgment about where the work happens, so only the first is here.
77
77
 
78
78
  ## The exemption marker
79
79
 
@@ -27,7 +27,7 @@ canon teach list regular-expressions --json
27
27
 
28
28
  With no topic it reports one line per workspace, carrying the lesson, learning-record, reference-page, and glossary-term counts, plus `next`, the ordinal an open would take. With one it reports the filenames behind each count and the glossary entries themselves.
29
29
 
30
- A folder not named `NN-<topic>` is still listed rather than dropped, since dropping it hides the one folder that needs a fix. Its ordinal reads as absent, it sorts last, and it moves no ordinal, so a malformed name cannot push a new workspace into a number a reader already cites.
30
+ A folder not named `NN-<topic>` is not a workspace, so the listing, `nav`, and every selector skip it and it moves no ordinal. That is what lets a teach root carry a sibling folder such as `evidence/` without it drawing as a stub row and receiving a contents page.
31
31
 
32
32
  The listing also names the required files a workspace does not carry, which is `MISSION.md`, `RESOURCES.md`, and `GLOSSARY.md`. That is a report rather than a refusal, because a workspace missing one is still a workspace a session can resume.
33
33
 
@@ -141,7 +141,7 @@ The order is drawn here rather than instructed, and that is the point of the ver
141
141
 
142
142
  ## Render
143
143
 
144
- `canon teach render` renders a lesson body's structural blocks to HTML, through the same components the fixture lesson under `examples/teach/00-fixture/` is generated from. It takes no topic and no `--root`, since the verb is a stateless transform reading nothing off a workspace on disk.
144
+ `canon teach render` renders a lesson body's structural blocks to HTML, through the three lesson components. It takes no topic and no `--root`, since the verb is a stateless transform reading nothing off a workspace on disk.
145
145
 
146
146
  ```bash
147
147
  echo '[{"type":"heading","level":1,"text":"Compass bearings"}]' | canon teach render --json
@@ -280,7 +280,8 @@ This section is the corpus the coverage claim is measured against: every name `c
280
280
  | `canon:role-worker` | To assert the worker role for a cold session building one branch under one plan |
281
281
  | `canon:session-relay` | When a worker or planner owes its controller a message and holds no send tool |
282
282
  | `canon:session-resume` | At the start of a session, to pick up what a previous one left |
283
- | `canon:session-map` | At the close of a session, to write the handoff a compaction would destroy |
283
+ | `canon:session-compact` | Before a compaction, to write a plain session's handoff note outside the board |
284
+ | `canon:session-map` | At the close of an orchestrating session, to write its board-side handoff |
284
285
 
285
286
  ### Keep the project current with the toolkit
286
287
 
@@ -304,6 +305,7 @@ This section is the corpus the coverage claim is measured against: every name `c
304
305
  | `canon:create-snippet` | For a reusable prompt |
305
306
  | `canon:create-standard` | For a new authoring convention |
306
307
  | `canon:draft-docs` | For a brand-new `docs/*.md` page, drafted against `standards/docs.md` |
308
+ | `canon:draft-ready` | For finished files a worker should copy, written as a ready folder with its overview, thin plan, and task |
307
309
  | `canon:draft-context` | For a brand-new `canon/context/<domain>.md` entry, drafted against `standards/context.md` |
308
310
  | `canon:draft-wireframes` | For a brand-new `canon/wireframes/<surface>.md` file, drafted against `standards/wireframes.md` |
309
311
  | `canon:draft-figure` | For a hand-drawn figure inside an existing doc, drafted against `standards/figures.md` and rendered through Mermaid or freehand SVG |
@@ -6,6 +6,10 @@ paths:
6
6
 
7
7
  # Ready standards
8
8
 
9
+ ## Writing a ready folder
10
+
11
+ - Load the `canon:draft-ready` skill to write a ready folder, its overview, the thin plan, and the task row. It owns the procedure. Report it rather than proceeding silently when it does not resolve, since it ships with the plugin and this rule ships with the CLI.
12
+
9
13
  ## Authority
10
14
 
11
15
  - Follow the ready standard for the folder layout, ordinal naming, the overview frontmatter, the mirrored tree, and the thin-plan contract. It is the single source. Read it with `canon standards ready`.
@@ -1,8 +1,7 @@
1
1
  ---
2
2
  description: Enforce settled waits and falsifiable guards in end-to-end tests
3
3
  paths:
4
- - 'e2e/*.ts'
5
- - 'e2e/**/*.ts'
4
+ - '**/e2e/**/*.ts'
6
5
  ---
7
6
 
8
7
  # End-to-end reliability standards
@@ -14,6 +13,8 @@ paths:
14
13
  - Bound every settle with an explicit timeout.
15
14
  - Do not raise a timeout to clear a failure that reproduces under load. Replace the wait with a settle.
16
15
  - Do not read a value once after a pause. Poll it.
16
+ - Settle a smooth scroll on the `scrollend` event. Fall back to a stillness window only when the target was already in view.
17
+ - Run with motion reduced unless the test asserts motion, and opt back in per test.
17
18
 
18
19
  ## Falsifiable guards
19
20
 
@@ -1,8 +1,7 @@
1
1
  ---
2
2
  description: Enforce which specs and which engines an end-to-end run covers at each point in the loop
3
3
  paths:
4
- - 'e2e/*.ts'
5
- - 'e2e/**/*.ts'
4
+ - '**/e2e/**/*.ts'
6
5
  ---
7
6
 
8
7
  # Test scope standards
@@ -24,6 +24,12 @@ paths:
24
24
  - Arrow keys: navigate within composite widgets. Do not let them exit the widget.
25
25
  - Escape: closes any overlay, popover, dialog, or dropdown. Cancels in-progress edits.
26
26
  - Do not introduce non-standard key bindings for common actions.
27
+ - Give every author-defined drag a single-pointer alternative and a keyboard path, such as move buttons or arrow-key reordering (WCAG 2.2 Dragging Movements, 2.5.7).
28
+
29
+ ## Pointer targets
30
+
31
+ - Size a web pointer target at least 24 by 24 CSS pixels, or space it so a 24 pixel circle centered on it touches no other target (WCAG 2.2 Target Size Minimum, 2.5.8).
32
+ - Size a native touch target at the platform figure, 44 points on iOS and 48 dp on Android. Do not apply the web figure to native, and do not apply the native figure as the web minimum.
27
33
 
28
34
  ## Composite widgets
29
35
 
@@ -34,6 +40,7 @@ paths:
34
40
 
35
41
  - Move focus into any overlay, popover, or dialog on open. Return focus to the trigger on close.
36
42
  - Never move focus on hover. Only on keyboard input or explicit pointer activation.
43
+ - Do not let a sticky header, footer, or overlay fully cover the focused control. Scroll it into view clear of them (WCAG 2.2 Focus Not Obscured, 2.4.11).
37
44
 
38
45
  ## ARIA semantics
39
46
 
@@ -46,3 +53,5 @@ paths:
46
53
  - Always provide `alt` on `<img>`. Use `alt=""` for decorative images.
47
54
  - Describe the content or function of the image, not its appearance. Do not prefix with "Image of" or "Photo of".
48
55
  - Alt text for functional images (icons, buttons) should describe the action, not the graphic.
56
+ - Give the visual boundary of a control and every graphic that carries meaning at least 3:1 contrast against adjacent colors (WCAG Non-text Contrast, 1.4.11).
57
+ - Never make color the only carrier of a state, error, or distinction. Pair it with text, an icon, or a pattern (WCAG Use of Color, 1.4.1).
@@ -26,6 +26,15 @@ paths:
26
26
  - Show the error directly under the field that caused it, not under an unrelated field.
27
27
  - When a change to field A causes a conflict involving field B, show the error under field A (the field the user edited), not under field B.
28
28
 
29
+ ## Submission errors
30
+
31
+ - When a failed submission carries several errors, move focus to a summary that links to each failing field. Keep the inline error beside each field.
32
+
33
+ ## Authentication and re-entry
34
+
35
+ - Let a password manager fill sign-in fields and accept a pasted value. Do not block paste or autofill on a password or code field (WCAG 2.2 Accessible Authentication, 3.3.8).
36
+ - Do not ask for information the user already gave earlier in the same flow. Prefill it or offer it for selection (WCAG 2.2 Redundant Entry, 3.3.7).
37
+
29
38
  ## Input sanitization
30
39
 
31
40
  - Trim leading and trailing whitespace from text inputs on blur or submit, not on every keystroke.
@@ -0,0 +1,29 @@
1
+ ---
2
+ description: Route edits that decide how a surface looks to the design-taste skill for the layer ordering, the coherence locks, and the defaults a model reaches for
3
+ paths:
4
+ - '**/*.css'
5
+ - '**/*.scss'
6
+ - 'canon/DESIGN.md'
7
+ ---
8
+
9
+ # Design taste standards
10
+
11
+ ## Authority
12
+
13
+ - Load the `canon:design-taste` skill for which design decision settles first, what must stay constant across a surface, and the defaults to reach past. It is the single source for all three.
14
+ - Load it before drafting, not after revising. A revision recovers a color and never recovers the composition a draft already settled.
15
+ - Report it rather than proceeding silently when the skill does not resolve. It ships with the plugin and this rule ships with the CLI, so a project that installed governance alone does not have it.
16
+ - Do not work the layer ordering or the coherence locks from memory.
17
+
18
+ ## When this fires and when it does not
19
+
20
+ - This rule scopes to the files where an undecided visual choice lands, which is the stylesheet and the design document. Building a surface somebody already decided is implementation rather than design, and the rules below carry what implementation owes.
21
+ - Load the skill by name when a component edit makes a visual choice nobody has taken yet, such as a new section's shape, a spacing relationship, or a radius. The glob cannot see that intent and no glob can.
22
+ - Do not load it to ship a decided design. A worker wiring a picked composition pays the read and gets nothing back.
23
+
24
+ ## Boundaries
25
+
26
+ - Accessibility, keyboard interaction and ARIA are a separate topic. `410-a11y` routes them, on every rendered path.
27
+ - States, empty and loading coverage, and destructive confirmation are a separate topic. `430-ux-completeness` routes them.
28
+ - Rendered copy casing, button labels and error wording are a separate topic. `400-ui` routes them.
29
+ - The floor those three carry is not restated in the skill, and it is not traded against a preference where the two collide.
@@ -0,0 +1,23 @@
1
+ ---
2
+ description: Moving content controls and the reduced-motion preference for rendered UI
3
+ paths:
4
+ - '**/*.tsx'
5
+ - '**/*.jsx'
6
+ - '**/*.astro'
7
+ - '**/*.html'
8
+ - '**/*.css'
9
+ ---
10
+
11
+ # Motion standards
12
+
13
+ ## Moving content
14
+
15
+ - Give any content that moves, blinks, or scrolls automatically for more than five seconds a control to pause, stop, or hide it (WCAG Pause, Stop, Hide, 2.2.2).
16
+ - Halt an auto-advancing carousel or ticker while it holds keyboard focus or pointer hover.
17
+ - Do not flash content more than three times in any one second.
18
+
19
+ ## Reduced motion
20
+
21
+ - Honor `prefers-reduced-motion: reduce` for every animation and transition that conveys no state. Remove it or replace it with an instant change.
22
+ - Keep essential state changes visible under reduced motion, using a fade or a static indicator instead of movement.
23
+ - Do not start video or animated backgrounds on their own when the preference is set.
@@ -1,2 +1,2 @@
1
1
  extends = "node"
2
- rules = ["210-astro", "350-security-web", "400-ui", "410-a11y", "430-ux-completeness", "440-surface-capture", "450-link-behavior"]
2
+ rules = ["210-astro", "300-testing-ts", "305-e2e-reliability", "306-test-scope", "350-security-web", "400-ui", "410-a11y", "420-forms", "430-ux-completeness", "440-surface-capture", "450-link-behavior", "460-design-taste", "470-motion"]
@@ -1,2 +1,2 @@
1
1
  extends = "node"
2
- rules = ["200-react", "230-nextjs", "250-tailwind", "300-testing-ts", "305-e2e-reliability", "306-test-scope", "310-zod", "350-security-web", "400-ui", "410-a11y", "420-forms", "430-ux-completeness", "440-surface-capture", "450-link-behavior"]
2
+ rules = ["200-react", "230-nextjs", "250-tailwind", "300-testing-ts", "305-e2e-reliability", "306-test-scope", "310-zod", "350-security-web", "400-ui", "410-a11y", "420-forms", "430-ux-completeness", "440-surface-capture", "450-link-behavior", "460-design-taste", "470-motion"]
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/canon",
3
3
  "type": "module",
4
- "version": "4.90.0",
4
+ "version": "4.93.0",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -19,7 +19,8 @@
19
19
  "tsconfig.json",
20
20
  "!scripts/sandbox",
21
21
  "!scripts/eval",
22
- "!**/*.test.ts"
22
+ "!**/*.test.ts",
23
+ "!**/*.test.tsx"
23
24
  ],
24
25
  "publishConfig": {
25
26
  "access": "public"
@@ -18,10 +18,11 @@
18
18
  # Only the HTML regenerates here. The PNG beside it is a chromium render whose
19
19
  # bytes move with the browser version, so asserting it in verify.sh would fail
20
20
  # on a machine whose chromium differs rather than on a stale count. Rebuild the
21
- # images with `canon capture assets/captures --selector .window --out assets`
21
+ # images with `canon capture assets/captures --selector .window --out assets/evidence`
22
22
  # after this script reports a change. The markup and the image sit in two
23
- # folders, so that run reads the sources here and sends every PNG and stamp back
24
- # up to assets/, where the documents point. The selector has no default, since
23
+ # folders, so that run reads the sources here and sends every PNG and stamp to
24
+ # assets/evidence/, where the documents point and where `canon pr evidence`
25
+ # compares a changed image. The selector has no default, since
25
26
  # the element a capture crops to belongs to the page rather than to the command,
26
27
  # and `.window` is the class this repository's own sources declare.
27
28
  # That capture also writes a .stamp beside each PNG, which records the digest of
@@ -25,6 +25,7 @@ import { formatStamp, hashSource, stampPath } from '@/capture/stamp'
25
25
  */
26
26
 
27
27
  const DEVICE_SCALE_FACTOR = 2
28
+ const DEFAULT_VIEWPORT_HEIGHT = 720
28
29
  const FONT_PROBE_SIZE = 72
29
30
  const FONT_PROBE_TEXT = 'canon capture 0123456789'
30
31
  const ABSENT_FAMILY = '__canon_absent_family__'
@@ -32,6 +33,7 @@ const ABSENT_FAMILY = '__canon_absent_family__'
32
33
  export interface CaptureOptions {
33
34
  selector: string
34
35
  outDir?: string
36
+ width?: number
35
37
  }
36
38
 
37
39
  export type CaptureResult =
@@ -54,7 +56,9 @@ export async function captureSources(
54
56
  const browser = await chromium.launch()
55
57
  try {
56
58
  return await Promise.all(
57
- sources.map((source) => captureOne(browser, source, options.selector)),
59
+ sources.map((source) =>
60
+ captureOne(browser, source, options.selector, options.width),
61
+ ),
58
62
  )
59
63
  } finally {
60
64
  await browser.close()
@@ -69,9 +73,19 @@ async function captureOne(
69
73
  browser: Browser,
70
74
  source: CaptureSource,
71
75
  selector: string,
76
+ width: number | undefined,
72
77
  ): Promise<CaptureResult> {
78
+ /**
79
+ * A viewport rather than a wrapper, since a media query answers to the
80
+ * viewport. Absent leaves the option out so the browser default stands and
81
+ * every committed capture renders as before. The height is Playwright's own
82
+ * default, which keeps `vh` units where they were.
83
+ */
73
84
  const page = await browser.newPage({
74
85
  deviceScaleFactor: DEVICE_SCALE_FACTOR,
86
+ ...(width === undefined
87
+ ? {}
88
+ : { viewport: { width, height: DEFAULT_VIEWPORT_HEIGHT } }),
75
89
  })
76
90
  try {
77
91
  await page.goto(
@@ -42,6 +42,11 @@ export const AUTHORING_CASES: readonly SkillCase[] = [
42
42
  'Write a brand-new docs page for the capture command, nothing under docs/ covers it yet.',
43
43
  expect: 'draft-docs',
44
44
  },
45
+ {
46
+ prompt:
47
+ 'I already wrote the finished skill files, package them as a ready folder with a plan and a task for a worker to copy.',
48
+ expect: 'draft-ready',
49
+ },
45
50
  {
46
51
  prompt: 'This project has no README.md at all, write one from scratch.',
47
52
  expect: 'draft-readme',
@@ -12,6 +12,11 @@ export const MISC_CASES: readonly SkillCase[] = [
12
12
  'Show me a few different treatments for this callout so I can pick one by looking.',
13
13
  expect: 'draft-and-pick',
14
14
  },
15
+ {
16
+ prompt:
17
+ 'Which layer of this landing page should I settle first, and what has to stay constant across it?',
18
+ expect: 'design-taste',
19
+ },
15
20
  {
16
21
  prompt:
17
22
  'This project has no logo yet. Draft one and give me a social card to go with it.',
@@ -29,6 +34,11 @@ export const MISC_CASES: readonly SkillCase[] = [
29
34
  {
30
35
  prompt:
31
36
  "We're about to hit the context limit, write the handoff before we lose state.",
37
+ expect: 'session-compact',
38
+ },
39
+ {
40
+ prompt:
41
+ 'I am the orchestrating session and about to compact, so write the board handoff as a session map on the task board.',
32
42
  expect: 'session-map',
33
43
  },
34
44
  {
package/src/cli.ts CHANGED
@@ -121,7 +121,7 @@ function showHelp(): void {
121
121
  `${GREY}│${NC} canon design board`,
122
122
  `${GREY}│${NC} canon slides render`,
123
123
  `${GREY}│${NC} canon slides list --json`,
124
- `${GREY}│${NC} canon capture assets/captures/install.html --selector .window --out assets`,
124
+ `${GREY}│${NC} canon capture assets/captures/install.html --selector .window --out assets/evidence`,
125
125
  `${GREY}│${NC} canon serve .canon/teach`,
126
126
  `${GREY}│${NC} canon inventory focus --json`,
127
127
  `${GREY}│${NC} canon drive http://localhost:4173 run.json --json`,