@erclx/canon 5.76.0 → 5.78.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 (90) hide show
  1. package/CHANGELOG.md +33 -0
  2. package/claude/.claude-plugin/plugin.json +1 -1
  3. package/claude/skills/auto-ship/references/ui-checklist.md +9 -1
  4. package/claude/skills/ci-workflow/REQUIREMENT.md +8 -2
  5. package/claude/skills/ci-workflow/SKILL.md +3 -0
  6. package/claude/skills/code-craft/REQUIREMENT.md +1 -1
  7. package/claude/skills/code-craft/SKILL.md +7 -0
  8. package/claude/skills/code-craft/references/adopted.md +8 -0
  9. package/claude/skills/pr-evidence/SKILL.md +6 -1
  10. package/claude/skills/pr-evidence/references/evidence.md +1 -0
  11. package/claude/skills/pr-evidence/references/reel.md +9 -1
  12. package/claude/skills/review-craft/references/ui.md +13 -1
  13. package/claude/skills/review-pr/references/body-variants.md +2 -0
  14. package/claude/skills/review-pr/references/close-out.md +2 -0
  15. package/claude/skills/review-pr/references/post.md +1 -0
  16. package/claude/skills/review-ui/REQUIREMENT.md +1 -0
  17. package/claude/skills/review-ui/SKILL.md +2 -2
  18. package/claude/skills/review-ui/references/driving.md +32 -4
  19. package/claude/skills/review-ui/references/post.md +6 -2
  20. package/claude/skills/role-orchestrator/SKILL.md +2 -2
  21. package/claude/skills/role-orchestrator/references/orchestrator-output.md +24 -1
  22. package/claude/skills/role-orchestrator/references/orchestrator-poll.md +3 -2
  23. package/claude/skills/role-orchestrator/references/orchestrator-review-dispatch.md +35 -4
  24. package/claude/skills/role-orchestrator/references/orchestrator-reviewer-launch.md +1 -1
  25. package/claude/skills/role-worker/SKILL.md +1 -0
  26. package/claude/skills/test-craft/SKILL.md +1 -0
  27. package/claude/skills/ui-checklist/REQUIREMENT.md +1 -1
  28. package/claude/skills/ui-checklist/SKILL.md +2 -1
  29. package/claude/skills/ui-craft/references/capture.md +16 -0
  30. package/claude/skills/ui-film/REQUIREMENT.md +2 -2
  31. package/claude/skills/ui-film/SKILL.md +6 -1
  32. package/claude/skills/ui-film/references/compose-craft.md +68 -11
  33. package/claude/skills/ui-film/references/config.md +2 -2
  34. package/claude/skills/ui-film/references/handoff.md +1 -1
  35. package/claude/skills/ui-film/references/harness.md +71 -0
  36. package/claude/skills/ui-film/references/take-craft.md +42 -17
  37. package/docs/agents/targets.md +3 -1
  38. package/docs/workflow/operating-model.md +6 -5
  39. package/docs/workflow/visual-design-workflow.md +2 -1
  40. package/governance/ledger/skills/code-craft/SKILL.md +9 -0
  41. package/governance/ledger/skills/test-craft/SKILL.md +1 -0
  42. package/governance/ledger/skills/ui-craft/capture.md +18 -5
  43. package/package.json +5 -4
  44. package/src/commands/pr.ts +117 -31
  45. package/src/commands/take.ts +12 -1
  46. package/src/commands/targets.ts +11 -1
  47. package/src/design/components.ts +5 -107
  48. package/src/design/favicon.ts +21 -17
  49. package/src/design/render.ts +9 -27
  50. package/src/design/template.ts +42 -0
  51. package/src/design/templates/favicon.svg +17 -0
  52. package/src/design/templates/hand-drawn-figure.css +54 -0
  53. package/src/design/templates/preview.html +27 -0
  54. package/src/design/templates/scrollbar.css +29 -0
  55. package/src/design/templates/status.css +24 -0
  56. package/src/gate/embedded.ts +400 -0
  57. package/src/gate/stages.ts +8 -0
  58. package/src/pr/evidence.ts +43 -4
  59. package/src/pr/frames.ts +1 -6
  60. package/src/pr/lift.ts +77 -9
  61. package/src/pr/rest.ts +2 -0
  62. package/src/pr/review-scope.ts +28 -0
  63. package/src/pr/review-state.ts +5 -5
  64. package/src/pr/waiver.ts +45 -0
  65. package/src/take/camera.ts +337 -208
  66. package/src/take/film.ts +7 -1
  67. package/src/take/log.ts +43 -2
  68. package/src/take/perform.ts +85 -13
  69. package/src/take/plan.ts +26 -5
  70. package/src/take/reel.ts +30 -5
  71. package/src/take/screencast.ts +110 -3
  72. package/src/take/stepper.ts +24 -4
  73. package/src/targets/registry.ts +99 -14
  74. package/src/targets/resolve.ts +3 -1
  75. package/tooling/astro/configs/card.config.mjs +4 -0
  76. package/tooling/astro/configs/scripts/check-card-exclusion.sh +7 -2
  77. package/tooling/astro/reference.md +2 -1
  78. package/tooling/web/configs/.github/workflows/readme-screenshot.yml +3 -1
  79. package/tooling/web/configs/.github/workflows/verify.yml +5 -2
  80. package/tooling/web/configs/scripts/install-playwright-deps.sh +52 -0
  81. package/tooling/web/reference.md +5 -0
  82. package/src/commands/fixtures/gh.sh +0 -64
  83. package/src/take/fixtures/busy.html +0 -107
  84. package/src/take/fixtures/fade.html +0 -49
  85. package/src/take/fixtures/fetch.html +0 -38
  86. package/src/take/fixtures/input.html +0 -138
  87. package/src/take/zoom/NOTICE.md +0 -45
  88. package/src/take/zoom/math-utils.ts +0 -69
  89. package/src/take/zoom/region-utils.ts +0 -55
  90. package/src/take/zoom/spring.ts +0 -133
