@erclx/canon 4.82.0 → 4.84.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 (41) hide show
  1. package/claude/.claude-plugin/plugin.json +1 -1
  2. package/claude/skills/docs-fold/REQUIREMENT.md +13 -0
  3. package/claude/skills/docs-fold/SKILL.md +20 -0
  4. package/claude/skills/docs-fold/references/classify.md +78 -0
  5. package/claude/skills/draft-and-pick/SKILL.md +1 -1
  6. package/claude/skills/draft-slides/SKILL.md +1 -1
  7. package/claude/skills/draft-wireframes/REQUIREMENT.md +1 -1
  8. package/claude/skills/draft-wireframes/SKILL.md +7 -4
  9. package/claude/skills/ux-walkthrough/REQUIREMENT.md +53 -0
  10. package/claude/skills/ux-walkthrough/SKILL.md +50 -0
  11. package/claude/skills/ux-walkthrough/references/builds.md +15 -0
  12. package/claude/skills/ux-walkthrough/references/candidate-pages.md +26 -0
  13. package/claude/skills/ux-walkthrough/references/measuring.md +22 -0
  14. package/claude/skills/ux-walkthrough/references/record.md +36 -0
  15. package/claude/skills/ux-walkthrough/references/relay.md +25 -0
  16. package/claude/skills/youtube-transcripts/SKILL.md +1 -1
  17. package/docs/agents/commands.md +1 -1
  18. package/docs/agents/context-audit-checks.md +22 -2
  19. package/docs/agents/context-audit.md +12 -2
  20. package/docs/agents/context-classify.md +1 -1
  21. package/docs/workflow/ai-workflow.md +2 -1
  22. package/docs/workflow/visual-design-workflow.md +2 -1
  23. package/governance/rules/claude/520-wireframes.md +1 -1
  24. package/package.json +1 -1
  25. package/src/claude/cases/workflow.ts +5 -0
  26. package/src/commands/context.ts +71 -2
  27. package/src/context/architecture.ts +73 -0
  28. package/src/context/audit.ts +18 -2
  29. package/src/context/gate.ts +44 -6
  30. package/src/context/wireframe-states.ts +238 -0
  31. package/src/record-root.ts +4 -2
  32. package/src/records/backup.ts +4 -3
  33. package/standards/architecture.md +11 -1
  34. package/standards/context.md +4 -1
  35. package/standards/design.md +6 -0
  36. package/standards/requirements.md +2 -1
  37. package/standards/skill.md +1 -1
  38. package/standards/wireframes.md +45 -29
  39. package/tooling/claude/reference.md +1 -1
  40. package/tooling/claude/seeds/CLAUDE.md +1 -1
  41. package/tooling/claude/seeds/canon/wireframes/index.md +2 -2
