@erclx/canon 4.92.0 → 4.94.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 (39) hide show
  1. package/README.md +5 -5
  2. package/claude/.claude-plugin/plugin.json +1 -1
  3. package/claude/skills/teach-workspace/references/lesson-craft.md +1 -1
  4. package/claude/skills/test-craft/REQUIREMENT.md +54 -0
  5. package/claude/skills/test-craft/SKILL.md +77 -0
  6. package/claude/skills/test-craft/references/adopted.md +44 -0
  7. package/claude/skills/test-craft/references/component.md +42 -0
  8. package/claude/skills/test-craft/references/e2e.md +44 -0
  9. package/claude/skills/test-craft/references/unit.md +49 -0
  10. package/claude/skills/test-first/REQUIREMENT.md +2 -0
  11. package/claude/skills/test-first/SKILL.md +4 -2
  12. package/docs/agents/capture.md +12 -5
  13. package/docs/agents/commands.md +3 -3
  14. package/docs/agents/rule-citations.md +1 -1
  15. package/docs/agents/teach.md +1 -1
  16. package/docs/workflow/ai-workflow.md +9 -8
  17. package/governance/rules/core/010-testing.md +4 -2
  18. package/governance/rules/lib/300-testing-ts.md +4 -0
  19. package/governance/rules/lib/305-e2e-reliability.md +3 -2
  20. package/governance/rules/lib/306-test-scope.md +1 -2
  21. package/governance/rules/lib/330-testing-py.md +4 -0
  22. package/governance/stacks/astro.toml +1 -1
  23. package/package.json +3 -2
  24. package/scripts/core/regen-hero.sh +4 -3
  25. package/src/capture/render.ts +15 -1
  26. package/src/claude/cases/misc.ts +5 -0
  27. package/src/cli.ts +1 -1
  28. package/src/commands/capture.ts +25 -1
  29. package/src/commands/serve.ts +11 -1
  30. package/src/design/components.ts +40 -26
  31. package/src/gate/measures.ts +5 -4
  32. package/src/gate/stages.ts +1 -1
  33. package/src/serve/static.ts +90 -6
  34. package/src/teach/fonts.ts +9 -19
  35. package/src/teach/render.ts +2 -2
  36. package/tooling/astro/configs/playwright.config.ts +2 -0
  37. package/tooling/nextjs/configs/playwright.config.ts +2 -0
  38. package/tooling/vite-react/configs/playwright.config.ts +2 -0
  39. package/src/teach/render-fixture.tsx +0 -95
package/README.md CHANGED
@@ -6,7 +6,7 @@
6
6
 
7
7
  canon is a CLI and Claude Code plugin that stops your AI conventions drifting apart across repositories. It keeps one authoritative copy and installs it into each project on demand.
8
8
 
9
- ![The canon catalog, listing skills, governance rules, and standards with the count each ships, the workflow skills named, and a sample of the rule and standard names](assets/hero.png)
9
+ ![The canon catalog, listing skills, governance rules, and standards with the count each ships, the workflow skills named, and a sample of the rule and standard names](assets/evidence/hero.png)
10
10
 
11
11
  The counts above are read from the catalogs when the image is built, so they're what the repo actually ships today. See it live at [canon.erclx.dev](https://canon.erclx.dev).
12
12
 
@@ -39,7 +39,7 @@ claude plugin marketplace add https://github.com/erclx/canon
39
39
  claude plugin install canon@canon
