@erclx/canon 4.88.0 → 4.89.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.
@@ -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.88.0",
4
+ "version": "4.89.0",
5
5
  "author": {
6
6
  "name": "Eric Le",
7
7
  "url": "https://github.com/erclx"
@@ -20,12 +20,21 @@ Without this skill, a session facing a decision nobody can settle from a diff:
20
20
  - Reads a color without compositing its alpha, samples a round control at the corner of its bounding box, or confirms a panel's dimensions while it sits above the viewport. Each reads as a passing measurement about something no reader sees.
21
21
  - Meets a machine with no browser binary and reports on the arms anyway, since nothing in the default flow separates a render that failed from one that was never attempted.
22
22
  - Hand-writes a mockup of a surface a running app already serves, which drifts from what ships by a few pixels and a few words the moment the app's own markup or stylesheet moves on.
23
+ - Varies a property the decision is not about, which satisfies the one-property rule and produces a set differing in the layer a reader notices least. Five palettes over one identical page read as five skins of one design, and the operator rejected the whole set on sight.
24
+ - Renders each arm in whatever theme it happens to carry, so a set spanning both themes cannot be compared without the operator toggling every arm and holding the earlier ones in memory.
25
+ - Looks at a render to confirm it came out rather than to judge whether it is worth showing, which satisfies the rule against reporting an unseen result and still hands over weak work.
26
+ - Writes the run's renders to a scratch path outside the record that cites them, leaving the folder holding source markup and none of the images, so the judgment is unreachable to everyone except the session that made it.
27
+ - Narrows the page onto the picked arm in place on each iteration, so every earlier round is overwritten and a later pass cannot see what was already rejected.
23
28
 
24
29
  ## Must
25
30
 
26
31
  - Produce three to five arms with the shipping state among them, each carrying an id, a label, and what the arm costs.
27
32
  - Author the whole candidate set as one self-contained page and render it once, so the comparison arrives as one image, except where the surface under decision is a running app: there the page links a copy of the built stylesheet rather than inlining it, per `references/live-arms.md`.
28
- - Vary one property across the arms, so the answer names a difference.
33
+ - Name the layer the decision lives at before drafting, and vary the arms at that layer alone, so the answer names the difference that decided it rather than the cheapest one to change.
34
+ - Render every arm in one theme at a time, with a control that sets the whole set, so the comparison holds still while the operator reads it.
35
+ - Judge each render against a stated bar before handing it over, name the weakest thing on the page, and fix it where that sentence would embarrass the work.
36
+ - Write each iteration to its own folder rather than narrowing the previous one in place, so every earlier round stays openable.
37
+ - Write the run's renders inside the record that cites them wherever one exists, and reserve the scratch path for inputs that are re-runnable and cited by nothing.
29
38
  - Render before reporting on any arm, and report a missing browser binary as a refusal naming the repair rather than describing an arm nobody has seen.
30
39
  - Take the pick through the structured question surface, with the recommendation ranked first and marked, and every option carrying its cost.
31
40
  - Keep every write inside the run's scratch folder until the pick is final.
@@ -15,7 +15,7 @@ Some decisions are settled by looking rather than by reasoning, and no draft is
15
15
 
16
16
  ## Step 1: name the decision and the arms
17
17
 
18
- 1. State the decision in one sentence, naming what changes between arms and what stays fixed.
18
+ 1. State the decision in one sentence, naming what changes between arms and what stays fixed. Name the layer that sentence puts the decision at, and check that the arms will differ there rather than somewhere cheaper to change.
19
19
  2. Derive a kebab slug from that sentence. Call the folder every file this run writes to `<dest>` below. `<dest>` is `.canon/tmp/<slug>/`, a nested `<slug>/` folder rather than a flat `<slug>-<file>.md`, which is the shape every temporary write in this project takes. Running inside a live `plan-groundwork` track is the one exception: `<dest>` is the track's own `evidence/<slug>/` instead, since a candidate render is evidence the track's decision file cites rather than spike input.
20
20
  3. Write one arm per candidate, each carrying an id, a label, and what the arm costs. An arm with no stated cost is not an option.
21
21
  4. Make the current state arm `0`, so the baseline is a candidate rather than an absence. A decision with nothing shipped yet says so and starts at arm `1`.
@@ -28,10 +28,12 @@ Write every arm side by side on one self-contained HTML page at `<dest>/candidat
28
28
  - One page for the pick, never a set of separate images handed to the operator to compare from memory. The comparison they judge is `candidates.html`, which Step 3 renders and Step 4 asks about. The per-arm files exist only for Step 6's archival capture, once the pick is made, and only the last pass through this step is what Step 6 finds there.
29
29
  - Wrap each arm's markup in the same class on both files, chosen once per run and reused everywhere, so one selector addresses an arm on the combined page and on its own standalone file alike.
30
30
  - Label each arm on the page with its id and its cost, so the render carries what the question will ask about.
31
+ - Give the combined page one control that sets every arm's theme at once, beside whatever per-arm control the arms carry. A set spanning both themes cannot be compared, since the operator has to toggle each arm and hold the earlier ones in memory, which is the failure the single-page rule exists to prevent.
31
32
  - Take the live-app branch instead when the surface under decision is a running app: lift the rendered markup and link a copy of the built stylesheet rather than inlining, per `${CLAUDE_SKILL_DIR}/references/live-arms.md`.
32
33
  - On the default path, inline every style, script, and asset the page needs. The render reads the file off disk, so a page reaching for a build step or a network font renders without it and the arms differ by something nobody chose.
33
34
  - On the default path, declare a font stack the machine resolves, such as `system-ui` behind a generic fallback. The render refuses a page that would rewrap against a substitute rather than shipping a false comparison, so a page naming no font at all is refused on whatever the default resolves to.
34
35
  - Vary one property across the arms. A page whose arms differ in three ways answers no question, since the pick cannot say which difference decided it.
36
+ - Vary the property the decision is about, which the rule above is satisfiable without. Holding composition fixed and varying color obeys it exactly and produces five skins of one design, because a set differing in the layer a reader notices least answers nothing. A palette is chosen to serve a composition, so it cannot be picked ahead of one.
35
37
 
36
38
  ## Step 3: render and hand off
37
39
 
@@ -44,8 +46,10 @@ canon capture <dest>/candidates.html --selector <element>
44
46
  - `--selector` has no default and the command refuses without it. Name the element wrapping the arms rather than `body`, which crops to whatever the page's own margins leave.
45
47
  - `canon capture` and `canon drive` both need a browser binary the toolkit does not install. When either refuses for that reason, report the refusal and name `bunx playwright install chromium` as the repair, then stop rather than describing an arm nobody has seen.
46
48
  - Serve the page instead of capturing it when the operator has to drive the decision, such as a hover response, a scroll-linked position, or a pace. Start `canon serve <dest> --entry candidates.html` in the background and read the link off its record, since the printed link opens `index.html` without that flag and `<dest>` holds no such file. A still answers how a thing looks and answers none of those.
49
+ - Write the render inside the record that cites it wherever one exists, by pointing `--out` at `<dest>/renders/` rather than at a session scratch path. A pick taken from an image the record does not hold is a judgment nobody but this session can check, and the archival capture in Step 6 covers the final round alone.
47
50
  - Hand over the address rather than a description. Emit the PNG path on its own line, and the link beside it where the page is served.
48
51
  - Never report a visual result you have not looked at. A claim about appearance with no render behind it is a guess.
52
+ - Look to judge rather than to confirm. Reading the image back to check it rendered satisfies the rule above and still hands over weak work, so name the weakest thing on the page in a sentence. Where that sentence would embarrass the work, fix it and hand over the second version. Say the remaining weakness out loud either way, so the operator is not hunting for what you already know.
49
53
 
50
54
  ## Step 4: take the pick
51
55
 
@@ -58,11 +62,11 @@ Put the choice to the operator through the structured question surface, since a
58
62
 
59
63
  ## Step 5: loop on the pick
60
64
 
61
- 1. Narrow the page to the picked arm, plus whatever the operator asked to change about it.
65
+ 1. Write each iteration to its own `<dest>` rather than narrowing the previous one in place, suffixing the slug so the folders sort. An iteration overwritten is one a later pass cannot open, and the losing round is what stops a correction re-proposing something already rejected.
62
66
  2. Write fresh arms off the pick and return to Step 2 where the correction opens a new question. Revise the one arm where it does not.
63
67
  3. Re-render, hand off again, and take the next answer.
64
68
  4. Repeat until the operator says it is right. The loop stops on their word and on nothing else, so a run stopping because the arms stopped differing has stopped early.
65
- 5. Hold the real surface untouched across every iteration. Nothing outside `<dest>` changes until the pick is final.
69
+ 5. Hold the real surface untouched across every iteration. Nothing outside the run's own folders changes until the pick is final.
66
70
 
67
71
  ## Step 6: close
68
72
 
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/canon",
3
3
  "type": "module",
4
- "version": "4.88.0",
4
+ "version": "4.89.0",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -4,26 +4,26 @@
4
4
  file changes freely and nothing a project wrote lives in it. */
5
5
 
6
6
  :root {
7
- --color-background: #191512;
8
- --color-surface: #211c19;
9
- --color-chrome: #241e1a;
10
- --color-border: #2f2823;
11
- --color-text: #f4efe9;
12
- --color-text-body: #c9c0b7;
13
- --color-text-secondary: #a79d94;
14
- --color-muted: #948a81;
15
- --color-accent: #e0724b;
7
+ --color-background: #0f0e0c;
8
+ --color-surface: #151412;
9
+ --color-chrome: #1b1a18;
10
+ --color-border: #2a2926;
11
+ --color-text: #d9d7d4;
12
+ --color-text-body: #aeada9;
13
+ --color-text-secondary: #8c8b86;
14
+ --color-muted: #7f7f7c;
15
+ --color-accent: #c76b5f;
16
16
  --color-success: #61c454;
17
- --color-light-background: #faf7f2;
18
- --color-light-surface: #f4efe6;
19
- --color-light-chrome: #ede4d6;
20
- --color-light-text: #1a1815;
21
- --color-light-text-body: #3d3630;
22
- --color-light-text-secondary: #5c544b;
23
- --color-light-muted: #726b62;
24
- --color-light-accent: #a4471c;
17
+ --color-light-background: #fbfaf8;
18
+ --color-light-surface: #f1f1ee;
19
+ --color-light-chrome: #e9e8e5;
20
+ --color-light-text: #2c2c29;
21
+ --color-light-text-body: #4b4947;
22
+ --color-light-text-secondary: #666561;
23
+ --color-light-muted: #6e6d6c;
24
+ --color-light-accent: #ad4a4b;
25
25
  --color-light-success: #2d6b22;
26
- --color-light-border: #e4dcd0;
26
+ --color-light-border: #d5d4d1;
27
27
  --space-xs: 6px;
28
28
  --space-sm: 12px;
29
29
  --space-md: 18px;
@@ -108,58 +108,64 @@ export const TOKENS: DesignTokens = {
108
108
  '',
109
109
  'Warning and error hold ANSI codes because that is what `scripts/lib/ui.sh` writes and no rendered surface implements an equivalent. Giving either a hex value would invent a mapping no file has, so they carry no contrast reading either.',
110
110
  '',
111
- 'Success is the one of the three that does have a rendered equivalent, which is why it carries a hex. `assets/captures/install.html` marks every confirmed step with it, and the shell writes `ANSI 32` for the same role, so the two are one role in two registers rather than one value in two spellings. The hex is what the rendered surface picked and no reading claims the terminal renders that value. It declares `background` alone as its ground, since that is the only role it is drawn on, where every other dark text role is drawn on both.',
111
+ 'Success is the one of the three that does have a rendered equivalent, which is why it carries a hex. `assets/captures/install.html` marks every confirmed step with it, and the shell writes `ANSI 32` for the same role, so the two are one role in two registers rather than one value in two spellings. The hex is what the rendered surface picked and no reading claims the terminal renders that value. It declares `background` alone as its ground, since that is the only role it is drawn on, where every other dark text role is drawn on both. It is the one role below that is not derived.',
112
+ '',
113
+ "Every other role is derived rather than picked, solved in OKLCH by binary search for the lightness that hits a target contrast against its ground, using this module's own anchors. Six anchors are the whole system: ground lightness 0.985 light and 0.165 dark, neutral chroma 0.003 light and 0.004 dark, neutral hue 90 both, accent hue 22 light and 28 dark, accent chroma 0.13 light and 0.12 dark for a mark and 0.095 light and 0.09 dark for a fill. Only the mark step is rendered below, in `accent`. That token already stands in for a fill in practice: `assets/captures/hero.html`'s `.cmd` rule paints its whole background with `var(--color-accent)` and sets text on top, which is a fill use rather than a mark one. The fill chroma step has no token of its own yet, so a lower-saturation `accent-fill` would recolor that button rather than introduce a new consumer. Targets, which are inputs rather than results: text 13.5, body 8.6, secondary 5.6, muted 4.6, accent 5.2. Dark is its own anchor set rather than an inversion of light, and every arm measured in the groundwork behind this landed on identical neutral ratios, which is what made the accent choice a question about hue alone.",
114
+ '',
115
+ '`muted` and `light-muted` solve against `surface` rather than `background`. Both declare two grounds and the groundwork solved its target against one, so the recorded hex cleared 4.6 against `background` and read 4.40 dark, 4.24 light against `surface`, the tighter of the two since surface sits a step closer to its text color. The values here are re-solved for the same 4.6 target read against `surface` instead, which clears both: 4.58 dark, 4.56 light against `surface`, and 4.80 dark, 4.95 light against `background`.',
116
+ '',
117
+ 'The accent is a quiet red at hue 22 light, 28 dark, chosen over a vivid red that read as an error state against a page reporting success, over indigo which carries less distinctiveness at hue 280 in tooling already dominated by that hue, and over rust at hue 42, a larger temperature shift than the problem required. The debt this accepts: `error` and `warning` now share a register with the most repeated element on every rendered surface, so both need differentiating by lightness or by an icon rather than by hue, and that work is unscheduled.',
112
118
  ].join('\n'),
113
119
 
114
120
  color: [
115
121
  {
116
122
  role: 'background',
117
123
  intent: 'page canvas',
118
- value: '#191512',
124
+ value: '#0f0e0c',
119
125
  },
120
126
  {
121
127
  role: 'surface',
122
128
  intent: 'cards, panels, raised blocks',
123
- value: '#211c19',
129
+ value: '#151412',
124
130
  },
125
131
  {
126
132
  role: 'chrome',
127
133
  intent: 'the window titlebar, one step above the canvas',
128
- value: '#241e1a',
134
+ value: '#1b1a18',
129
135
  },
130
136
  {
131
137
  role: 'border',
132
138
  intent: 'every rule and panel edge',
133
- value: '#2f2823',
139
+ value: '#2a2926',
134
140
  },
135
141
  {
136
142
  role: 'text',
137
143
  intent: 'headings, counts, emphasized runs',
138
- value: '#f4efe9',
144
+ value: '#d9d7d4',
139
145
  grounds: DARK_GROUNDS,
140
146
  },
141
147
  {
142
148
  role: 'text-body',
143
149
  intent: 'default body copy',
144
- value: '#c9c0b7',
150
+ value: '#aeada9',
145
151
  grounds: DARK_GROUNDS,
146
152
  },
147
153
  {
148
154
  role: 'text-secondary',
149
155
  intent: 'labels, captions, supporting copy',
150
- value: '#a79d94',
156
+ value: '#8c8b86',
151
157
  grounds: DARK_GROUNDS,
152
158
  },
153
159
  {
154
160
  role: 'muted',
155
161
  intent: 'the faintest step, trailing notes',
156
- value: '#948a81',
162
+ value: '#7f7f7c',
157
163
  grounds: DARK_GROUNDS,
158
164
  },
159
165
  {
160
166
  role: 'accent',
161
167
  intent: 'install command, mark, primary action',
162
- value: '#e0724b',
168
+ value: '#c76b5f',
163
169
  grounds: DARK_GROUNDS,
164
170
  },
165
171
  {
@@ -173,46 +179,46 @@ export const TOKENS: DesignTokens = {
173
179
  {
174
180
  role: 'light-background',
175
181
  intent: 'page canvas on a light ground',
176
- value: '#faf7f2',
182
+ value: '#fbfaf8',
177
183
  },
178
184
  {
179
185
  role: 'light-surface',
180
186
  intent: 'cards and panels on a light ground',
181
- value: '#f4efe6',
187
+ value: '#f1f1ee',
182
188
  },
183
189
  {
184
190
  role: 'light-chrome',
185
191
  intent: 'the window titlebar, one step above the canvas',
186
- value: '#ede4d6',
192
+ value: '#e9e8e5',
187
193
  },
188
194
  {
189
195
  role: 'light-text',
190
196
  intent: 'primary text on a light ground',
191
- value: '#1a1815',
197
+ value: '#2c2c29',
192
198
  grounds: LIGHT_GROUNDS,
193
199
  },
194
200
  {
195
201
  role: 'light-text-body',
196
202
  intent: 'default body copy on a light ground',
197
- value: '#3d3630',
203
+ value: '#4b4947',
198
204
  grounds: LIGHT_GROUNDS,
199
205
  },
200
206
  {
201
207
  role: 'light-text-secondary',
202
208
  intent: 'labels, captions, supporting copy on a light ground',
203
- value: '#5c544b',
209
+ value: '#666561',
204
210
  grounds: LIGHT_GROUNDS,
205
211
  },
206
212
  {
207
213
  role: 'light-muted',
208
214
  intent: 'secondary text on a light ground',
209
- value: '#726b62',
215
+ value: '#6e6d6c',
210
216
  grounds: LIGHT_GROUNDS,
211
217
  },
212
218
  {
213
219
  role: 'light-accent',
214
220
  intent: 'links and primary action on light',
215
- value: '#a4471c',
221
+ value: '#ad4a4b',
216
222
  grounds: LIGHT_GROUNDS,
217
223
  },
218
224
  {
@@ -224,7 +230,7 @@ export const TOKENS: DesignTokens = {
224
230
  {
225
231
  role: 'light-border',
226
232
  intent: 'rules and panel edges on light',
227
- value: '#e4dcd0',
233
+ value: '#d5d4d1',
228
234
  verify: true,
229
235
  },
230
236
  ],