@@ -0,0 +1,238 @@
1
+ import { readFile, readdir, stat } from 'node:fs/promises'
2
+ import { basename, dirname, join, relative } from 'node:path'
3
+ import type { AuditedFolder } from '@/context/folders'
4
+ import { type BodyLine, bodyLines } from '@/markdown/scan'
5
+
6
+ /** `standards/wireframes.md`'s `## States` heading, matched by text alone. */
7
+ const STATES_HEADING = /^##\s+States\s*$/i
8
+ const SECTION_HEADING = /^##\s+\S/
9
+ const TABLE_ROW = /^\s*\|/
10
+ const TABLE_SEPARATOR = /^\s*\|[\s:|-]+\|\s*$/
11
+
12
+ /** The one exception the standard admits to the states/evidence bijection. */
13
+ const NOT_CAPTURED = 'not captured'
14
+
15
+ /** A fenced block's opening delimiter naming the `plaintext` info string. */
16
+ const PLAINTEXT_FENCE = /^`{3,}\s*plaintext\s*$/i
17
+
18
+ export interface StateRow {
19
+ readonly line: number
20
+ readonly state: string
21
+ /** The evidence cell as written, backticks and all. */
22
+ readonly evidence: string
23
+ }
24
+
25
+ export interface MissingFolderFinding {
26
+ readonly line: number
27
+ readonly state: string
28
+ /** The cited path, so a report names what to go create or fix. */
29
+ readonly path: string
30
+ }
31
+
32
+ export interface UnlistedFolderFinding {
33
+ /** The evidence root the folder sits under, cited by at least one row. */
34
+ readonly root: string
35
+ readonly folder: string
36
+ }
37
+
38
+ export interface WireframeStatesReport {
39
+ readonly rel: string
40
+ readonly rows: readonly StateRow[]
41
+ /** A row whose evidence cell names a path with no folder at it. */
42
+ readonly missingFolders: readonly MissingFolderFinding[]
43
+ /** A folder under a cited root that no row names. */
44
+ readonly unlistedFolders: readonly UnlistedFolderFinding[]
45
+ /** Line of the `plaintext` fence, absent from an entry carrying no sketch. */
46
+ readonly sketchLine?: number
47
+ /** Whether that sketch sits beside a state whose evidence already exists. */
48
+ readonly sketchWithEvidence: boolean
49
+ }
50
+
51
+ function cells(row: string): string[] {
52
+ const parts = row.split('|')
53
+ if ((parts[0] ?? '').trim() === '') parts.shift()
54
+ if ((parts[parts.length - 1] ?? '').trim() === '') parts.pop()
55
+ return parts.map((cell) => cell.trim())
56
+ }
57
+
58
+ function columnIndex(headers: readonly string[], name: string): number {
59
+ return headers.findIndex(
60
+ (header) => header.toLowerCase() === name.toLowerCase(),
61
+ )
62
+ }
63
+
64
+ /**
65
+ * Reads the States table under the `## States` heading, matching its `State`
66
+ * and `Evidence` columns by header text rather than position.
67
+ *
68
+ * Column order is the one part of the standard's own table the standard could
69
+ * still move during its own review, per the plan this ships under. Matching
70
+ * position would break silently on a reorder, where matching text breaks the
71
+ * same way a renamed header would: visibly, by finding nothing.
72
+ */
73
+ export function parseStatesTable(lines: readonly BodyLine[]): StateRow[] {
74
+ let index = 0
75
+ while (index < lines.length && !STATES_HEADING.test(lines[index].text)) {
76
+ index++
77
+ }
78
+ if (index >= lines.length) return []
79
+ index++
80
+
81
+ while (index < lines.length && !SECTION_HEADING.test(lines[index].text)) {
82
+ const separator = lines[index + 1]
83
+ if (
84
+ TABLE_ROW.test(lines[index].text) &&
85
+ separator !== undefined &&
86
+ TABLE_SEPARATOR.test(separator.text)
87
+ ) {
88
+ break
89
+ }
90
+ index++
91
+ }
92
+ if (
93
+ index >= lines.length ||
94
+ SECTION_HEADING.test(lines[index].text) ||
95
+ !TABLE_ROW.test(lines[index].text)
96
+ ) {
97
+ return []
98
+ }
99
+
100
+ const headers = cells(lines[index].text)
101
+ const stateCol = columnIndex(headers, 'State')
102
+ const evidenceCol = columnIndex(headers, 'Evidence')
103
+ if (stateCol === -1 || evidenceCol === -1) return []
104
+
105
+ index += 2
106
+ const rows: StateRow[] = []
107
+ while (index < lines.length && TABLE_ROW.test(lines[index].text)) {
108
+ const row = cells(lines[index].text)
109
+ rows.push({
110
+ line: lines[index].number,
111
+ state: row[stateCol] ?? '',
112
+ evidence: row[evidenceCol] ?? '',
113
+ })
114
+ index++
115
+ }
116
+
117
+ return rows
118
+ }
119
+
120
+ /** Line of the first `plaintext`-fenced sketch, or nothing. */
121
+ export function findSketchLine(lines: readonly BodyLine[]): number | undefined {
122
+ for (const line of lines) {
123
+ if (PLAINTEXT_FENCE.test(line.text.trim())) return line.number
124
+ }
125
+ return undefined
126
+ }
127
+
128
+ /**
129
+ * Reads an evidence cell into the path it cites, or nothing for the one cell
130
+ * value the standard exempts from having one.
131
+ */
132
+ function evidencePath(cell: string): string | undefined {
133
+ const stripped = cell.replace(/^`+|`+$/g, '').trim()
134
+ if (stripped.toLowerCase() === NOT_CAPTURED) return undefined
135
+ return stripped.replace(/\/+$/, '')
136
+ }
137
+
138
+ async function isDirectory(path: string): Promise<boolean> {
139
+ try {
140
+ return (await stat(path)).isDirectory()
141
+ } catch {
142
+ return false
143
+ }
144
+ }
145
+
146
+ async function subfolders(path: string): Promise<string[]> {
147
+ try {
148
+ const entries = await readdir(path, { withFileTypes: true })
149
+ return entries
150
+ .filter((entry) => entry.isDirectory())
151
+ .map((entry) => entry.name)
152
+ } catch {
153
+ return []
154
+ }
155
+ }
156
+
157
+ /**
158
+ * Measures one wireframe entry against its own States table.
159
+ *
160
+ * The evidence cell resolves as the literal path it names against `root`,
161
+ * per the plan's operator answer, since the standard puts the path there and a
162
+ * root guessed from convention reads wrong in a monorepo carrying more than
163
+ * one evidence tree.
164
+ *
165
+ * An unlisted folder is reported only under a root at least one row already
166
+ * cites, per the same operator answer, so a project holding evidence with no
167
+ * states table at all reports nothing. `roots` is built from the rows this
168
+ * loop already resolved rather than scanned separately, which is what keeps
169
+ * the two readings in the same pass.
170
+ */
171
+ export async function measureWireframeStates(
172
+ root: string,
173
+ rel: string,
174
+ source: string,
175
+ ): Promise<WireframeStatesReport> {
176
+ const lines = bodyLines(source)
177
+ const rows = parseStatesTable(lines)
178
+ const sketchLine = findSketchLine(lines)
179
+
180
+ const missingFolders: MissingFolderFinding[] = []
181
+ const cited = new Map<string, Set<string>>()
182
+ let anyCaptured = false
183
+
184
+ for (const row of rows) {
185
+ const path = evidencePath(row.evidence)
186
+ if (path === undefined) continue
187
+
188
+ if (!(await isDirectory(join(root, path)))) {
189
+ missingFolders.push({ line: row.line, state: row.state, path })
190
+ continue
191
+ }
192
+
193
+ anyCaptured = true
194
+ const evidenceRoot = dirname(path)
195
+ const folder = basename(path)
196
+ const named = cited.get(evidenceRoot) ?? new Set<string>()
197
+ named.add(folder)
198
+ cited.set(evidenceRoot, named)
199
+ }
200
+
201
+ const unlistedFolders: UnlistedFolderFinding[] = []
202
+ for (const [evidenceRoot, named] of cited) {
203
+ for (const folder of await subfolders(join(root, evidenceRoot))) {
204
+ if (!named.has(folder)) {
205
+ unlistedFolders.push({ root: evidenceRoot, folder })
206
+ }
207
+ }
208
+ }
209
+
210
+ return {
211
+ rel,
212
+ rows,
213
+ missingFolders,
214
+ unlistedFolders,
215
+ ...(sketchLine !== undefined && { sketchLine }),
216
+ sketchWithEvidence: sketchLine !== undefined && anyCaptured,
217
+ }
218
+ }
219
+
220
+ /** Measures every wireframe entry in a resolved `wireframes` folder. */
221
+ export async function measureWireframeFolder(
222
+ root: string,
223
+ folder: AuditedFolder,
224
+ ): Promise<WireframeStatesReport[]> {
225
+ const reports: WireframeStatesReport[] = []
226
+
227
+ for (const path of folder.entries) {
228
+ reports.push(
229
+ await measureWireframeStates(
230
+ root,
231
+ relative(root, path),
232
+ await readFile(path, 'utf8'),
233
+ ),
234
+ )
235
+ }
236
+
237
+ return reports
238
+ }
@@ -46,8 +46,9 @@ const CANON_SCRATCH = 'tmp'
46
46
  /**
47
47
  * Every entry that lives under the record root, at the name `.claude/` gave it.
48
48
  *
49
- * These are the twelve ignore patterns the move to `.canon/` collapsed into one,
50
- * so the list counts entries rather than record folders: `.records.git` is the
49
+ * These are the twelve ignore patterns the move to `.canon/` collapsed into
50
+ * one, plus every record folder added since, so the list counts entries
51
+ * rather than record folders: `.records.git` is the
51
52
  * backup history rather than a record, and `README.md` is a file a records pull
52
53
  * writes back. `worktrees` is absent because the harness creates a worktree
53
54
  * under `.claude/` and requires its target to sit there.
@@ -72,6 +73,7 @@ export const RECORD_ENTRIES: readonly string[] = [
72
73
  'review',
73
74
  'tasks',
74
75
  'teach',
76
+ 'walkthroughs',
75
77
  ]
76
78
 
77
79
  /** Whether a name under `.claude/` is one the record root owns. */