@@ -9,8 +9,24 @@
9
9
 
10
10
  - Run the project's capture command against a preview server, never a dev server, after changing a surface. Report a project with none.
11
11
  - Capture every theme the surface ships, and again after fixing a defect found.
12
+ - Add a case for every rendered state the change adds or alters, on a new surface or an already captured one. Proof is the default.
13
+ - Count a focus ring, an outline, a toast, a tooltip, a size change from server-written markup, and a state reachable only by interaction as visible states.
12
14
  - Read your capture against the `canon:ui-checklist` boxes before opening the pull request, reporting a skill that does not resolve.
13
15
 
16
+ ### The exemption
17
+
18
+ Only a change with no visible result in any reachable state is exempt, such as a keyboard order that moves no ring. A capture costs one command and a missing one costs a review round, so the worker never judges the exemption alone.
19
+
20
+ - State it in the pull request body as a line naming the path and why nothing shows.
21
+ - A reviewer accepts it by writing a ``- **Waived capture:** `<path>`: <reason>`` line in a review.
22
+ - A painted path with neither a case nor that line keeps the pull request in draft.
23
+
24
+ ## When the fixture cannot reach a state
25
+
26
+ - Extend the drive fixture in the same branch when a box or a case needs a state it lacks, and commit the added state under a path the fixture declares, since the throwaway copy wipes anything else.
27
+ - Never record the missing state as a skip, since the next pull request meets the same fixture and skips again.
28
+ - State the setup inline when the project has no `canon/config/app-drive.toml`, since the drive there starts from the served root and a fixture file is not owed.
29
+
14
30
  ## What a capture covers
15
31
 
16
32
  - Capture the full page, never a component in isolation.
@@ -14,14 +14,14 @@ A session that does reach `canon take` writes the plan from its own reading of t
14
14
  ## Must
15
15
 
16
16
  - The operator approves the beats in plain words before anything is filmed
17
- - Each beat films one action from a fresh start state, with a hold before and after it
17
+ - Each beat films one action from a fresh start state, with a hold before and after it, and a harness beat has its known faults named before it films
18
18
  - Every take carries its clip, its click log, and its camera track, and no clip carries a drawn cursor
19
19
  - The session looks at a still of each take before handing it off, and names a beat that fell back to the real-time route
20
20
  - HyperFrames receives the takes with what it needs to apply the camera and draw the cursor
21
21
 
22
22
  One video keeps its plan, takes, and composition in one workspace the operator can find, backed up without its media. A README there records how to re-film the takes, and the composing session adds how to re-render. It keeps what a re-film and a re-render need and clears the rest.
23
23
 
24
- A session composing the takes finds the footage guidance HyperFrames lacks, being captions over app UI, other shapes, a README loop, and sped-up clips. A machine without HyperFrames keeps its takes and stops at the handoff naming the install, which the session never runs itself.
24
+ A session composing the takes finds the footage guidance HyperFrames lacks, being captions over app UI, other shapes, a README loop, sped-up clips, pacing, transitions, and the review of a cut's motion. A machine without HyperFrames keeps its takes and stops at the handoff naming the install, which the session never runs itself.
25
25
 
26
26
  ## Must not
27
27
 
@@ -59,6 +59,8 @@ Wait for the operator's approval or edits before Step 3. Read silence as no appr
59
59
 
60
60
  Read `${CLAUDE_SKILL_DIR}/references/take-craft.md` before writing the first beat. It holds the filming rules and the plan's step kinds.
61
61
 
62
+ Read `${CLAUDE_SKILL_DIR}/references/harness.md` beside it when a beat takes the harness route, since a harness fault films as a flaky take and a refilm on a guess repeats it.
63
+
62
64
  - Write one beat per approved line to `.canon/video/<slug>/plan.json`, named as the operator approved it
63
65
  - Find each target on the served page before writing it, through `canon browser` or the source, rather than guessing a selector
64
66
 
@@ -101,7 +103,10 @@ Check that the `hyperframes` skill resolves before handing anything to it, as `h
101
103
  ❌ HyperFrames is not installed, so nothing can compose .canon/video/<slug>/takes/. Run /plugin install canon-media@canon, or install hyperframes@hyperframes alone, then load hyperframes to compose.
102
104
  ```
103
105
 
104
- Read `${CLAUDE_SKILL_DIR}/references/handoff.md` on reaching this step, for what HyperFrames receives, where each composition lives, and how one applies the camera track and draws the cursor. Read `${CLAUDE_SKILL_DIR}/references/compose-craft.md` beside it, for the footage rules the HyperFrames skills lack.
106
+ Read `${CLAUDE_SKILL_DIR}/references/handoff.md` on reaching this step, for what HyperFrames receives, where each composition lives, and how one applies the camera track and draws the cursor.
107
+
108
+ - Read `${CLAUDE_SKILL_DIR}/references/compose-craft.md` beside it, for the footage rules the HyperFrames skills lack
109
+ - Hold each cut to its motion review before the operator sees it, since a still at a beat's midpoint passes a cut whose motion is wrong
105
110
 
106
111
  Close the throwaway copy per `isolate.md` when Step 1 built one, after the stills are read.
107
112
 
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Compose craft
3
- description: The footage rules a session composing ui-film takes follows where the HyperFrames skills say nothing, being captions over app UI, other shapes, the README loop, sped-up clips, storyboard headings, and the workspace README
3
+ description: The footage rules a session composing ui-film takes follows where the HyperFrames skills say nothing, being captions over app UI, other shapes, the README loop, pacing, sped-up clips, transitions, reviewing a cut, music, storyboard headings, and the workspace README
4
4
  ---
5
5
 
6
6
  # Compose craft