40
40
  ```
41
41
 
42
- ![Adding the canon marketplace and installing the plugin in Claude Code](assets/install.png)
42
+ ![Adding the canon marketplace and installing the plugin in Claude Code](assets/evidence/install.png)
43
43
 
44
44
  The skills land as `/canon:<name>`. If your session was already open, run `/reload-plugins` to pick them up.
45
45
 
@@ -67,19 +67,19 @@ Each domain has a canonical source in this repo and a thin install or sync CLI o
67
67
 
68
68
  Those domains split on one line: some are copied into your project and become yours to edit, and some are never copied at all.
69
69
 
70
- ![canon tooling list and canon standards list side by side, the first showing five stacks with their dependency and script counts, the second showing standards against the artifact each governs](assets/install-surface.png)
70
+ ![canon tooling list and canon standards list side by side, the first showing five stacks with their dependency and script counts, the second showing standards against the artifact each governs](assets/evidence/install-surface.png)
71
71
 
72
72
  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.
73
73
 
74
74
  Governance is the third shape, and it is worth seeing rather than reading about, because the glob beside each rule is what decides whether it reaches a session at all.
75
75
 
76
- ![canon gov list, showing seven stacks with the rules each carries and a sample of rules beside the path glob that loads each one](assets/governance.png)
76
+ ![canon gov list, showing seven stacks with the rules each carries and a sample of rules beside the path glob that loads each one](assets/evidence/governance.png)
77
77
 
78
78
  Stacks compose, so a react project inherits node and node inherits base. A rule with a glob loads only when a matching path is edited, and a rule with none loads every session. Both counts and every row above are read from the catalogs when the image is built.
79
79
 
80
80
  The toolkit tracks its own work the same way, in a task board no catalog can read back.
81
81
 
82
- ![the task board's Run now table, one row per task in flight, each naming the files it touches and the plan it runs under](assets/task-board.png)
82
+ ![the task board's Run now table, one row per task in flight, each naming the files it touches and the plan it runs under](assets/evidence/task-board.png)
83
83
 
84
84
  `.canon/tasks/` is gitignored session scratch, so unlike the two frames above, this one is a hand-taken snapshot rather than something the build reads live, and it goes stale the moment the board moves.
85
85
 
@@ -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.92.0",
4
+ "version": "4.94.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -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.
@@ -0,0 +1,54 @@
1
+ ---
2
+ name: test-craft
3
+ description: Why a session writing a test needs a stated layer rule and a quality filter, and where that judgment stops short of the procedures that run it
4
+ ---
5
+
6
+ # Test craft requirement
7
+
8
+ ## Gap
9
+
10
+ Without this skill, a session writing a test has no surface telling it which layer the test belongs at or what makes one good. It writes whatever the touched file suggests, and nothing in the toolkit corrects that choice before the test lands.
11
+
12
+ The one surface that did pick a layer picked the expensive one. The UI test skill routed interactions, state transitions, loading, empty, and error states to a browser end to end run, the opposite of every external source read. `test-first` runs red and green with no layer choice and no refactor step. The testing rule states floors on structure and says nothing about layer.
13
+
14
+ The failure is measured on a real target. A single-page portfolio site carried 219 end to end tests against 4 unit test files, an inverted pyramid. Its suite could not raise its worker count without failing between 11 and 24 tests on every retry, because timing-sensitive assertions sat at the layer where CI load decides the result. One copy edit failed three engines because the copy was asserted in a browser run.
15
+
16
+ Moving the guidance into a path-scoped rule does not reach the session that needs it. A path-scoped rule loads when a session reads a matching file and not when it creates one, measured in three headless runs, so a rule scoped to test files is silent on the first test of every feature.
17
+
18
+ ## Must
19
+
20
+ - State the smallest-layer rule with a decision guide keyed on what the behavior touches, so a session picks a layer from the behavior rather than from the file it is in
21
+ - Open every test with the question of which production change makes it fail and whether that change is a bug or a decision, since a test with no answer proves nothing
22
+ - State the end to end budget in words: journeys and what only a real browser renders, never a state a component test reaches
23
+ - Carry a final filter the session runs over its own tests, each item a shape it can check: mirror assertion, change detector, assertion on a mock, framework behavior, fixed pause, and the mutation check
24
+ - Hold in the body what every test decision pays for, and defer each layer's specifics to a reference loaded only when a test at that layer is being written
25
+ - Promise a middle layer for behavior a simulated DOM cannot render, naming the real-browser component runners, so a static site is not left choosing between a simulated DOM that computes no layout and a whole-site run
26
+ - Repeat in the end to end reference the two floors a session writing its first spec never loads through the path-scoped rule, being the `scrollend` settle and motion reduced by default
27
+ - Record the external patterns adopted and declined with the reason for each, so a later session extends the position instead of re-deriving it
28
+ - Trigger on writing or changing any test, after implementing as well as before, since regression tests and end to end tests are mostly written after the code exists
29
+
30
+ ## Must not
31
+
32
+ - Name a framework or runner in the body. Runner specifics live in the references and in the language testing rules, so the body holds across stacks.
33
+ - Carry a numeric mix across layers. A codebase-wide ratio is a target a session writing one test cannot act on.
34
+ - Restate the structure floors the testing rule already carries, such as arrange, act, assert, one behavior per test, isolation, and no snapshots
35
+ - Restate the settle, guard, and run-scope rules the end to end rules carry, beyond the two floors named above
36
+ - Run the red, green, refactor procedure or decide its order, which `test-first` owns
37
+ - Import an external skill's tone, such as a law stated as absolute or a table of rationalizations with rebuttals
38
+ - Be reached only by an author typing its name. The intended non-author callers are `test-first`'s first step and a description matching any request to write or change a test. A test written in a session that never loaded this skill is the evidence the trigger is too weak, and nothing answers that before the skill has run.
39
+
40
+ ## Guards
41
+
42
+ - A behavior spanning more than one row of the decision guide is split into one test per layer rather than asserted whole at the highest layer it touches
43
+ - A test failing the final filter is rewritten or cut, never kept with a note
44
+
45
+ ## Out of scope
46
+
47
+ - The red, green, refactor loop and the order it runs in: `test-first`, which loads this skill at its first step
48
+ - Reproducing a failure with no known cause: `systematic-debugging`
49
+ - Structure floors for every test: the core testing rule
50
+ - Runner choice, file suffix, placement, and query conventions: the TypeScript and Python testing rules
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`
53
+ - Grading the tests a pull request adds: `review-pr`
54
+ - Whether the guidance moves where a session puts a test, which needs a measured with-and-without run rather than a rule here
@@ -0,0 +1,77 @@
1
+ ---
2
+ name: test-craft
3
+ description: Carries the rule that decides which layer a test belongs at, the question each one opens with, the end to end budget, and the final filter a session runs over what it wrote. Use when writing, changing, or judging a test, after implementing as well as before, when adding a regression guard, when choosing between unit, component, or end to end, or when asked "where should this test go", "is this a good test", "should this be an e2e test", or "why is our e2e suite so slow". Do NOT use for the red, green, refactor procedure, which is `test-first`, or for a bug nobody has explained yet, which is `systematic-debugging`.
4
+ ---
5
+
6
+ # Test craft
7
+
8
+ A session with no stated layer writes the test the file it is touching suggests, so a loading state lands in a full browser run and an end to end suite grows past what it guards. This skill carries the judgment: where a test belongs, what it has to prove, and what to cut before handing it over.
9
+
10
+ Load it before writing a test rather than after. A test written at the wrong layer passes review, and moving it later costs a second test.
11
+
12
+ ## Open every test with one question
13
+
14
+ Ask what production change makes this test fail, and whether that change is a bug or a decision.
15
+
16
+ - A bug is what the test exists for. Write it.
17
+ - A decision, such as a reworded message or a reordered field, makes the test a change detector. Assert the contract the decision serves, or drop the test.
18
+ - No answer means the test proves nothing yet. Name the break before writing the assertion.
19
+ - Cover the edges of a behavior as well as its main path: the empty input, the boundary value, the refused call. Each edge is its own case at the same layer.
20
+
21
+ ## Put the test at the smallest layer that can catch the break
22
+
23
+ Size a test by what it touches rather than by what it covers. Pick the first row that can observe the break:
24
+
25
+ | The behavior touches | Layer |
26
+ | ------------------------------------------------------------------------------------ | ------------------------------------- |
27
+ | Logic with no rendering and no I/O: a parser, a reducer, a formatter, a rule | Unit |
28
+ | A module boundary on one machine: a file, a local database, a handler with its route | Integration, still in the unit runner |
29
+ | Rendered behavior: states, interactions, conditional rendering, validation, focus | Component |
30
+ | A journey across routes, or what only a real browser renders: layout, scroll, motion | End to end |
31
+
32
+ - Read the row off the behavior, never off the file. A component file holding a pure formatter takes a unit test for the formatter.
33
+ - Split a behavior that spans rows. Test the copy at unit and the render at component, rather than asserting both in one browser run.
34
+ - Write the lower test when a higher one catches a break no lower test did. A red end to end run with every unit and component test green names a missing lower test.
35
+ - Reach for a real-browser component test before end to end when a simulated DOM cannot render the behavior. The middle layer exists for layout and scroll too.
36
+
37
+ ## Spend end to end on journeys
38
+
39
+ - Cover the journeys a user cannot lose without the product failing, and what no lower layer can render.
40
+ - Never write an end to end test for a state a component test can reach, whether loading, empty, error, or a toggle.
41
+ - Count a new end to end test as a cost every later run pays. A suite grows by quality rather than by count, which is what keeps continuous integration fast.
42
+ - Carry no numeric mix. The right shape follows the architecture, and a ratio across a codebase is a target nobody writing one test can act on.
43
+
44
+ ## Keep test code readable over clever
45
+
46
+ - Prefer a descriptive, repeated arrange over a shared helper a reader has to open. A test is read when it fails, by someone who did not write it.
47
+ - Assert through the public surface a caller uses. A test reaching into internals breaks on every refactor and catches nothing a caller sees.
48
+ - Prefer a real collaborator, then a fake, then a stub, and a mock last. A mock asserts how the code is built rather than what it does.
49
+
50
+ ## Read the reference for the layer
51
+
52
+ Read the one reference matching the layer the table picked, before writing the first test at it. Skip the other two.
53
+
54
+ - Unit and integration: `${CLAUDE_SKILL_DIR}/references/unit.md`
55
+ - Component: `${CLAUDE_SKILL_DIR}/references/component.md`
56
+ - End to end: `${CLAUDE_SKILL_DIR}/references/e2e.md`
57
+
58
+ Read `${CLAUDE_SKILL_DIR}/references/adopted.md` only when extending this guidance or arguing against a rule in it. It records which external patterns were adopted, which declined, and why.
59
+
60
+ ## Run the final filter before handing tests over
61
+
62
+ Check every test written this session against each line. A test that fails one is rewritten or cut, never kept with a note.
63
+
64
+ - **Mirror assertion.** The expected value is computed by the code under test. Write the literal the behavior should produce.
65
+ - **Change detector.** The assertion pins a constant, a message's wording, or a markup shape no caller depends on.
66
+ - **Assertion on a mock.** The test proves a double was called rather than that the behavior happened.
67
+ - **Framework behavior.** The test proves the runtime, the router, or the library works rather than this code's contract.
68
+ - **Fixed pause.** A sleep or timeout stands in for a condition. Settle on the condition instead.
69
+ - **Mutation check.** Break the code under test in three to five plausible ways in your head. At least one test fails for each, or a case is missing.
70
+
71
+ ## What this delegates
72
+
73
+ - The red, green, refactor procedure and its order: `test-first`, which loads this skill at its first step
74
+ - Reproducing a failure with no known cause: `systematic-debugging`
75
+ - Test structure floors such as arrange, act, assert, one behavior per test, isolation, and no snapshots: `010-testing`
76
+ - Runner, file suffix, placement, and query conventions per language: `300-testing-ts` and `330-testing-py`
77
+ - Settle, guard, and run-scope rules inside an end to end file: `305-e2e-reliability` and `306-test-scope`
@@ -0,0 +1,44 @@
1
+ ---
2
+ title: Adopted and declined
3
+ description: Which external testing patterns this skill adopted, which it declined, and why, so a later session extends the position instead of re-deriving it
4
+ ---
5
+
6
+ # Adopted and declined
7
+
8
+ External sources were read and filtered rather than imported. This file is the record. A later session extending the skill adds to it rather than re-arguing an item already settled here.
9
+
10
+ ## Adopted
11
+
12
+ ### Choosing the layer
13
+
14
+ **The smallest-layer rule.** Fowler: "Push your tests as far down the test pyramid as you can", and write a lower test when a higher one catches what no lower one did ([Practical Test Pyramid](https://martinfowler.com/articles/practical-test-pyramid.html)). It is the body's layer rule and the reason for the decision table.
15
+
16
+ **Sizing by what a test touches.** Google's small, medium, and large sizes name what a test may touch rather than what it covers ([Software Engineering at Google, ch. 11](https://abseil.io/resources/swe-book/html/ch11.html)). The decision table reads the same way, keyed on what the behavior touches, so it transfers across stacks without naming a runner. The scope names unit, component, and end to end stay as the labels, since those are the names a reader and a file suffix already use.
17
+
18
+ **The component layer carrying UI weight.** Kent C. Dodds's trophy ([Write tests](https://kentcdodds.com/blog/write-tests)) and Testing Library's principle that tests resembling use give more confidence ([guiding principles](https://testing-library.com/docs/guiding-principles)). The pyramid and the trophy are one rule applied to different code: a component's logic is its rendered behavior, so its lowest layer is a rendered test.
19
+
20
+ ### Judging a test
21
+
22
+ **The opening question and the final filter.** From `obra/superpowers@5bf4e78`, `skills/test-driven-development/writing-good-tests.md`: name the production change that makes a test fail and whether it is a bug or a decision, then cut mirror assertions, change detectors, assertions on mocks, framework tests, and run the mutation check. Adopted as the body's opening question and final filter, with the fixed pause added from the end to end incidents.
23
+
24
+ **Doubles in preference order and DAMP over DRY.** From `addyosmani/agent-skills@c004a74`, `skills/test-driven-development/SKILL.md`: real, fake, stub, mock, and descriptive test bodies over shared helpers. Adopted in `unit.md`.
25
+
26
+ **Kent Beck's desiderata as background.** The twelve properties at [testdesiderata.com](https://testdesiderata.com/) trade against each other. "Behavioral" and "structure-insensitive" together are the whole of "test behavior, not implementation", which is why the body states that pair as asserting through the public surface.
27
+
28
+ ### Per layer
29
+
30
+ **Component query and wait rules.** Role queries first, user events over synthetic ones, one assertion per wait, no side effects inside a wait ([Common mistakes](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library)). Adopted in `component.md` with the library names removed from the rules.
31
+
32
+ **Web-first assertions, actionability, config-wide reduced motion, sharding.** From Playwright's own pages ([best practices](https://playwright.dev/docs/best-practices), [actionability](https://playwright.dev/docs/actionability), [TestOptions](https://playwright.dev/docs/api/class-testoptions)). Adopted in `e2e.md`, where the runner is named because the reference is the per-layer home for runner specifics.
33
+
34
+ ## Declined
35
+
36
+ **A numeric mix.** Google targets roughly 80% unit, 15% integration, 5% end to end ([same chapter](https://abseil.io/resources/swe-book/html/ch11.html)). A ratio is a codebase-wide target that a session writing one test cannot act on, and web.dev's point that the shape "should fit your architecture" ([Pyramid or Crab](https://web.dev/articles/ta-strategies)) is what a motion-heavy static site needs. The body states the rule and the budget in words instead.
37
+
38
+ **Delete the code written before its test.** `superpowers`'s test-driven skill tells a session to delete implementation written ahead of a failing test. Declined: this toolkit audits the order after the fact with its test-order verb and reports rather than destroys, since deleting working code punishes the session without restoring the evidence the red run would have given.
39
+
40
+ **Rationalization tables and an iron law.** Both external test-driven skills carry an "Iron Law" and a table of excuses with rebuttals. Declined for tone: this toolkit states a rule once with its reason, and a table of arguments against imagined objections costs a read on every load and adds no constraint.
41
+
42
+ **One skill per layer.** A unit skill, a component skill, and an end to end skill each loaded on its own trigger. Declined because the layer has to be chosen before the right skill can load, so the choice ends up restated in all three or in a fourth skill that is this one.
43
+
44
+ **A standard in place of a skill.** The guidance is judgment applied while writing, loaded on intent. A standard governs an artifact a reader checks afterwards, and nobody checks a test file against a layer rule after the fact.
@@ -0,0 +1,42 @@
1
+ ---
2
+ title: Component tests
3
+ description: Render with real children, query by role, drive with user events, wait on one condition at a time, and move to a real-browser runner for what a simulated DOM cannot render
4
+ ---
5
+
6
+ # Component tests
7
+
8
+ Read this before writing the first test at the component layer. A component's logic is its rendered behavior, so the lowest layer that catches a break in a loading state, an interaction, or a validation message is a rendered component test rather than a browser run across the whole site. Kent C. Dodds's testing trophy makes this the widest band for UI code ([Write tests](https://kentcdodds.com/blog/write-tests)), on the principle Testing Library states: "The more your tests resemble the way your software is used, the more confidence they can give you" ([guiding principles](https://testing-library.com/docs/guiding-principles)).
9
+
10
+ ## What belongs here
11
+
12
+ - Every state a component can show: loading, empty, error, populated, disabled.
13
+ - Every interaction that changes what it shows: a toggle, a filter, a form's validation and submit.
14
+ - Conditional rendering, keyboard handling inside the component, and focus moving where it should.
15
+ - Not a journey across routes, and not layout, scroll, or motion a simulated DOM does not compute. Those go to a real-browser component test below, or to end to end.
16
+
17
+ ## Render
18
+
19
+ - Render with real children. Stubbing a child hides the break at the seam the user actually meets.
20
+ - Double the network at its edge with a request interceptor rather than mocking the data hook, so the component's own loading and error handling runs.
21
+ - Build the props or the fixture through a factory, keeping the values this case depends on visible.
22
+
23
+ ## Query and act like a user
24
+
25
+ - Query by role and accessible name first, then by label, then by visible text. Reach for a test id only when nothing a user perceives identifies the element ([common mistakes](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library)).
26
+ - Drive interaction through the user-event API rather than firing synthetic events, so focus, pointer, and keyboard sequences run as a browser would run them ([same](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library)).
27
+ - Assert what the user perceives: text, role, state, focus. Never assert a class name, a hook's return value, or an internal state variable.
28
+
29
+ ## Wait on one condition
30
+
31
+ - Put one assertion inside each wait. A wait holding several retries on the first and reports the wrong failure ([common mistakes](https://kentcdodds.com/blog/common-mistakes-with-react-testing-library)).
32
+ - Never put a side effect inside a wait. It runs once per retry.
33
+ - Prefer the query that waits over a wait wrapping a query that does not.
34
+
35
+ ## When a simulated DOM cannot render it
36
+
37
+ A simulated DOM computes no layout, no scroll position, and no animation frame. When the behavior under test is one of those, move to a runner that renders the component in a real browser rather than promoting the test to end to end.
38
+
39
+ - Vitest browser mode runs tests "in the browser natively", with Playwright or WebdriverIO as the provider, and lists React, Vue, Svelte, Angular, Lit, Preact, and Qwik. Astro is not on the list ([Vitest browser mode](https://vitest.dev/guide/browser/)). For a static site, test the client module that drives the behavior against a fixture DOM here.
40
+ - Playwright component testing is stable in plain `@playwright/test` through a mount fixture, and "if your dev server can render it, Playwright can test it" ([Playwright component testing](https://playwright.dev/docs/test-components)). The experimental `-ct-*` packages were removed after 1.62.
41
+
42
+ Either runs in a real browser without navigating the whole site, which keeps the test at the component's cost while observing what only a browser computes.
@@ -0,0 +1,44 @@
1
+ ---
2
+ title: End to end tests
3
+ description: Which journeys earn a browser run, how to settle rather than pause, why motion is off by default, and how isolation and sharding keep the suite fast, with two worked failures
4
+ ---
5
+
6
+ # End to end tests
7
+
8
+ Read this before writing the first end to end test for a change. A browser run across the real site is the most expensive test a project owns and the most exposed to timing, so every one written here has to be one no lower layer could have been.
9
+
10
+ Read the settle, guard, and run-scope rules in the project's end to end reliability and test scope rules once they load. They fire on a read of an existing end to end file, and not on the creation of a new one, so the two floors below are repeated here for the session writing its first spec.
11
+
12
+ ## What earns a browser run
13
+
14
+ - A journey a user cannot lose without the product failing, crossing more than one route: sign up, check out, publish ([Practical Test Pyramid](https://martinfowler.com/articles/practical-test-pyramid.html)).
15
+ - Behavior only a real browser computes across the whole page: layout at a breakpoint, scroll position, focus crossing a route, motion.
16
+ - Not a state a component test can reach. Loading, empty, and error states, a toggle, and a validation message belong one layer down, even when the component sits on a page with other end to end coverage.
17
+ - Not copy. Asserting exact wording in a browser run makes every copy edit a failure across every engine the gate runs.
18
+
19
+ Suites dominated by end to end tests inflate both runtime and flake ([Google Testing Blog, 2015](https://testing.googleblog.com/2015/04/just-say-no-to-more-end-to-end-tests.html)). Nothing fixes a count ceiling, and the right shape depends on the architecture ([Pyramid or Crab](https://web.dev/articles/ta-strategies)). A site whose behavior is mostly layout and motion carries more browser tests than a form-heavy app, and each one still has to earn its place.
20
+
21
+ ## Settle on conditions
22
+
23
+ - Assert with a web-first assertion that retries until the condition holds. A manual visibility check reads once and does not retry ([best practices](https://playwright.dev/docs/best-practices)).
24
+ - Lean on actionability for clicks and fills. An element counts as stable once it "has maintained the same bounding box for at least two consecutive animation frames", so an action waits out an animation on its own ([actionability](https://playwright.dev/docs/actionability)). A read does not wait, so poll it.
25
+ - Never pause for a fixed duration to stand in for a condition. Reserve a fixed window for asserting that nothing happened across it.
26
+ - Settle a smooth scroll on the `scrollend` event. Fall back to a stillness window only when the target was already in view.
27
+
28
+ ## Motion off by default
29
+
30
+ - Run with motion reduced unless the test asserts motion, and opt back in per test with `page.emulateMedia({ reducedMotion: 'no-preference' })`.
31
+ - Set it once in the config's `use` block as `reducedMotion: 'reduce'` rather than per test, since the option defaults to `'no-preference'` ([TestOptions](https://playwright.dev/docs/api/class-testoptions)). A site honoring the reduced preference then has nothing to wait out in any test that does not opt back in.
32
+
33
+ ## Isolation and speed
34
+
35
+ - Give every test its own state: a fresh context, its own account or seed, nothing left behind for the next test.
36
+ - Speed a slow suite with parallel workers and sharding across machines rather than by cutting waits ([best practices](https://playwright.dev/docs/best-practices)). A suite that fails once workers rise carries timing-sensitive assertions, and those are the tests to move down a layer.
37
+ - Capture a trace on the first retry rather than a video on every run ([best practices](https://playwright.dev/docs/best-practices)).
38
+ - Count the cost before adding a test: its runtime, times every engine in the matrix, times every push. That product is what the gate charges each later change.
39
+
40
+ ## Two worked failures
41
+
42
+ **A copy assertion in a browser run.** A portfolio site's home spec asserted the exact wording of its about section. A one-line copy edit then failed three engines in continuous integration, and the deploy waited on it. The production change that broke the test was a decision rather than a bug, which makes the assertion a change detector. The copy belonged in a unit test beside the module that holds it, and the browser run needed at most the section's presence.
43
+
44
+ **A settle that narrowed a failure without removing it.** A focus-ring spec waited on a scripted focus read with a condition wait, replacing a fixed pause. The next push to the trunk still failed one engine on every attempt with the condition timing out, and flaked a second test in the same file, which skipped the deploy. The settle was correct and the layer was wrong: an assertion whose result depends on how loaded the CI machine is keeps failing at the layer where load decides it. Moving the focus behavior to a real-browser component test takes it off the whole-site run.
@@ -0,0 +1,49 @@
1
+ ---
2
+ title: Unit and integration tests
3
+ description: Doubles in preference order, descriptive over shared setup, table-driven cases, and the mutation check, for tests that run in one process
4
+ ---
5
+
6
+ # Unit and integration tests
7
+
8
+ Read this before writing the first test at the unit or integration layer. A unit test runs in one process with no network, no sleep, and no real I/O. An integration test may touch the local machine, such as a temporary directory or a local database, and nothing beyond it. Google's size vocabulary draws the same line by what a test may touch rather than what it covers ([Software Engineering at Google, ch. 11](https://abseil.io/resources/swe-book/html/ch11.html)).
9
+
10
+ ## Doubles
11
+
12
+ Prefer the most real collaborator the test can afford, in this order (`addyosmani/agent-skills@c004a74`, `skills/test-driven-development/SKILL.md`):
13
+
14
+ 1. **Real.** The actual collaborator, when it is fast and deterministic. A pure function never needs a double.
15
+ 2. **Fake.** A working implementation with a shortcut, such as an in-memory store standing in for a database.
16
+ 3. **Stub.** Canned answers to the calls the test makes, with no assertions of its own.
17
+ 4. **Mock.** A double that asserts how it was called. Reach for it only when the call itself is the contract, such as a payment that must be sent exactly once.
18
+
19
+ - Double only at a boundary the project does not own or cannot run locally: the network, the clock, randomness, a third-party service.
20
+ - Never assert that a mock exists or was constructed. That proves the test's own setup rather than the code (`obra/superpowers@5bf4e78`, `skills/test-driven-development/writing-good-tests.md`).
21
+ - Control the clock and randomness through an injected source rather than patching a global for the whole file.
22
+
23
+ ## Descriptive over shared
24
+
25
+ - Prefer DAMP over DRY in a test body: descriptive and meaningful phrases a reader follows without opening a helper (`addyosmani/agent-skills@c004a74`, `skills/test-driven-development/SKILL.md`).
26
+ - Extract a factory for the data a test builds, and keep the values that matter to this case visible at the call site.
27
+ - Never share mutable state between tests. A test passing only after another one ran is two tests pretending to be one.
28
+
29
+ ## Table-driven cases
30
+
31
+ - Write one table when several inputs exercise the same behavior and differ only in value. Each row names the case it proves.
32
+ - Keep a row to input and expected output. A row carrying its own branch of setup is a separate test.
33
+ - Split the table when a case needs a different assertion. A conditional inside the loop body is the loop hiding a second behavior.
34
+
35
+ ## Integration at a boundary
36
+
37
+ - Test a module through the interface its callers use, with its real neighbors on the same machine.
38
+ - Use a temporary directory or an isolated database per test, created and removed by the test, never a shared fixture a previous run left behind.
39
+ - Cover the failure path at the boundary: the missing file, the refused write, the malformed record. That is where an integration test earns its extra cost over a unit test.
40
+
41
+ ## The mutation check
42
+
43
+ Run it on each new test before calling it done (`obra/superpowers@5bf4e78`, `skills/test-driven-development/writing-good-tests.md`):
44
+
45
+ 1. Name three to five plausible breaks in the code under test: an off-by-one, a flipped condition, a dropped branch, a wrong default, a swallowed error.
46
+ 2. For each, say which test fails.
47
+ 3. A break no test catches is a missing case. Write it, or state why the break is not a bug.
48
+
49
+ A mutation testing tool automates the same check across a codebase. The manual pass here is the version a session writing one test can run.
@@ -15,6 +15,7 @@ Without this skill, a session implementing a planned change writes the test afte
15
15
  - Run the test before implementing and confirm it fails for the missing behavior, not for an unrelated mistake in the test itself
16
16
  - Implement the minimum the current test demands, and start a new test before extending past it
17
17
  - Re-run the test after implementing and confirm the pass is the one the test was written to prove
18
+ - Refactor the implementation and the test once the suite is green, re-running it after each change, since the smallest change that passes leaves duplication and shortcuts the loop exists to remove before they reach history
18
19
 
19
20
  ## Must not
20
21
 
@@ -29,6 +30,7 @@ Without this skill, a session implementing a planned change writes the test afte
29
30
 
30
31
  ## Out of scope
31
32
 
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
32
34
  - Finding the cause of an unexplained failure: `canon:systematic-debugging`
33
35
  - Confirming visual output after a change lands: `070-planning.md` states the order and `ui-test` covers it
34
36
  - The mechanical audit of whether an implementation reached history ahead of its test: `canon gov test-order`, invoked from `auto-ship`
@@ -1,17 +1,18 @@
1
1
  ---
2
2
  name: test-first
3
- description: Write the failing test for a behavior before writing the code that satisfies it, confirm it fails for the right reason, then implement the smallest change that turns it green. Auto-triggers before implementing a planned feature, adding a function or an endpoint, or extending existing behavior whose new shape is already decided. Do NOT use for a failure with no known cause, which is canon:systematic-debugging, or when the test for the behavior already exists and already passes.
3
+ description: Write the failing test for a behavior before writing the code that satisfies it, confirm it fails for the right reason, implement the smallest change that turns it green, then refactor with the suite green. Auto-triggers before implementing a planned feature, adding a function or an endpoint, or extending existing behavior whose new shape is already decided. Do NOT use for a failure with no known cause, which is canon:systematic-debugging, or when the test for the behavior already exists and already passes.
4
4
  ---
5
5
 
6
6
  # Test first
7
7
 
8
8
  ## The loop
9
9
 
10
- 1. Find or write the test for the behavior before touching the code it covers. Follow the pairing convention the project already uses, which is commonly a test sitting beside its subject under one name, so that `foo.ts` takes `foo.test.ts` beside it.
10
+ 1. Load `canon:test-craft` and pick the layer the test belongs at before writing it. Then find or write the test at that layer before touching the code it covers. Follow the pairing convention the project already uses, which is commonly a test sitting beside its subject under one name, so that `foo.ts` takes `foo.test.ts` beside it. Report it rather than proceeding silently when `canon:test-craft` does not resolve, and write the test at the smallest layer that can observe the behavior.
11
11
  2. Write the test against the behavior as it should exist once the change lands, naming the case for what it proves rather than for the function it calls.
12
12
  3. Run only that test and read the failure. Confirm it fails because the behavior is missing, not because of a typo, a missing import, or a signature the test itself got wrong. A test failing for the wrong reason still reads green the moment any code exists to satisfy that wrong reason, so treat any other failure as a defect in the test and fix the test before moving on.
13
13
  4. Implement the smallest change that turns the failure into a pass. Do not add behavior the test does not ask for. A second behavior wants its own test written first, not a shortcut folded into the change already open.
14
14
  5. Run the test again and confirm it passes for the reason it was written for, not by accident. Then run the surrounding suite so a change made to satisfy this test has not broken another.
15
+ 6. Refactor with the suite green. Remove the duplication and the shortcuts step 4 left, in the implementation and in the test, without changing what either does. Run the suite after each change, and undo a change that turns anything red rather than fixing forward.
15
16
 
16
17
  ## Extending existing behavior
17
18
 
@@ -20,5 +21,6 @@ description: Write the failing test for a behavior before writing the code that
20
21
 
21
22
  ## What this skill does not cover
22
23
 
24
+ - Which layer a test belongs at and what makes it a good one. `canon:test-craft` owns that judgment, and step 1 loads it.
23
25
  - A failure with no known cause. Route through `canon:systematic-debugging` first, whose own fix phase already writes the reproducing test as part of finding the cause. This skill starts once the shape of the fix or the feature is already decided.
24
26
  - Confirming a change looks right once it has landed, which is a separate concern from whether the test passed and runs after implementation rather than before it.
@@ -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
 
@@ -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 components `src/teach/render-fixture.tsx` composes by hand. 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
@@ -226,14 +226,15 @@ This section is the corpus the coverage claim is measured against: every name `c
226
226
 
227
227
  ### Build the feature
228
228
 
229
- | Skill | When to use |
230
- | ---------------------------- | ------------------------------------------------------------------------- |
231
- | `canon:session-worktree` | At the plan-to-execute boundary, to get an isolated tree and branch |
232
- | `canon:auto-ship` | After plan approval, to chain implement, verify, review, draft PR |
233
- | `canon:project-commands` | When the project's own command needs running |
234
- | `canon:test-first` | Before implementing a planned change, to write and run its test red first |
235
- | `canon:systematic-debugging` | When a test fails or a bug surfaces, to force root cause first |
236
- | `canon:ui-test` | After a UI change, to generate e2e tests and a visual checklist |
229
+ | Skill | When to use |
230
+ | ---------------------------- | ------------------------------------------------------------------------------- |
231
+ | `canon:session-worktree` | At the plan-to-execute boundary, to get an isolated tree and branch |
232
+ | `canon:auto-ship` | After plan approval, to chain implement, verify, review, draft PR |
233
+ | `canon:project-commands` | When the project's own command needs running |
234
+ | `canon:test-first` | Before implementing a planned change, to run its test red, green, then refactor |
235
+ | `canon:test-craft` | When writing or changing any test, to pick its layer and filter what it asserts |
236
+ | `canon:systematic-debugging` | When a test fails or a bug surfaces, to force root cause first |
237
+ | `canon:ui-test` | After a UI change, to generate e2e tests and a visual checklist |
237
238
 
238
239
  ### Check the work before it leaves the branch
239
240
 
@@ -4,6 +4,10 @@ description: Enforce universal testing standards and best practices
4
4
 
5
5
  # Testing standards
6
6
 
7
+ ## Before writing a test
8
+
9
+ - Load the `canon:test-craft` skill before choosing the layer for a new test, and report it rather than proceeding silently when the skill does not resolve.
10
+
7
11
  ## Test structure
8
12
 
9
13
  - Structure tests using the Arrange, Act, Assert (AAA) pattern.
@@ -12,9 +16,7 @@ description: Enforce universal testing standards and best practices
12
16
 
13
17
  ## Test focus
14
18
 
15
- - Test user-visible behavior rather than implementation details.
16
19
  - Do not test private functions or internal state directly.
17
- - Cover critical paths and edge cases.
18
20
  - Do not target arbitrary coverage percentages.
19
21
 
20
22
  ## Organization and isolation
@@ -9,6 +9,10 @@ paths:
9
9
 
10
10
  # TypeScript/JavaScript testing tooling
11
11
 
12
+ ## Layer
13
+
14
+ - Load the `canon:test-craft` skill to pick the layer a test belongs at before choosing the tooling below, and report it rather than proceeding silently when the skill does not resolve.
15
+
12
16
  ## Unit and integration
13
17
 
14
18
  - Use Vitest for unit and integration tests.