@@ -6,8 +6,8 @@ import { RECORD_ROOTS, recordRoot } from '@/record-root'
6
6
 
7
7
  /**
8
8
  * The folders a backup carries, relative to the record root `workTree` resolves
9
- * rather than to either root specifically, since the same ten names sit under
10
- * whichever one a tree holds.
9
+ * rather than to either root specifically, since the same eleven names sit
10
+ * under whichever one a tree holds.
11
11
  *
12
12
  * Nothing bounds this list any more, and the move is what took the bound away.
13
13
  * The claude manifest used to ship a folder apiece, so the `# Claude` group
@@ -18,7 +18,7 @@ import { RECORD_ROOTS, recordRoot } from '@/record-root'
18
18
  * a name is written here.
19
19
  *
20
20
  * Three counts describe this surface and each is right about a different
21
- * question, so they are stated apart rather than reconciled. Ten is what a
21
+ * question, so they are stated apart rather than reconciled. Eleven is what a
22
22
  * disk loss would take, which is this list. Twelve is what sat under `.claude/`
23
23
  * as an ignored folder before the move, which adds the scratch folder that is
24
24
  * deletable without loss and `worktrees/`, whose contents belong to the
@@ -47,6 +47,7 @@ export const BACKED_FOLDERS = [
47
47
  'tasks',
48
48
  'teach',
49
49
  'transcripts',
50
+ 'walkthroughs',
50
51
  ] as const
51
52
 
52
53
  /**
@@ -28,16 +28,26 @@ Does not govern:
28
28
  - How individual functions work line by line. The code carries its own behavior.
29
29
  - Full type definitions. They live in code. Reference the shape conceptually if needed.
30
30
  - A measurement paragraph specific to one domain's own mechanism. Route it to that domain's `canon/context/<domain>.md` entry instead. The choice and its rejected alternative stay here whatever their reach, since reach is what makes a decision cross-domain, not how many domains its supporting measurement happens to touch.
31
+ - A decision that constrains one domain alone. It lives in that domain's context entry, and this file carries at most one line pointing at it.
32
+ - The history of how a decision was reached or revised: rounds of candidates, a figure followed by its correction, a branch or change that moved a number. That trail goes to the decision log or the change that introduced it.
31
33
 
32
34
  ## Sections
33
35
 
34
36
  Use `## Overview`, `## Key technical decisions` with one named H3 per decision, and `## Risks / open questions`. Name each decision and give the reasoning, especially for non-obvious choices. Skip entries where the rationale is self-evident.
35
37
 
38
+ ## Keeping it current
39
+
40
+ - Rewrite a decision a later one changed rather than appending the change beside it. A reader should find the design that stands in one place, with the alternative that lost stated once.
41
+ - Hold only what is open under `## Risks / open questions`. An entry leaves the section in the change that settles it, becoming a decision here when it constrains more than one domain and moving to that domain's context entry when it does not.
42
+
36
43
  ## Verification anchors
37
44
 
38
45
  A decision's reasoning stays correct while the numbers it cites move. The anchor records what a measured claim was read against, so a reader can tell a number that was checked and held from one nobody has looked at since.
39
46
 
40
47
  - Close a decision entry whose reasoning cites a measured number with a trailing sentence naming the short commit SHA and the ISO date that number was read: `Measured at <short-sha> on <YYYY-MM-DD>.`
48
+ - Name a commit, never a pull request number or a branch. A branch is gone after merge, and a pull request number resolves only on the forge.
49
+ - Carry one anchor per figure. When a number is re-measured, rewrite the number and its anchor in place, and leave the old value to the change that moved it.
50
+ - Point at a generated file that already records a figure and its commit, rather than copying the figure and anchoring the copy.
41
51
  - Anchor on the number alone. A decision citing none takes no anchor whatever its reasoning rests on, because a marker over a claim nobody can re-measure is one no reader can falsify.
42
52
  - Anchor a decision when writing it or when amending its reasoning. Leave an entry written before the rule unanchored rather than dating it by blame, which is archaeology for a marker nothing reads back.
43
53
  - Read an absent anchor as unchecked rather than as current. On an entry citing no number there is nothing to check. On one citing a number the number is due a read.
@@ -46,7 +56,7 @@ A decision's reasoning stays correct while the numbers it cites move. The anchor
46
56
 
47
57
  ## Length
48
58
 
49
- Every session pays for this file before any work starts, so a heavy read is a real cost. Judge weight by reading the file rather than by counting it: a file that reads heavy is carrying too many decisions, not decisions written too long.
59
+ Every session pays for this file before any work starts, so a heavy read is a real cost. Judge weight by reading the file rather than by counting it: a file that reads heavy is carrying too many decisions, not decisions written too long. A word count, for the file and for each decision, is read alongside that judgment when one is available, and it never gates.
50
60
 
51
61
  - Bring a heavy file back by merging two decisions or retiring one, never by compressing a decision's prose.
52
62
  - Yield to the paragraph weight checkpoint in `markdown.md`. A paragraph past the checkpoint is a defect no length guideline licenses.
@@ -70,6 +70,7 @@ Pick by what the domain is. Add domain-specific headings as needed.
70
70
  ## Ordering
71
71
 
72
72
  - Order sections `Overview`, `Layout`, `Decisions`, `Gotchas`, then everything else.
73
+ - Name a heading for its subject as it stands (`## Cache`), never for the event that shaped it (`## The cache was the untested part, and the test moved it`). A heading is what a scan reads, so a narrating heading turns the table of contents into a changelog.
73
74
  - Entries get read top-down and often partially, so irreducible content sits above recoverable content.
74
75
 
75
76
  ## The development entry
@@ -84,6 +85,7 @@ Only the `development` entry carries this section. It is not a general-purpose h
84
85
  - Decisions specific to the domain. Broader cross-domain decisions belong in `canon/ARCHITECTURE.md`.
85
86
  - Constraints, gotchas, things tried and rejected
86
87
  - Domain-specific conventions that do not fit a `paths:`-scoped rule
88
+ - A measured figure, anchored the way `architecture.md` states under `## Verification anchors`, so an entry and the architecture record date a number the same way.
87
89
  - A reference to another entry, spelled as the path that entry sits at rather than as its bare filename. A bare name resolves against whichever folder the reader is already in, so a domain that splits into subfolders strands every inbound reference and the break surfaces nowhere. A reference to a seed, a standard, or a file the project owns elsewhere keeps the form its own surface uses.
88
90
 
89
91
  ## What does not go in
@@ -98,12 +100,13 @@ Only the `development` entry carries this section. It is not a general-purpose h
98
100
  - Anything already in `canon/REQUIREMENTS.md` or `canon/ARCHITECTURE.md`.
99
101
  - The history of how the domain reached its current shape. An entry describes the repository as it stands, so a change number, release label, or date attached to a change goes wherever the project tracks work.
100
102
  - A rejected alternative's provenance, which is the same rule at the one place the section above admits history. Keep what was tried and why it lost. Cut who tried it and when.
103
+ - The route to a decision: the rounds of candidates, the tuning steps a threshold passed through, the review pass that caught a miss. State the decision, what lost, and why, once and in the present tense. The route goes to the decision log or the change that introduced it.
104
+ - Measured results a generated file already carries. Point at the file instead of copying the figure, so a re-run cannot leave the entry behind.
101
105
 
102
106
  ## Length
103
107
 
104
108
  - Aim for one entry per domain. There is no hard cap. Length is a symptom, not the defect.
105
109
  - Past roughly 150 rendered lines, check three things before adding more: whether the entry still covers a single domain, whether it has filled with content `ls` or `--help` reproduces, and whether it has accumulated the history of its own changes. Fix whichever is true rather than trimming to hit a number. Rendered lines count as `markdown.md` defines them.
106
- - Where a bullet sits past the weight checkpoint `markdown.md` states, the overflow to move is the incident that motivated the decision, which specializes that rule's instruction to send the overflow to prose. Keep the current design and the alternative that lost, and send the incident to the change that introduced it, the issue that tracked it, or the research record behind it.
107
110
  - Never cut a `## Decisions` or `## Gotchas` entry to shorten a file. Cut a `## Layout` or `## CLI` section instead.
108
111
  - Retire a decision or gotcha once its subject is gone, rewriting the bullet to state the current design rather than leaving the narration of what it replaced beside it. A rejected alternative is not a retired one, so what was tried and why it lost stays whatever its age. The rule above protects content whose subject is live, and this one releases content whose subject is not.
109
112
  - Rewrite a decision a later one replaced rather than appending the replacement beside it. The subject is still live, so the rule above does not reach it, and two bullets on one subject leave a reader to work out which of them is current. State the design that stands and keep the superseded reasoning only where it is the alternative that lost.
@@ -27,6 +27,7 @@ Does not govern:
27
27
 
28
28
  - CSS classes and prop names. Those live in code.
29
29
  - Anything that needs updating every time the code is refactored
30
+ - The history of how a value or a mark was chosen: the candidates, the rounds, the date of a pick. State the rule and the current value. The trail goes to the decision log.
30
31
 
31
32
  ## Format
32
33
 
@@ -48,6 +49,11 @@ A prose section takes its uncertainty inline instead, in a sentence saying what
48
49
 
49
50
  Use `## Personality`, `## Color`, `## Typography`, `## Spacing`, `## Borders`, `## Motion`, and `## Iconography`. The token tables carry fixed headers the renderer reads.
50
51
 
52
+ Add either optional section when the project has content for it:
53
+
54
+ - `## Layout`: page width, breakpoints, and grid rules, as current constraints
55
+ - `## Mark`: the logo's construction rules and the files that carry it
56
+
51
57
  ## Template
52
58
 
53
59
  The column headers are the strings the renderer parses, read by exact key, so they stay verbatim. Row names are not. Each is slugged into the variable name it emits, which leaves a project free to rename a row, add one, or drop one it has no use for.
@@ -30,10 +30,11 @@ Does not govern:
30
30
 
31
31
  - Implementation details, API names, or internal component references
32
32
  - Anything that describes how a feature is built rather than what it does
33
+ - Measured results, such as scores, benchmark figures, or token counts. They move on every run and this file changes least. Name where the results live instead.
33
34
 
34
35
  ## Sections
35
36
 
36
- Use `## Problem`, `## Goals`, `## Non-goals`, `## MVP features`, `## Tech stack`, and `## Constraints`. Add `## Distribution` when the rule below applies. Drop a section rather than pad it with filler.
37
+ Use `## Problem`, `## Goals`, `## Non-goals`, `## MVP features`, `## Tech stack`, and `## Constraints`. Add `## Distribution` when the rule below applies. Add `## Premise` when the project exists to answer a question, stated as the question and what would count as an answer, never as the answer measured so far. Drop a section rather than pad it with filler.
37
38
 
38
39
  ## Lifecycle
39
40
 
@@ -210,7 +210,7 @@ Without this skill, a session <observed failure>, <observed failure>.
210
210
  ### Output and tuning
211
211
 
212
212
  - Skill success lines emit the full relative path from the project root (`<dir>/<file>`) for any file written, updated, or deleted. A bare filename names a file the reader cannot open. The `## Output` section of the project's instruction file sets the form that path takes, so a skill body states which path is emitted and leaves the form to that section.
213
- - Before a skill writes anything, decide whether the output is a deliverable the project keeps or a toolkit session record. A deliverable lands among the project's own tracked files. A session record lands under `.canon/`, in the named subfolder for its kind (`tasks/`, `plans/`, `review/`, `memory/`, `groundwork/`, `intake/`, `proposals/`, `diagrams/`, or `teach/`, with `tmp/` for scratch nothing else claims), never in a folder the body invents. `canon/ARCHITECTURE.md`'s per-folder decisions are the precedent for which kind takes which folder.
213
+ - Before a skill writes anything, decide whether the output is a deliverable the project keeps or a toolkit session record. A deliverable lands among the project's own tracked files. A session record lands under `.canon/`, in the named subfolder for its kind (`tasks/`, `plans/`, `review/`, `memory/`, `groundwork/`, `intake/`, `proposals/`, `diagrams/`, `teach/`, or `walkthroughs/`, with `tmp/` for scratch nothing else claims), never in a folder the body invents. `canon/ARCHITECTURE.md`'s per-folder decisions are the precedent for which kind takes which folder.
214
214
  - Codify a skill's posted or generated output as a fenced template, and keep the body consistent with every capability the frontmatter description names.
215
215
  - When a skill gathers user input or pre-seeds a template, attach a concrete proposed default to every question, derived from project context. Accept "use defaults" as a bulk-confirm.
216
216
  - Separate correctness axes (routing, sourcing, escalation, decline) from shape axes (line count, formatting, variant sprawl) when tuning a skill. Tighten only on correctness regressions. Do not convert soft caps to hard caps for aesthetic drift when correctness passes.
@@ -24,11 +24,12 @@ Does not govern:
24
24
 
25
25
  A wireframe works when someone can rebuild the surface from it without opening the components:
26
26
 
27
- - What is on screen, and where does it sit relative to everything else?
28
- - Which states can a visitor reach, and what does each one look like?
27
+ - What regions are on screen, by name, and where does each sit relative to the others?
28
+ - Which states can a visitor reach, what triggers each, and where is its captured evidence?
29
29
  - What does its structural copy say, word for word, and where does its long-form content come from?
30
+ - What was deliberately left off the surface?
30
31
 
31
- A wireframe that fails these is non-conforming regardless of whether it satisfies every section rule below. The fences are the means. These three questions are the test.
32
+ A wireframe that fails these is non-conforming regardless of whether it satisfies every section rule below. The sections are the means. These four questions are the test.
32
33
 
33
34
  ## Transcription wireframes
34
35
 
@@ -45,18 +46,20 @@ Route detail the source citation does not already carry to `canon/context/` as u
45
46
 
46
47
  Both fields feed `canon/wireframes/index.md` when regenerated.
47
48
 
48
- ## Layout
49
+ ## Regions
49
50
 
50
- - Draw each surface as an ASCII block inside a `plaintext` fence. One fence per distinct layout.
51
- - Label regions with `←` annotations. Never use `#` for annotations.
52
- - Show a region's role, not its styling. `← status pill` reads better than a class name or hex value. A transcription wireframe is the exception: see `## Transcription wireframes`.
53
- - Keep the grid honest. The ASCII proportions should match the intended widths, since conveying proportion is the wireframe's job.
51
+ - Name every region the surface holds, in a bullet list ordered as a reader scans the page, each with where it sits relative to the others: `- Act pane: right of the answer from 1024 up, an overlay below 1024`. These names are the vocabulary plans, picks, and tests use, and the placement is what a list without a sketch would otherwise lose. Pixel sizes stay in `canon/DESIGN.md`.
52
+ - Draw an ASCII block inside a `plaintext` fence only for a layout that is not built yet. Label regions with `←` annotations, show a region's role rather than its styling, and keep the proportions honest.
53
+ - Remove the fence in the change that first captures evidence for the layout. The capture is the exact picture, and a sketch kept beside it drifts from it. A wireframe carrying both a sketch and an evidence folder for the same layout is non-conforming.
54
+ - Give a layout that changes across a breakpoint its own entry under the list, named by what triggers it (`At 1024 and wider`), not by an arbitrary label. A wider gutter alone is not a new layout.
55
+ - Keep one surface per file, indexed by `index.md`.
54
56
 
55
- ## Variants
57
+ ## States
56
58
 
57
- - Add a second fence only when the layout itself changes across a breakpoint or state. A wider gutter alone is not a new layout.
58
- - Name each variant by what triggers it (`## Desktop (≥768px)`, `## Empty state`), not by an arbitrary label.
59
- - One H2 per variant. Do not stack unrelated surfaces in one file. Keep one surface per file, indexed by `index.md`.
59
+ - List every state a visitor can reach in one table: the state name, what reaches it, what it shows in words, and its evidence folder.
60
+ - Name a state in plain kebab case (`answered`, `refused`) and name its evidence folder the same way, with no ordinal prefix. The table carries the reading order, so a state added in the middle renames nothing.
61
+ - Keep the table and the evidence folders one-to-one. A state with no capture yet reads `not captured` in its evidence cell, which is the one exception, and it stays visible until the capture lands.
62
+ - Add an H3 below the table only for a state that needs more than its row: what differs from the regions in words, and its own copy or behavior.
60
63
 
61
64
  ## Copy
62
65
 
@@ -66,9 +69,19 @@ Both fields feed `canon/wireframes/index.md` when regenerated.
66
69
 
67
70
  ## Behavior
68
71
 
69
- - Describe interaction intent: what the visitor does, what changes on screen, what each state looks like.
72
+ - Describe interaction intent: what the visitor does and what changes on screen.
70
73
  - State the rule, not the mechanism. `The rail tracks the active section as the visitor scrolls` is intent. The scroll handler, throttle, and observer margins are not.
71
- - Keep it to a short list. A Behavior section longer than the layout is a sign implementation detail has leaked in.
74
+ - Keep it to a short list. A Behavior section longer than the regions and states is a sign implementation detail has leaked in.
75
+
76
+ ## Not on this surface
77
+
78
+ - State what was deliberately left off, one bullet per exclusion, as a present-tense rule: `- No navigation rail`. This is the section that stops a draft from proposing what was already ruled out.
79
+ - Keep the reason to a clause where one is needed. The rounds that ruled it out go to the decision log.
80
+
81
+ ## What moves out
82
+
83
+ - Which candidate beat which, and when: the decision log
84
+ - Measurements such as contrast ratios and timings: `canon/DESIGN.md` for tokens and floors, the surface's context entry for the rest
72
85
 
73
86
  ## What moves to canon/context/
74
87
 
@@ -84,13 +97,12 @@ Reference the context entry from the wireframe by path when a reader needs the m
84
97
  ## Maintenance
85
98
 
86
99
  - When a surface's layout or interaction changes, update its wireframe file in the same PR. A wireframe showing a defunct layout is worse than none.
100
+ - When a state is added, removed, or renamed, update its row and its evidence folder in the same PR.
87
101
  - The Behavior and Copy prose around an ASCII block is prose and follows `markdown.md` and the `write-human` skill. The fenced block itself is not, so a check scoped to prose is the wrong thing to rely on for what sits inside it.
88
102
 
89
103
  ## Template
90
104
 
91
- One H2 per layout variant, each holding its own fence. A surface with a single layout carries one.
92
-
93
- ````markdown
105
+ ```markdown
94
106
  ---
95
107
  title: <Surface name>
96
108
  description: <when and where the surface appears>
@@ -98,17 +110,18 @@ description: <when and where the surface appears>
98
110
 
99
111
  # <Surface name>
100
112
 
101
- ## <what triggers this variant>
113
+ <One paragraph: what the surface is for and what it covers.>
102
114
 
103
- ```plaintext
104
- +------------------------------------------+
105
- | <region> ← <its role> |
106
- +------------------------------------------+
107
- | |
108
- | <region> ← <its role> |
109
- | |
110
- +------------------------------------------+
111
- ```
115
+ ## Regions
116
+
117
+ - <Region>: <what it holds>, <where it sits relative to the others>
118
+ - <Region>: <what it holds>, <where it sits relative to the others>
119
+
120
+ ## States
121
+
122
+ | State | Reached when | Shows | Evidence |
123
+ | ------- | ------------ | ---------- | -------------------------- |
124
+ | <state> | <trigger> | <in words> | `<evidence-root>/<state>/` |
112
125
 
113
126
  ## Copy
114
127
 
@@ -119,5 +132,8 @@ description: <when and where the surface appears>
119
132
  ## Behavior
120
133
 
121
134
  - <what the visitor does, and what changes on screen>
122
- - <what each reachable state looks like>
123
- ````
135
+
136
+ ## Not on this surface
137
+
138
+ - <what was deliberately left out, as a present-tense rule>
139
+ ```
@@ -15,7 +15,7 @@ canon/
15
15
  ├── DESIGN.md ← seeded. Visual intent and the decisions behind it
16
16
  ├── context/ ← seeded. Per-domain narrative. `index.md` is the discovery anchor.
17
17
  ├── decisions/ ← seeded. Decision history a canonical doc points at, never loaded eagerly. `<nn>-<slug>.md` records.
18
- └── wireframes/ ← seeded. Per-surface ASCII layouts. `index.md` is the discovery anchor; `<surface>.md` files hold the sketches and behavior bullets.
18
+ └── wireframes/ ← seeded. Per-surface regions and states. `index.md` is the discovery anchor; `<surface>.md` files hold the regions list, the states table, and the behavior bullets.
19
19
 
20
20
  .claude/
21
21
  ├── GOV.md ← retired. Removed by `canon gov sync` if present from a prior install
@@ -21,5 +21,5 @@
21
21
  - `src/`: [description]
22
22
  - `canon/DESIGN.md`: design tokens and the visual system
23
23
  - `canon/context/`: per-domain narrative (how a domain is structured, decisions, gotchas), indexed via `canon/context/index.md`
24
- - `canon/wireframes/`: per-surface ASCII layouts loaded on demand, indexed via `canon/wireframes/index.md`
24
+ - `canon/wireframes/`: per-surface regions and states loaded on demand, indexed via `canon/wireframes/index.md`
25
25
  - `canon/decisions/`: decision history a project doc points at, never loaded eagerly
@@ -1,8 +1,8 @@
1
1
  ---
2
2
  title: Wireframes
3
- subtitle: Per-surface ASCII layouts loaded on demand
3
+ subtitle: Per-surface regions and states loaded on demand
4
4
  ---
5
5
 
6
6
  # Wireframes
7
7
 
8
- Per-surface ASCII layouts loaded on demand
8
+ Per-surface regions and states loaded on demand