@@ -14,7 +14,11 @@ Each rule states the principle. Where a number appears, it is the case measured
14
14
  - [Captions over app footage](#captions-over-app-footage)
15
15
  - [Other shapes](#other-shapes)
16
16
  - [The README loop](#the-readme-loop)
17
+ - [Pacing](#pacing)
17
18
  - [Sped-up clips](#sped-up-clips)
19
+ - [Transitions](#transitions)
20
+ - [Reviewing a cut](#reviewing-a-cut)
21
+ - [Music](#music)
18
22
  - [Storyboard headings](#storyboard-headings)
19
23
  - [The workspace README](#the-workspace-readme)
20
24
  - [Sources](#sources)
@@ -46,14 +50,56 @@ Cut a README loop only when the operator asks for one. A README hero plays inlin
46
50
  - End it on the finished state, held long enough to read before the loop restarts
47
51
  - Export an animated WebP under about 2 MB, since a README renders one inline where a video needs a player. Frameboard's loop played inline at 1.72 MB.
48
52
 
53
+ ## Pacing
54
+
55
+ A cut paced at one rate throughout reads rushed where the viewer reads and slow where an agent works. Frameboard's launch film read rushed with hand actions at 1.25x to 1.42x, read slow at about 1x everywhere, and landed at about 78 s with the rates split by beat kind.
56
+
57
+ ### Rates by beat kind
58
+
59
+ Set each beat's playback rate by what the viewer does during it.
60
+
61
+ - Play an agent replay montage faster than a hand action, since the viewer follows its result rather than each step. Frameboard's ran up to about 2x.
62
+ - Play hand actions and anything read near real time, as Frameboard's did at about 1.0x to 1.2x
63
+ - Hold a typing beat to the rate `## Sped-up clips` gives, whatever rate the rest of its scene takes
64
+
65
+ ### Holds
66
+
67
+ Every result and every line of text holds still long enough to land before the cut.
68
+
69
+ - Hold each result on screen before the cut, such as a recolored button or an agent's reply. About 1 to 2 s fit Frameboard's film.
70
+ - Hold text by its length, with HyperFrames' `line_unread` warning as the floor. One short terminal line fit at about 1.5 s on Frameboard's film, where 0.75 s was missed and 2.9 s dragged.
71
+ - Pause after a mode switch, such as arming a tool, before the next action, or the switch does not register. About 1 s fit Frameboard's film.
72
+
73
+ ### Windows from marks
74
+
75
+ Read each scene's window off the take's `log.json`, from its first event less a lead to its last result plus that result's hold, then set the rate from the scene's length. Reusing an old cut's offsets against a new take lands the camera on moments that moved.
76
+
49
77
  ## Sped-up clips
50
78
 
51
79
  A clip the edit plays faster runs its zoom faster too, so a push that settled in real time pumps several times a beat.
52
80
 
53
81
  - Rebuild the beat's track for the playback rate with `canon take camera <dir>/log.json --rate <n> --out <file>`, and drive the wrapper from that file rather than from `camera.json`
54
- - Never scale `camera.json`'s times by hand, since the rebuilt track also keeps each push inside the 1 s ceiling at the new speed
82
+ - Never scale `camera.json`'s times by hand, since the rebuilt track also times each push to its glide on the new clock
55
83
  - Keep a typing beat at a rate where each word still reads, since a viewer cannot read a fill that finishes in a few frames
56
84
 
85
+ ## Transitions
86
+
87
+ HyperFrames owns transitions in general. App footage changes one thing, since only canon's camera zooms the footage under a dissolve.
88
+
89
+ - Settle a zoomed scene back to the whole page before a dissolve into another shot of the same app, so the dissolve blends two matching wide shots rather than ghosting one app at two scales
90
+ - Ease the camera wrapper out over the scene's last stretch rather than cutting its scale, as Frameboard's film did over about 0.8 s ahead of a 0.45 s dissolve
91
+
92
+ ## Reviewing a cut
93
+
94
+ Stills at beat midpoints pass a cut whose motion is wrong, as they passed every cut on Frameboard's film that the operator then rejected for its motion. Review motion before showing the cut, beside the review HyperFrames' `review.md` runs.
95
+
96
+ - Step a dense frame strip with `snapshot --range START,END --frames 8` around every take's action and every transition, rather than only the hero move `review.md` names
97
+ - Check the drawn cursor against the element it acts on in each strip, since the cursor is drawn from the log and nothing but a look shows it leaving what the page moved
98
+
99
+ ## Music
100
+
101
+ HyperFrames owns the soundtrack, so score the film by its `sound.md` and `film.md`. On Frameboard's launch film the music faults hid in the first 2 s and the last 10 s, being a silent lead and a drop that landed early, which is one film's case rather than a rule.
102
+
57
103
  ## Storyboard headings
58
104
 
59
105
  - Write each frame heading as `## Frame N: title`, since the dash form in HyperFrames' `storyboard-format.md` example fails the markdown audit
@@ -71,12 +117,23 @@ Step 5 writes the filming half of `.canon/video/<slug>/README.md`. The composing
71
117
 
72
118
  ## Sources
73
119
 
74
- | Rule | Source |
75
- | --------------------------- | -------------------------------------------------------------------------------------------------------- |
76
- | Caption placement and scrim | HyperFrames' `captions.md`, adjusted for footage with chrome along its bottom edge |
77
- | A new project per shape | The `hyperframes` skill's `types/vertical.md`, which recomposes a vertical film rather than squeezing it |
78
- | The 1080x1080 window | Frameboard's launch film, cut square from stepped takes |
79
- | The 2 MB WebP loop | Frameboard's README hero, an animated WebP cut from a raw take, which played inline at 1.72 MB |
80
- | Rebuilding a sped-up track | `canon take camera --help`, whose `--rate` divides every time in the log |
81
- | Storyboard heading form | HyperFrames' `storyboard-format.md` and the markdown audit's character bans, with the colon read assumed |
82
- | The README's two halves | Frameboard's workspace, whose README recorded how to re-film and re-render after a session ended |
120
+ The rules measured on Frameboard's launch film carry its case beside them rather than a default.
121
+
122
+ | Rule | Source |
123
+ | ------------------------- | --------------------------------------------------------------------------------------------------- |
124
+ | The 1080x1080 window | Frameboard's launch film, cut square from stepped takes |
125
+ | Rates, holds, and windows | Frameboard's launch film, cut at three paces before about 78 s landed |
126
+ | Settling wide | Frameboard's launch film, whose dissolves ghosted two boards at different scales until each settled |
127
+ | Where music faults hide | Frameboard's launch film, which opened silent and ended over near silence |
128
+
129
+ The rest come from HyperFrames, the verb, or Frameboard's other deliverables.
130
+
131
+ | Rule | Source |
132
+ | --------------------------- | --------------------------------------------------------------------------------------------------------- |
133
+ | Caption placement and scrim | HyperFrames' `captions.md`, adjusted for footage with chrome along its bottom edge |
134
+ | A new project per shape | The `hyperframes` skill's `types/vertical.md`, which recomposes a vertical film rather than squeezing it |
135
+ | The 2 MB WebP loop | Frameboard's README hero, an animated WebP cut from a raw take, which played inline at 1.72 MB |
136
+ | Reviewing motion | HyperFrames' `review.md`, widened to every action after midpoint stills passed cuts the operator rejected |
137
+ | Rebuilding a sped-up track | `canon take camera --help`, whose `--rate` divides every time in the log |
138
+ | Storyboard heading form | HyperFrames' `storyboard-format.md` and the markdown audit's character bans, with the colon read assumed |
139
+ | The README's two halves | Frameboard's workspace, whose README recorded how to re-film and re-render after a session ended |
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: UI film config
3
- description: What a take reads from canon/config/app-drive.toml, the access key it adds, and what to ask when the file is absent
3
+ description: What a take reads from canon/config/app-drive.toml, the access key it shares with review-ui, and what to ask when the file is absent
4
4
  ---
5
5
 
6
6
  # UI film config
@@ -21,7 +21,7 @@ access = "/write?token=demo"
21
21
  | `data` | A fixture path inside the worktree to load before serving, if any |
22
22
  | `access` | The path or address that opens the app in the state a take starts from |
23
23
 
24
- `access` is optional and belongs to the take alone. A drive ignores it.
24
+ `access` is optional. A take joins it, and so does a `review-ui` pass, which opens its chosen address behind it. `app-drive` ignores it.
25
25
 
26
26
  - Join a path value, one starting with `/`, to the served address. Take a full address as it stands.
27
27
  - Use it for whatever the app needs before its start state shows, such as a write link, a signed-in route, or a demo token
@@ -29,7 +29,7 @@ Each frame carries `scale`, the view's top-left `x` and `y`, the `cursor`, and `
29
29
  - `cursor` is the pointer's position on the page
30
30
  - `ring` runs from 0 to 1 across the 450 ms after a click and is `null` otherwise. On a drag it grows to 0.3, holds there from press to release, and runs on to 1 from the release.
31
31
 
32
- The track is final. It already carries the easing, the spring, and the hold, so a composition sets each frame's values and never tweens between them.
32
+ The track is final. It already carries the easing and the hold, so a composition sets each frame's values and never tweens between them.
33
33
 
34
34
  ## Where the composition lives
35
35
 
@@ -0,0 +1,71 @@
1
+ ---
2
+ title: Harness
3
+ description: The route a beat takes when canon take cannot drive it, and the harness faults that cost a launch film most of its refilms, each with its source
4
+ ---
5
+
6
+ # Harness
7
+
8
+ Read at Step 3 of `ui-film` when a beat takes the harness route. A harness fault looks like a flaky take, so a session that has not read these refilms on a guess instead of fixing the cause.
9
+
10
+ Each rule states the principle. Where a number appears, it is the case measured on one app's launch film, not a default for the next app.
11
+
12
+ ## Contents
13
+
14
+ - [The route](#the-route)
15
+ - [The script](#the-script)
16
+ - [The server](#the-server)
17
+ - [The steps](#the-steps)
18
+ - [The log](#the-log)
19
+ - [Sources](#sources)
20
+
21
+ ## The route
22
+
23
+ A beat driven from outside the page, such as a drag between two windows or an input the step kinds lack, cannot run as `canon take` steps. Until the verb can run an outside script itself, film it through a harness.
24
+
25
+ - Keep each script in the workspace's `harness/` folder and write its output into the take's own folder, never at the workspace root
26
+ - Drive the beat from a Playwright script that records the page and writes a log in the shape of a take's `log.json`
27
+ - Build its camera track from that log with `canon take camera <log> --out <file>`, so the composition applies it like any other take
28
+ - Tell the operator which beats ran this way, since their clips carry the harness's resolution rather than the stepper's
29
+
30
+ ## The script
31
+
32
+ A harness runs the page and whatever acts on it in one script, so anything that blocks the script freezes the take wherever it lands.
33
+
34
+ - Run a scripted agent's calls, such as a CLI it shells out to, asynchronously, since a synchronous call blocks the process driving Playwright and freezes the take mid-typing or between two clicks
35
+ - Type one character at a time on a fixed clock rather than through `locator.pressSequentially`, which stalled 3.6 s mid-note on Frameboard's launch film while a fixed clock held at 7 ms or less a key
36
+ - Hold a clip's last frame with FFmpeg's `tpad` when a long take ends before its scene needs it, since a held frame is invisible only while nothing on screen moves
37
+
38
+ ## The server
39
+
40
+ A harness beat often starts its own server or restores the app's state, and both race the beat when the order is wrong.
41
+
42
+ - Restore the app's state before a fresh serve, never while the server runs, since a running app logs the restore as a change and can show it on camera, as a stray change notice did on Frameboard's film
43
+ - Wait until the fresh server answers before the beat's setup writes anything, since a server that misses the setup write never shows the control under test
44
+ - Keep a local helper the harness calls, such as one that mints edit links, up across server restarts, answering 503 while no server runs rather than crashing on the next request
45
+
46
+ ## The steps
47
+
48
+ A recorded harness step goes stale when the app changes under it, and nothing refuses it the way `canon take` refuses `target-missing`.
49
+
50
+ - Target each control by its name, such as its role and label, never by its position, since a position finds whatever sits there after a change
51
+ - Re-check each recorded step against the current build before filming, since a token list that began closing on pick made Frameboard's old closing click reopen it
52
+
53
+ ## The log
54
+
55
+ The edit draws the cursor and aims the camera from the log alone, so a harness log carries the fields the stepper writes for the same events.
56
+
57
+ - Write `ms` on each `type` event as the measured length of the burst, since a camera guessing at 45 ms a character released its zoom halfway through a 5.7 s note on Frameboard's film
58
+ - Write `points` on each `move` event, being every position sent with its clip time, so the drawn cursor matches what the page received rather than an eased curve the page never saw
59
+ - Pace a drag in small steps, wait on each, and log each step as its own linear move, since easing each step stutters. About 30 steps a second kept Frameboard's divider within 2 px of the cursor.
60
+
61
+ ## Sources
62
+
63
+ | Rule | Source |
64
+ | ------------------------------ | ------------------------------------------------------------------------------------------------------ |
65
+ | The route | `canon take --help` and `canon take camera --help`, whose log shape a harness writes |
66
+ | Agent calls and typing clock | Frameboard's launch film, whose harness froze on a synchronous call and stalled on `pressSequentially` |
67
+ | Restore, readiness, and helper | Frameboard's launch film, refilmed for a stray change notice, a missing control, and a crashed helper |
68
+ | Targets and re-checked steps | Frameboard's launch film, whose recorded steps broke when a token list changed between versions |
69
+ | `ms` and `points` on the log | The `type` and `move` events `canon take` writes, which the camera and the drawn cursor read |
70
+ | Paced drags | Frameboard's split compare, whose divider lagged a burst of moves until each step was paced and logged |
71
+ | Holding a last frame | FFmpeg's [`tpad` filter](https://ffmpeg.org/ffmpeg-filters.html#tpad), used on Frameboard's film |
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  title: Take craft
3
- description: The filming rules a take plan follows, the frame-stepping limits, and the pointer and zoom rules the camera relies on, each with its source
3
+ description: The filming rules a take plan follows, the frame-stepping limits, the pointer and zoom rules the camera relies on, and how a beat is framed, each with its source
4
4
  ---
5
5
 
6
6
  # Take craft
@@ -15,6 +15,7 @@ Read at Step 3 of `ui-film`, before writing the first beat. `canon take --help`
15
15
  - [Targets](#targets)
16
16
  - [The pointer](#the-pointer)
17
17
  - [The zoom](#the-zoom)
18
+ - [Framing](#framing)
18
19
  - [One source for on-camera content](#one-source-for-on-camera-content)
19
20
  - [The harness route](#the-harness-route)
20
21
  - [Sources](#sources)
@@ -41,7 +42,7 @@ A plan holds the approved beats in order. Each beat names its setup, which runs
41
42
  ## One action per take
42
43
 
43
44
  - Film one action per beat, such as one click, one fill, or one scroll, so the edit can cut, reorder, or drop a beat without re-filming its neighbors
44
- - Keep a take as long as its beat. The verb opens and closes each one on a 0.6 s hold, which gives the edit a still to cut on, so add no hold of your own around the action.
45
+ - Keep a take as long as its beat. The verb holds still at each end and paces each press and its result, at the timings `canon take --help` prints, so add no hold of your own around the action.
45
46
  - Add a `wait` after an action whose result animates, loads, or settles, long enough for the result to finish on screen
46
47
  - Reach the beat's start state through `setup`, since every beat opens the page fresh and nothing a setup step does is filmed
47
48
  - Write the beat's `name` as the operator approved it, since it names the clip's folder
@@ -92,10 +93,11 @@ Two rules hold once a beat is filmed.
92
93
 
93
94
  The camera track already carries the zoom, so these rules decide what to film for it to aim at.
94
95
 
95
- - Earn a zoom with an action. The camera aims only at clicks and typing, and a wait, a move, or a scroll aims nowhere, so a result too small to read needs a click on it.
96
+ - Earn a zoom with an action. The camera aims at a press, typing, and a `move` carrying `hold`, so a result too small to read needs one of those on it.
97
+ - Expect a wait, a plain move, or a scroll to aim nowhere
96
98
  - Expect a scroll to end a zoom, since the content moves under the camera
97
99
  - Expect a target filling most of the view to get less zoom, and one filling the page to get none
98
- - The push reaches 90% of its depth in about 633 ms, inside the 1 s ceiling a push onto an action keeps
100
+ - The push rides the glide onto its target and lands as the cursor arrives, so it lasts as long as that glide, between 300 ms and 1.2 s
99
101
 
100
102
  ### Depth, opt-out, and speed
101
103
 
@@ -105,6 +107,32 @@ Three controls change the push itself.
105
107
  - Set `"push": false` on a beat the edit holds wide, such as one showing a whole layout change. Its track keeps the cursor and the click ring and drops the zoom.
106
108
  - Rebuild a beat's track with `canon take camera <dir>/log.json --rate <n> --out <file>` for a clip the edit plays `n` times faster, so the zoom settles on the clock a viewer sees
107
109
 
110
+ ### The cursor leads the camera
111
+
112
+ The viewer follows the cursor, so the camera zooms where the cursor acts and never anywhere the cursor is not.
113
+
114
+ - Film a look-back at a changed element as a `move` to it carrying `hold`, so the cursor glides there and the camera holds on its arrival
115
+ - Never aim the camera at a point the cursor left behind, since a push onto a button while the drawn cursor sits elsewhere reads as broken
116
+ - End a hold when the cursor leaves. A glide onto the next target plus about 1 s of hold fit Frameboard's launch film, where a 3.2 s camera-only hold read as too long.
117
+
118
+ ### One track per take
119
+
120
+ A camera track is read off one take's log and is true of that take alone.
121
+
122
+ - Rebuild every camera track from the new log after a re-film, each `--rate` track included, since a track left from the old take aims at moments that no longer happen
123
+ - Never hand-tune a track, since the next re-film overwrites the log under it and leaves the tuning aimed at the old clock
124
+
125
+ ## Framing
126
+
127
+ A beat's view is part of what it shows, so set it before filming rather than letting the app move it.
128
+
129
+ - Film a beat that builds several elements into one fixed view fitted to its end state, so each element lands where it finishes and the next beat opens on the same view
130
+ - Reach that view by opening the page on the finished state, letting the app fit to it, then emptying it in `setup`, rather than following the app's own view as each element lands
131
+ - Film the selection a hand edit acts on, since `setup` is never filmed and a viewer who missed the selection cannot tell what changed
132
+ - End a hand edit on the edited element rather than a wide view, where neighbors that already looked the same suggest several things changed
133
+
134
+ Frameboard's launch film followed the board's own view in its first cut, which centered each frame as it landed and left a tall frame cut off below a short one.
135
+
108
136
  ## One source for on-camera content
109
137
 
110
138
  A take shows content the app loads or a script writes, such as a fixture, a snapshot, or copy typed into a field. Keep one source for it, naming whatever the take reads on camera.
@@ -115,21 +143,18 @@ A take shows content the app loads or a script writes, such as a fixture, a snap
115
143
 
116
144
  ## The harness route
117
145
 
118
- A beat driven from outside the page, such as a drag between two windows or an input the step kinds lack, cannot run as `canon take` steps. Until the verb can run an outside script itself, film it through a harness.
119
-
120
- - Keep each script in the workspace's `harness/` folder and write its output into the take's own folder, never at the workspace root
121
- - Drive the beat from a Playwright script that records the page and writes a log in the shape of a take's `log.json`
122
- - Build its camera track from that log with `canon take camera <log> --out <file>`, so the composition applies it like any other take
123
- - Tell the operator which beats ran this way, since their clips carry the harness's resolution rather than the stepper's
146
+ A beat driven from outside the page, such as a drag between two windows or an input the step kinds lack, runs through a Playwright harness rather than as `canon take` steps. Read `harness.md` beside this file before writing one, since it holds the route and the faults that cost a reshoot most of its refilms.
124
147
 
125
148
  ## Sources
126
149
 
127
- | Rule | Source |
128
- | -------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------- |
129
- | Zoom on a click | Screen Studio's [auto zoom guide](https://screen.studio/guide/auto-zoom) keys its zoom on click positions |
130
- | The 1 s ceiling | Material Design 3's [duration tokens](https://m3.material.io/styles/motion/easing-and-duration/tokens-specs) end at 1000 ms, the top of the ambient band |
131
- | The 633 ms push | Twelve moves in a Screen Studio clip reached 90% in a median of 667 ms, measured when the camera was tuned |
132
- | The typing and scroll risk | Takes of a canvas design board filmed while the stepper was built |
133
- | Jumping scrolls and GIFs | [hasuwini77/demo-reel#28](https://github.com/hasuwini77/demo-reel/pull/28), read through a search summary rather than the page |
150
+ | Rule | Source |
151
+ | ----------------------------- | ------------------------------------------------------------------------------------------------------------------------------ |
152
+ | Zoom on a click | Screen Studio's [auto zoom guide](https://screen.studio/guide/auto-zoom) keys its zoom on click positions |
153
+ | The push riding the glide | The operator judged renders of a fixture page by eye and kept a zoom landing with the cursor over one leading it |
154
+ | The typing and scroll risk | Takes of a canvas design board filmed while the stepper was built |
155
+ | The cursor leading the camera | Frameboard's launch film, whose operator flagged every push the drawn cursor did not lead |
156
+ | One track per take | Frameboard's launch film, where tracks left from an earlier take drove two beats off sync |
157
+ | Framing | Frameboard's launch film, refilmed into a fixed view fitted to its finished frames |
158
+ | Jumping scrolls and GIFs | [hasuwini77/demo-reel#28](https://github.com/hasuwini77/demo-reel/pull/28), read through a search summary rather than the page |
134
159
 
135
160
  The pointer rules carry no number. One pointer and a click inside its box follow from the log recording each target's box.
@@ -9,7 +9,9 @@ kind: reference
9
9
  `canon targets list` reports the projects this toolkit installed into. `canon targets pulls [paths…]` reports each target's open pull requests, or every target's when none is named. Both take `--json`. Read the record's fields, since a wrapping shell function can flatten the exit.
10
10
 
11
11
  - `list` takes `--sweep <path…>`, `--depth <n>` (default 4), and `--record`, which seeds the index from swept stamps and needs `--sweep`
12
- - Each stamping sync records its target in `$XDG_STATE_HOME/canon/targets.json`, and no row is removed
12
+ - Each stamping sync records its target in `$XDG_STATE_HOME/canon/targets.json`, except a folder under the OS temp folder, the state folder, `~/.claude/jobs/`, or any `.canon/tmp/`
13
+ - Every write drops a row whose folder is gone or sits under one of those, and `list` leaves the same rows out. A target on an unmounted drive drops until its next sync records it again.
14
+ - `--record` counts a swept checkout under one of those folders as skipped, not failed
13
15
  - A sweep finds installs the index never held
14
16
 
15
17
  `canon changelog` prints the releases a target has not seen, from the `CHANGELOG.md` the installed package ships. It reads no registry and needs no `gh`.
@@ -30,11 +30,12 @@ Each role keeps to its own writes, and five rules cover how the roles meet.
30
30
  1. Plan the next feature with `plan-feature` into `.canon/plans/`. Run it warm when the row turns on a contract other rows consume, and dispatch a planner otherwise.
31
31
  2. Check the plan waits on nobody, the branch is unclaimed, and no track in flight conflicts. Dispatch a background worker with `claude --bg`, naming the branch and the model.
32
32
  3. The worker enters its own worktree, builds, self-checks, opens a PR, and stops there.
33
- 4. Dispatch a reviewer under `role-reviewer` for every PR but a release one, or one pair per batch of small contract-free PRs. It posts `review-pr` findings per PR, and `review-ui` drives any checklist.
34
- 5. The reviewer tells the session holding the branch to run `review-address`, found with `canon sessions list --branch`, once a pass posts a finding at any severity.
35
- 6. The worker addresses the findings, rebases onto `origin/main` if a sibling landed on its files, pushes, and asks that reviewer for a re-review of the added commits.
36
- 7. A pass posts `## Review` while anything is open and `## Review closed` when nothing is. The reviewer then runs `canon pr lift`.
37
- 8. The human merges with `canon pr merge <number>`, a batch as each pass closes. The orchestrator tells any trailing worker whose files the merge touched to run `review-address`, which rebases.
33
+ 4. The orchestrator reads `canon pr evidence --check` and sends a painted PR with no capture or evidence comment back to its worker.
34
+ 5. Dispatch a reviewer under `role-reviewer` for every PR but a release one, or one pair per batch of small contract-free PRs. It posts `review-pr` findings, and `review-ui` drives any non-taste box.
35
+ 6. The reviewer tells the session holding the branch to run `review-address`, found with `canon sessions list --branch`, once a pass posts a finding at any severity.
36
+ 7. The worker addresses the findings, rebases onto `origin/main` if a sibling landed on its files, pushes, and asks that reviewer for a re-review of the added commits.
37
+ 8. A pass posts `## Review` while anything is open and `## Review closed` when nothing is. The reviewer then runs `canon pr lift`.
38
+ 9. The human merges with `canon pr merge <number>`, a batch as each pass closes. The orchestrator tells any trailing worker whose files the merge touched to run `review-address`, which rebases.
38
39
 
39
40
  Each worker halts at the PR, and the merge stays a human gate. `canon pr merge` squashes with the body's Summary, Key changes, and Technical context as the message, and the seeded deny rule covers the forms an agent writes by default. A release-please or Dependabot pull request merges the same way, from the bot's own body.
40
41
 
@@ -45,6 +45,7 @@ Two reviews run beside each other and finish on their own clocks.
45
45
  - `canon:review-pr` reads the code and the stills in the evidence comment, and posts under `## Review`.
46
46
  - `canon:review-ui` never reads the diff. It drives each checklist box in the running app through `canon browser`, which runs the pinned `@playwright/cli`, and posts under `## UI review`.
47
47
  - Every box gets one verdict, being pass, fail, needs eyes, or not driven.
48
+ - A page that shows its start state only behind a token or a signed-in route names that path as `access` in `canon/config/app-drive.toml`, and the pass opens its address behind it.
48
49
  - The pass refuses rather than download a browser. Install it once per machine with `canon browser install-browser chromium`.
49
50
 
50
51
  After the boxes, `review-ui` sweeps every route they reached:
@@ -63,7 +64,7 @@ A UI review stays open while the branch owes a fix. Three things owe one:
63
64
 
64
65
  Two kinds of result report without holding the review open.
65
66
 
66
- - A taste box needs eyes, so the pass captures both themes and leaves the call to you.
67
+ - A taste box needs eyes, so the pass captures both themes and leaves the call to you. A checklist holding only taste boxes gets no UI reviewer at all, and the lift does not wait for one.
67
68
  - Every other probe result, and every axe `incomplete`, is a reading to confirm, since each probe carries a known class of false finding.
68
69
 
69
70
  At the time of writing, the gated axe classes are contrast, target size, accessible names, labels, alt text, document title and language, valid ARIA, and nested interactive controls. A rule leaves the list on its first false finding, so `canon drive --help` and the `review-ui` skill carry the current one.
@@ -42,6 +42,15 @@ Parnas's paper refused a read at its ACM DOI, `10.1145/361598.361623`, so the ro
42
42
  | Rules free of transport and storage | [Cockburn, Hexagonal Architecture](https://alistair.cockburn.us/hexagonal-architecture/): an application "developed and tested in isolation from its eventual run-time devices and databases" | 2026-10-24 |
43
43
  | No ports and adapters for record CRUD | [Microsoft, Design a DDD-oriented microservice](https://learn.microsoft.com/en-us/dotnet/architecture/microservices/microservice-ddd-cqrs-patterns/ddd-oriented-microservice): "Simpler responsibilities, like a CRUD service, can be managed with simpler approaches." | 2026-10-24 |
44
44
 
45
+ ## Give another language its own file
46
+
47
+ | Bullet | Source | Read |
48
+ | --------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
49
+ | A long page, stylesheet, or script out | [Meszaros, Inline Resource](http://xunitpatterns.com/Inline%20Resource.html): "If the contents of the resource are large, chances are high that you are also suffering from Eager Test" | 2026-11-03 |
50
+ | A branching script out at any length | House choice: the `ci-workflow` skill already moves a `run:` step past one guard to a script, and a fake that branches is shared by many tests, so no test reads it as its own input | 2026-11-03 |
51
+ | An interpolating template stays in code | House choice: a template built from data is code generating output, and a file holding it would carry placeholders no linter for its language reads either | 2026-11-03 |
52
+ | Short input under test stays inline | [Meszaros, Obscure Test](http://xunitpatterns.com/Obscure%20Test.html): with a Mystery Guest "the test reader is not able to see the cause and effect between fixture and verification logic" | 2026-11-03 |
53
+
45
54
  ## Check your own diff
46
55
 
47
56
  | Bullet | Source | Read |
@@ -44,6 +44,7 @@ The field is software testing practice. Software Engineering at Google carries t
44
44
  | Test data through a factory or builder | [Pryce, Test Data Builders](http://www.natpryce.com/articles/000714.html): "For each class you want to use in a test, create a Builder for that class", one that "Initialises its instance variables to commonly used or safe values" | 2026-10-24 |
45
45
  | Assert through the public surface | [Software Engineering at Google, ch. 12](https://abseil.io/resources/swe-book/html/ch12.html): tests should "make calls against its public API rather than its implementation details" | 2026-10-24 |
46
46
  | Real, then fake, then stub, mock last | [addyosmani/agent-skills, Test-Driven Development](https://github.com/addyosmani/agent-skills/blob/c004a74/skills/test-driven-development/SKILL.md): "Preference order (most to least preferred): 1. Real implementation", then fake, stub, and mock | 2026-10-24 |
47
+ | A long fixture in its own file | [Meszaros, Inline Resource](http://xunitpatterns.com/Inline%20Resource.html): "If the contents of the resource are large, chances are high that you are also suffering from Eager Test" | 2026-11-03 |
47
48
 
48
49
  ## Hold the structure floors
49
50
 
@@ -16,11 +16,24 @@ The rule states canon's own evidence procedure for a rendered change, so most ro
16
16
 
17
17
  ## When to capture
18
18
 
19
- | Bullet | Source | Read |
20
- | ---------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
21
- | Preview, not dev server | House choice: a dev server injects overlays and skips the production build's transforms, so its frame differs from what ships | 2026-10-18 |
22
- | Every theme, and again | House choice: a defect in the dark theme passes a light-only capture, and a repair inside a shared stylesheet can cancel a rule written earlier in the same file | 2026-10-18 |
23
- | Read the capture before the pull request | House choice: a defect visible in the worker's own capture, such as a 20px target under the 24px bullet in `pointer.md`, reached review because nobody read the frame against the checklist first, and each review round re-runs both reviews. The skill ships with the plugin, so the bullet carries a report-if-missing clause | 2026-10-23 |
19
+ | Bullet | Source | Read |
20
+ | ----------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
21
+ | Preview, not dev server | House choice: a dev server injects overlays and skips the production build's transforms, so its frame differs from what ships | 2026-10-18 |
22
+ | Every theme, and again | House choice: a defect in the dark theme passes a light-only capture, and a repair inside a shared stylesheet can cancel a rule written earlier in the same file | 2026-10-18 |
23
+ | Read the capture before the pull request | House choice: a defect visible in the worker's own capture, such as a 20px target under the 24px bullet in `pointer.md`, reached review because nobody read the frame against the checklist first, and each review round re-runs both reviews. The skill ships with the plugin, so the bullet carries a report-if-missing clause | 2026-10-23 |
24
+ | A case per added or altered state | House choice: workers read the old rule as covering new surfaces only, so new states of captured surfaces shipped with no case and the next pull request met the same thin fixture. Proof is owed by default. | 2026-11-03 |
25
+ | States that count as visible | House choice: a focus ring, an outline, a toast, a tooltip, a size change from server-written markup, and a state reachable only by interaction each show on a page, and workers filed them as behavior with nothing to capture | 2026-11-03 |
26
+ | Exemption stated in the pull request body | House choice: the reviewer needs the path and the reason in front of them to accept it, and a line in the body is the one place both reviewers read | 2026-11-03 |
27
+ | Reviewer accepts through the waiver line | House choice: the lift reads waivers from the newest review pass alone, so only that line holds the draft mark open to a painted path with no case | 2026-11-03 |
28
+ | Uncovered painted path stays in draft | House choice: a painted path with neither a case nor a waiver is the gap the lift refuses, so the rule names the outcome a worker meets | 2026-11-03 |
29
+
30
+ ## When the fixture cannot reach a state
31
+
32
+ | Bullet | Source | Read |
33
+ | ------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------- |
34
+ | Extend the fixture in the same branch | House choice: a drive fixture lacking a state made a box or reel skip and close, and only the building session can add the state before the pull request opens | 2026-11-03 |
35
+ | Never record the state as a skip | House choice: a skip leaves the fixture as thin as it was, so the next pull request meets the same gap and skips again | 2026-11-03 |
36
+ | State the setup inline with no fixture file | House choice: a project with no `canon/config/app-drive.toml` drives the served root, so a fixture file is not owed and the setup goes on the box, as `(local only)` already does | 2026-11-03 |
24
37
 
25
38
  ## What a capture covers
26
39
 
package/package.json CHANGED
@@ -1,11 +1,11 @@
1
1
  {
2
2
  "name": "@erclx/canon",
3
3
  "type": "module",
4
- "version": "5.76.0",
4
+ "version": "5.78.0",
5
5
  "description": "Runs your agent's work and keeps its code good, on one setup for every repo.",
6
6
  "license": "MIT",
7
7
  "bin": {
8
- "canon": "./src/cli.ts"
8
+ "canon": "src/cli.ts"
9
9
  },
10
10
  "files": [
11
11
  "src",
@@ -18,6 +18,7 @@
18
18
  "tsconfig.json",
19
19
  "assets/brand/mark.svg",
20
20
  "!scripts/eval",
21
+ "!src/**/fixtures/**",
21
22
  "!**/*.test.ts",
22
23
  "!**/*.test.tsx"
23
24
  ],
@@ -74,8 +75,8 @@
74
75
  "astro": "^6.1.9",
75
76
  "husky": "^9.1.7",
76
77
  "micromatch": "^4.0.8",
77
- "oxfmt": "0.74.0",
78
- "oxlint": "1.89.0",
78
+ "oxfmt": "0.75.0",
79
+ "oxlint": "1.90.0",
79
80
  "prettier": "^3.8.1",
80
81
  "prettier-plugin-astro": "^0.14.1",
81
82
  "prettier-plugin-tailwindcss": "^0.8.1",