@erclx/canon 5.52.0 → 5.54.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 (201) hide show
  1. package/CHANGELOG.md +17 -0
  2. package/README.md +11 -13
  3. package/claude/.claude-plugin/plugin.json +1 -1
  4. package/claude/skills/canon-cli/SKILL.md +5 -8
  5. package/claude/skills/context-fold/REQUIREMENT.md +0 -1
  6. package/claude/skills/design-identity/REQUIREMENT.md +2 -2
  7. package/claude/skills/design-identity/SKILL.md +6 -6
  8. package/claude/skills/design-record/REQUIREMENT.md +2 -2
  9. package/claude/skills/design-record/SKILL.md +4 -4
  10. package/claude/skills/design-record/references/folder.md +1 -1
  11. package/claude/skills/design-taste/REQUIREMENT.md +1 -1
  12. package/claude/skills/design-taste/references/kinds.md +0 -1
  13. package/claude/skills/draw-figure/REQUIREMENT.md +3 -4
  14. package/claude/skills/draw-figure/SKILL.md +5 -6
  15. package/claude/skills/draw-figure/references/figures.md +0 -1
  16. package/claude/skills/draw-figure/references/hand-drawn.md +1 -1
  17. package/claude/skills/pick-by-eye/REQUIREMENT.md +4 -5
  18. package/claude/skills/pick-by-eye/SKILL.md +34 -41
  19. package/claude/skills/pick-by-eye/references/board.md +42 -0
  20. package/claude/skills/pick-by-eye/references/live-arms.md +3 -3
  21. package/claude/skills/plan-groundwork/SKILL.md +1 -1
  22. package/claude/skills/teach-craft/REQUIREMENT.md +30 -0
  23. package/claude/skills/teach-craft/SKILL.md +70 -0
  24. package/claude/skills/teach-craft/references/adopted.md +113 -0
  25. package/claude/skills/teach-craft/references/lesson-craft.md +60 -0
  26. package/claude/skills/{teach-workspace → teach-craft}/references/pedagogy.md +41 -6
  27. package/docs/agents/audits.md +0 -1
  28. package/docs/agents/install-and-sync.md +0 -8
  29. package/docs/target/migrations.md +16 -2
  30. package/docs/target/projects.md +1 -1
  31. package/docs/workflow/parallel-features.md +1 -1
  32. package/docs/workflow/skill-map-on-request.md +10 -22
  33. package/docs/workflow/skill-map.md +11 -14
  34. package/docs/workflow/visual-design-workflow.md +1 -4
  35. package/governance/detect.toml +1 -6
  36. package/governance/renames.toml +0 -1
  37. package/package.json +2 -8
  38. package/scripts/core/regen-web-previews.ts +2 -67
  39. package/src/audits/catalog.ts +0 -1
  40. package/src/browser/engine.ts +3 -6
  41. package/src/capture/render.ts +1 -1
  42. package/src/claude/cases/authoring.ts +0 -12
  43. package/src/claude/cases/misc.ts +0 -22
  44. package/src/claude/cases/workflow.ts +2 -38
  45. package/src/claude/skills-audit.ts +1 -1
  46. package/src/cli/help.ts +0 -10
  47. package/src/cli.ts +0 -8
  48. package/src/commands/design.ts +16 -315
  49. package/src/commands/records.ts +0 -1
  50. package/src/commands/serve.ts +2 -2
  51. package/src/commands/sync.ts +4 -5
  52. package/src/design/components.ts +8 -1514
  53. package/src/design/css.ts +5 -6
  54. package/src/design/fonts.ts +3 -5
  55. package/src/design/regen.ts +5 -32
  56. package/src/design/render.ts +1 -1
  57. package/src/design/tokens.ts +3 -2
  58. package/src/driver/drive.ts +3 -4
  59. package/src/driver/steps.ts +2 -5
  60. package/src/gate/stages.ts +4 -10
  61. package/src/gov/audit.ts +1 -6
  62. package/src/inventory/walk.ts +1 -1
  63. package/src/markdown/scope.ts +6 -35
  64. package/src/records/validate.ts +4 -176
  65. package/src/sandbox/equivalence.ts +0 -5
  66. package/src/shipped/references.ts +1 -2
  67. package/src/sync/check.ts +4 -29
  68. package/src/sync/stamp.ts +6 -6
  69. package/src/sync/target.ts +1 -4
  70. package/tooling/claude/reference.md +1 -1
  71. package/tooling/claude/seeds/canon/DESIGN.md +1 -1
  72. package/claude/skills/canvas-board/REQUIREMENT.md +0 -35
  73. package/claude/skills/canvas-board/SKILL.md +0 -143
  74. package/claude/skills/canvas-board/references/canvas.md +0 -170
  75. package/claude/skills/draft-and-pick/REQUIREMENT.md +0 -26
  76. package/claude/skills/draft-and-pick/SKILL.md +0 -11
  77. package/claude/skills/draft-doc/REQUIREMENT.md +0 -26
  78. package/claude/skills/draft-doc/SKILL.md +0 -11
  79. package/claude/skills/draft-figure/REQUIREMENT.md +0 -26
  80. package/claude/skills/draft-figure/SKILL.md +0 -11
  81. package/claude/skills/draft-identity/REQUIREMENT.md +0 -26
  82. package/claude/skills/draft-identity/SKILL.md +0 -11
  83. package/claude/skills/draft-ready/REQUIREMENT.md +0 -26
  84. package/claude/skills/draft-ready/SKILL.md +0 -11
  85. package/claude/skills/draft-screencast/REQUIREMENT.md +0 -26
  86. package/claude/skills/draft-screencast/SKILL.md +0 -11
  87. package/claude/skills/draft-slides/REQUIREMENT.md +0 -26
  88. package/claude/skills/draft-slides/SKILL.md +0 -11
  89. package/claude/skills/make-deck/REQUIREMENT.md +0 -36
  90. package/claude/skills/make-deck/SKILL.md +0 -80
  91. package/claude/skills/make-deck/references/deck.md +0 -183
  92. package/claude/skills/read-frames/REQUIREMENT.md +0 -30
  93. package/claude/skills/read-frames/SKILL.md +0 -55
  94. package/claude/skills/record-screencast/REQUIREMENT.md +0 -35
  95. package/claude/skills/record-screencast/SKILL.md +0 -99
  96. package/claude/skills/script-screencast/REQUIREMENT.md +0 -34
  97. package/claude/skills/script-screencast/SKILL.md +0 -70
  98. package/claude/skills/script-screencast/references/beats-template.md +0 -119
  99. package/claude/skills/teach-workspace/REQUIREMENT.md +0 -34
  100. package/claude/skills/teach-workspace/SKILL.md +0 -196
  101. package/claude/skills/teach-workspace/references/glossary.md +0 -90
  102. package/claude/skills/teach-workspace/references/lesson-build.md +0 -106
  103. package/claude/skills/teach-workspace/references/lesson-craft.md +0 -135
  104. package/claude/skills/teach-workspace/references/promotion-fold.md +0 -35
  105. package/claude/skills/teach-workspace/references/promotion.md +0 -116
  106. package/claude/skills/teach-workspace/references/revisit.md +0 -28
  107. package/claude/skills/teach-workspace/references/teach.md +0 -211
  108. package/claude/skills/video-craft/REQUIREMENT.md +0 -35
  109. package/claude/skills/video-craft/SKILL.md +0 -96
  110. package/claude/skills/video-craft/references/adopted.md +0 -110
  111. package/governance/rules/ui/canvas.md +0 -13
  112. package/src/canvas/address.ts +0 -227
  113. package/src/canvas/arrange.ts +0 -66
  114. package/src/canvas/capture.ts +0 -391
  115. package/src/canvas/client/app.tsx +0 -188
  116. package/src/canvas/client/inspector/align-grid.tsx +0 -162
  117. package/src/canvas/client/inspector/color-field.tsx +0 -398
  118. package/src/canvas/client/inspector/color-inputs.tsx +0 -139
  119. package/src/canvas/client/inspector/color-picker.tsx +0 -668
  120. package/src/canvas/client/inspector/color-space.ts +0 -240
  121. package/src/canvas/client/inspector/field.tsx +0 -263
  122. package/src/canvas/client/inspector/fields.css +0 -763
  123. package/src/canvas/client/inspector/section.tsx +0 -27
  124. package/src/canvas/client/inspector/size-field.tsx +0 -69
  125. package/src/canvas/client/inspector/token-select.tsx +0 -109
  126. package/src/canvas/client/inspector/values.ts +0 -297
  127. package/src/canvas/client/inspector.tsx +0 -692
  128. package/src/canvas/client/keys.ts +0 -71
  129. package/src/canvas/client/layers.tsx +0 -118
  130. package/src/canvas/client/main.tsx +0 -71
  131. package/src/canvas/client/pages-panel.tsx +0 -212
  132. package/src/canvas/client/panel-handle.tsx +0 -90
  133. package/src/canvas/client/selection.tsx +0 -199
  134. package/src/canvas/client/shell.css +0 -762
  135. package/src/canvas/client/state.ts +0 -792
  136. package/src/canvas/client/surface.tsx +0 -803
  137. package/src/canvas/client/theme-panel.tsx +0 -55
  138. package/src/canvas/client/tools.tsx +0 -153
  139. package/src/canvas/content.ts +0 -781
  140. package/src/canvas/edit.ts +0 -575
  141. package/src/canvas/editing.ts +0 -203
  142. package/src/canvas/fit.ts +0 -238
  143. package/src/canvas/global.d.ts +0 -15
  144. package/src/canvas/history.ts +0 -116
  145. package/src/canvas/index.html +0 -20
  146. package/src/canvas/server.ts +0 -766
  147. package/src/canvas/tokens.ts +0 -221
  148. package/src/commands/canvas.ts +0 -1097
  149. package/src/commands/demo.ts +0 -542
  150. package/src/commands/slides.ts +0 -184
  151. package/src/commands/teach.ts +0 -1401
  152. package/src/demo/beats.ts +0 -135
  153. package/src/demo/compile.ts +0 -350
  154. package/src/demo/container.ts +0 -219
  155. package/src/demo/cursors.ts +0 -55
  156. package/src/demo/drive.ts +0 -442
  157. package/src/demo/pointer.ts +0 -178
  158. package/src/demo/theme.ts +0 -112
  159. package/src/demo/timeline.ts +0 -64
  160. package/src/design/adapter.ts +0 -59
  161. package/src/design/base.css +0 -203
  162. package/src/design/brand-favicon.ts +0 -18
  163. package/src/design/neutral.ts +0 -127
  164. package/src/design/project-fonts.ts +0 -392
  165. package/src/slides/convert/chart.ts +0 -165
  166. package/src/slides/convert/deck.ts +0 -244
  167. package/src/slides/convert/export.ts +0 -567
  168. package/src/slides/convert/master.ts +0 -198
  169. package/src/slides/convert/shapes.ts +0 -804
  170. package/src/slides/convert/svg.ts +0 -38
  171. package/src/slides/convert/walk.ts +0 -671
  172. package/src/slides/decks.ts +0 -110
  173. package/src/slides/open.ts +0 -18
  174. package/src/slides/package/fonts.ts +0 -301
  175. package/src/slides/package/motion.ts +0 -316
  176. package/src/slides/package/test-face.ts +0 -113
  177. package/src/teach/browser/compile.ts +0 -44
  178. package/src/teach/browser/dismiss.ts +0 -76
  179. package/src/teach/browser/focus-line.ts +0 -19
  180. package/src/teach/browser/glossary-filter.ts +0 -44
  181. package/src/teach/browser/head.ts +0 -30
  182. package/src/teach/browser/quiz.ts +0 -19
  183. package/src/teach/browser/reload.ts +0 -9
  184. package/src/teach/browser/sidebar.ts +0 -233
  185. package/src/teach/browser/theme.ts +0 -18
  186. package/src/teach/components/code.tsx +0 -24
  187. package/src/teach/components/heading.tsx +0 -11
  188. package/src/teach/components/list.tsx +0 -17
  189. package/src/teach/components/paragraph.tsx +0 -29
  190. package/src/teach/components/refs.tsx +0 -28
  191. package/src/teach/cross-references.ts +0 -78
  192. package/src/teach/fonts.ts +0 -16
  193. package/src/teach/highlight.ts +0 -71
  194. package/src/teach/html/jsx-dev-runtime.ts +0 -13
  195. package/src/teach/html/jsx-runtime.ts +0 -113
  196. package/src/teach/html/raw.ts +0 -28
  197. package/src/teach/lesson.ts +0 -178
  198. package/src/teach/nav.ts +0 -1337
  199. package/src/teach/render.ts +0 -240
  200. package/src/teach/workspace.ts +0 -1164
  201. package/src/teach/write-if-changed.ts +0 -17
@@ -1,4 +1,4 @@
1
- import { existsSync, statSync } from 'node:fs'
1
+ import { existsSync } from 'node:fs'
2
2
  import { readdir, readFile } from 'node:fs/promises'
3
3
  import { join } from 'node:path'
4
4
  import { parseFrontmatter, readField } from '@/indexes/frontmatter'
@@ -12,17 +12,8 @@ import {
12
12
  recordDir as resolveRecordDir,
13
13
  recordDirs as resolveRecordDirs,
14
14
  } from '@/roots/record'
15
- import {
16
- TEACH_GLOSSARY,
17
- TEACH_MISSION,
18
- TEACH_RECORDS,
19
- TEACH_REFERENCE,
20
- TEACH_RESOURCES,
21
- TEACH_SUCCESS_HEADING,
22
- WORKSPACE_NAME,
23
- } from '@/teach/workspace'
24
15
 
25
- export const RECORD_KINDS = ['plans', 'groundwork', 'intake', 'teach'] as const
16
+ export const RECORD_KINDS = ['plans', 'groundwork', 'intake'] as const
26
17
 
27
18
  export type RecordKind = (typeof RECORD_KINDS)[number]
28
19
 
@@ -759,171 +750,9 @@ async function checkDump(dir: string, slug: string): Promise<Finding[]> {
759
750
  return [...findings, ...perCluster.flat()]
760
751
  }
761
752
 
762
- const NUMBERED_RECORD = /^\d{4}-[a-z0-9]+(-[a-z0-9]+)*\.md$/
763
- /**
764
- * A kebab slug that does not open with an ordinal. The lookahead rejects a
765
- * leading run of digits followed by a hyphen and nothing else, so a subject
766
- * whose own name starts with a digit still passes.
767
- */
768
- const REFERENCE_NAME = /^(?!\d+-)[a-z0-9]+(-[a-z0-9]+)*\.md$/
769
-
770
- /** The two fields every markdown file in a workspace carries. */
771
- async function checkTeachFile(
772
- dir: string,
773
- slug: string,
774
- file: string,
775
- subject: string,
776
- ): Promise<Finding[]> {
777
- const frontmatter = parseFrontmatter(await readFile(join(dir, file), 'utf8'))
778
-
779
- const missing = ['title', 'description'].filter(
780
- (field) => !readField(frontmatter, field),
781
- )
782
-
783
- if (missing.length === 0) return []
784
-
785
- return [
786
- finding(
787
- 'frontmatter-incomplete',
788
- slug,
789
- subject,
790
- `carries no ${missing.join(' and no ')}.`,
791
- ),
792
- ]
793
- }
794
-
795
- /**
796
- * One markdown subfolder of a workspace. `lessons/` and `assets/` are never
797
- * reached, because a lesson is generated markup carrying no frontmatter and a
798
- * walk over it would report every one as malformed.
799
- */
800
- async function checkTeachSubfolder(
801
- dir: string,
802
- slug: string,
803
- folder: string,
804
- name: RegExp,
805
- message: string,
806
- ): Promise<Finding[]> {
807
- const path = join(dir, folder)
808
-
809
- // Tested as a directory rather than for presence. Every other walk in this
810
- // module takes its path from `listFolders`, and this one is built from a
811
- // fixed name, so a workspace holding a plain file called `reference` would
812
- // reach `readdir` and take the whole run down with `ENOTDIR`.
813
- if (!statSync(path, { throwIfNoEntry: false })?.isDirectory()) return []
814
-
815
- const files = await listMarkdown(path)
816
-
817
- const malformed = files
818
- .filter((file) => !name.test(file))
819
- .map((file) =>
820
- finding('name-malformed', slug, `${folder}/${file}`, message),
821
- )
822
-
823
- const perFile = await Promise.all(
824
- files.map((file) => checkTeachFile(path, slug, file, `${folder}/${file}`)),
825
- )
826
-
827
- return [...malformed, ...perFile.flat()]
828
- }
829
-
830
- async function checkWorkspace(dir: string, slug: string): Promise<Finding[]> {
831
- const findings: Finding[] = []
832
-
833
- if (!WORKSPACE_NAME.test(slug)) {
834
- findings.push(
835
- finding(
836
- 'name-malformed',
837
- slug,
838
- slug,
839
- 'is not named NN-<topic> with a two-digit ordinal, so a listing sorts alphabetically rather than by when each workspace opened.',
840
- ),
841
- )
842
- }
843
-
844
- const files = await listMarkdown(dir)
845
-
846
- if (!files.includes(TEACH_MISSION)) {
847
- findings.push(
848
- finding(
849
- 'index-missing',
850
- slug,
851
- TEACH_MISSION,
852
- 'is absent, so the workspace states no subject and no success to finish against.',
853
- ),
854
- )
855
- }
856
-
857
- for (const required of [TEACH_RESOURCES, TEACH_GLOSSARY]) {
858
- if (!files.includes(required)) {
859
- findings.push(
860
- finding(
861
- 'section-missing',
862
- slug,
863
- required,
864
- 'is required and the workspace carries no such file.',
865
- ),
866
- )
867
- }
868
- }
869
-
870
- const perFile = await Promise.all(
871
- files.map((file) => checkTeachFile(dir, slug, file, file)),
872
- )
873
- findings.push(...perFile.flat())
874
-
875
- if (files.includes(TEACH_MISSION)) {
876
- const text = await readFile(join(dir, TEACH_MISSION), 'utf8')
877
-
878
- if (!hasOpeningDate(parseFrontmatter(text)?.raw ?? '')) {
879
- findings.push(
880
- finding(
881
- 'date-malformed',
882
- slug,
883
- TEACH_MISSION,
884
- 'carries no date field as YYYY-MM-DD, so the workspace states no opening day.',
885
- ),
886
- )
887
- }
888
-
889
- if (
890
- !linesOutsideFences(text).some(
891
- (line) => line.trim() === TEACH_SUCCESS_HEADING,
892
- )
893
- ) {
894
- findings.push(
895
- finding(
896
- 'section-missing',
897
- slug,
898
- TEACH_SUCCESS_HEADING,
899
- 'is absent, so the mission names no observable thing the learner will be able to do.',
900
- ),
901
- )
902
- }
903
- }
904
-
905
- return [
906
- ...findings,
907
- ...(await checkTeachSubfolder(
908
- dir,
909
- slug,
910
- TEACH_REFERENCE,
911
- REFERENCE_NAME,
912
- 'is not named <slug>.md as a kebab slug opening with no ordinal, so a page looked up rather than worked through implies an order no reader follows.',
913
- )),
914
- ...(await checkTeachSubfolder(
915
- dir,
916
- slug,
917
- TEACH_RECORDS,
918
- NUMBERED_RECORD,
919
- 'is not numbered NNNN-<slug>.md, so the records carry no read order.',
920
- )),
921
- ]
922
- }
923
-
924
753
  /**
925
- * The three kinds whose records are folders rather than files. Keyed by kind so
926
- * a seventh arrives as an entry here and the compiler names the walk it owes,
754
+ * The two kinds whose records are folders rather than files. Keyed by kind so
755
+ * a third arrives as an entry here and the compiler names the walk it owes,
927
756
  * where a ternary chain would silently fall through to whichever branch is last.
928
757
  */
929
758
  const FOLDER_CHECK: Readonly<
@@ -934,7 +763,6 @@ const FOLDER_CHECK: Readonly<
934
763
  > = {
935
764
  groundwork: checkTrack,
936
765
  intake: checkDump,
937
- teach: checkWorkspace,
938
766
  }
939
767
 
940
768
  /**
@@ -74,11 +74,6 @@ export const MASKS: readonly Mask[] = [
74
74
  reason:
75
75
  'the install stamp carries a `syncedAt` wall-clock time, so two provisions of one commit differ',
76
76
  },
77
- {
78
- arm: 'claude:read-frames/resolved',
79
- key: /^file:demos\/cold-open\.webm$/,
80
- reason: 'ffmpeg output carries an encoder timestamp, so two runs differ',
81
- },
82
77
  {
83
78
  key: /^log$/,
84
79
  replace: /sandbox-occupant-\d+|\(pid \d+\)/g,
@@ -15,8 +15,7 @@ import { isMarked } from '@/sweep/exempt-marker'
15
15
  * number resolves.
16
16
  *
17
17
  * That is also what keeps the check a prose pattern rather than a parser.
18
- * `src/design/base.css` and `src/design/tokens.ts` carry sixteen values shaped
19
- * `#191512`, which no width or boundary rule separates from a pull request
18
+ * `src/design/tokens.ts` carries twenty values shaped `#191512`, which no width or boundary rule separates from a pull request
20
19
  * number, so telling them apart would need the line's syntactic position.
21
20
  */
22
21
  export const SHIPPED_CORPORA = [
package/src/sync/check.ts CHANGED
@@ -2,7 +2,6 @@ import { existsSync } from 'node:fs'
2
2
  import { basename, join, sep } from 'node:path'
3
3
  import { execa } from 'execa'
4
4
  import { gitEnv } from '@/git/env'
5
- import { createDesignAdapter, DESIGN_INSTALL_DIR } from '@/design/adapter'
6
5
  import { createGovAdapter } from '@/gov/adapter'
7
6
  import { successorResolver } from '@/gov/renames'
8
7
  import { lookupRules } from '@/gov/install'
@@ -46,17 +45,11 @@ import { readSkew, type SkewReport } from '@/version/skew'
46
45
  * being one of these, because `src/tooling/` never calls `planSync`, so the
47
46
  * three lookups below have no entry to offer it. Standards and snippets left
48
47
  * the list with their install channels: nothing writes either corpus into a
49
- * target, so there is no installed copy to attribute.
50
- *
51
- * Design joins as a scanned domain rather than a stamp-only one because its
52
- * base file is attributed the same way a rule is, and its install marker is
53
- * what keeps it off a target that never asked for it: a project with no
54
- * `.claude/design/` is never scanned, so design values arrive on an install
55
- * rather than on the next sync.
48
+ * target, so there is no installed copy to attribute. Design left with its
49
+ * install verb for the same reason.
56
50
  */
57
51
  export const SCANNED_DOMAINS = [
58
52
  'governance',
59
- 'design',
60
53
  ] as const satisfies readonly StampDomain[]
61
54
 
62
55
  export type ScannedDomain = (typeof SCANNED_DOMAINS)[number]
@@ -68,17 +61,14 @@ export type ScannedDomain = (typeof SCANNED_DOMAINS)[number]
68
61
  */
69
62
  const SYNCED_SOURCES: Record<ScannedDomain, string> = {
70
63
  governance: 'governance/rules/',
71
- design: 'src/design/',
72
64
  }
73
65
 
74
66
  const ADAPTERS: Record<ScannedDomain, (root: string) => SyncAdapter> = {
75
67
  governance: createGovAdapter,
76
- design: createDesignAdapter,
77
68
  }
78
69
 
79
70
  const INSTALL_MARKERS: Record<ScannedDomain, readonly string[]> = {
80
71
  governance: ['.claude', 'rules', 'canon'],
81
- design: DESIGN_INSTALL_DIR.split(sep),
82
72
  }
83
73
 
84
74
  export interface StateCounts {
@@ -215,33 +205,18 @@ export interface CheckReport {
215
205
  readonly skew: SkewReport
216
206
  }
217
207
 
218
- /**
219
- * Scanned domains a target takes deliberately rather than by being managed.
220
- *
221
- * An absent one is a choice, so it is never reported as unstamped. Governance
222
- * wants the opposite reading, since a managed target without it has yet to
223
- * install what every project is expected to carry, and naming it is the only
224
- * place that shows up.
225
- */
226
- export const OPT_IN_DOMAINS: readonly ScannedDomain[] = ['design']
227
-
228
208
  /**
229
209
  * Scanned domains this target should have stamped and has not, which is the
230
210
  * one line a domain nobody installed ever appears on.
231
211
  *
232
212
  * An unmigrated domain is excluded because the relocation is its remedy rather
233
- * than a sync, and an opt-in domain the target does not hold is excluded
234
- * because there is nothing there to stamp and the sync it would name refuses.
213
+ * than a sync.
235
214
  */
236
215
  export function uncoveredDomains(report: CheckReport): ScannedDomain[] {
237
216
  const unmigrated = new Set(report.unmigrated.map((entry) => entry.domain))
238
- const installed = new Set(report.domains.map((entry) => entry.domain))
239
217
 
240
218
  return SCANNED_DOMAINS.filter(
241
- (domain) =>
242
- !report.covers.includes(domain) &&
243
- !unmigrated.has(domain) &&
244
- (!OPT_IN_DOMAINS.includes(domain) || installed.has(domain)),
219
+ (domain) => !report.covers.includes(domain) && !unmigrated.has(domain),
245
220
  )
246
221
  }
247
222
 
package/src/sync/stamp.ts CHANGED
@@ -10,17 +10,17 @@ import { compareVersions, parseVersion } from '@/version/compare'
10
10
  import { readInstalled } from '@/version/installed'
11
11
 
12
12
  /**
13
- * Domains the stamp can record. Governance and design both attribute file by
14
- * file through the sync engine. Tooling runs its own inject and manifest
15
- * machinery, so it records the stack chain it resolved instead and carries no
16
- * file hashes.
13
+ * Domains the stamp can record. Governance attributes file by file through
14
+ * the sync engine. Tooling runs its own inject and manifest machinery, so it
15
+ * records the stack chain it resolved instead and carries no file hashes.
17
16
  *
18
17
  * A stamp written before the standards or snippets install channel closed
19
- * still carries a `standards` or `snippets` record. `isStamp` ignores the key
18
+ * still carries a `standards` or `snippets` record, and one written before the
19
+ * design install retired carries a `design` record. `isStamp` ignores the key
20
20
  * and `sortDomains` drops it on the next write, so the target loses a domain
21
21
  * nothing can refresh rather than losing the whole file.
22
22
  */
23
- export const STAMP_DOMAINS = ['governance', 'design', 'tooling'] as const
23
+ export const STAMP_DOMAINS = ['governance', 'tooling'] as const
24
24
 
25
25
  export type StampDomain = (typeof STAMP_DOMAINS)[number]
26
26
 
@@ -1,9 +1,8 @@
1
1
  import { existsSync } from 'node:fs'
2
2
  import { join } from 'node:path'
3
- import { DESIGN_INSTALL_DIR } from '@/design/adapter'
4
3
  import { isDirectory } from '@/targets/validate'
5
4
 
6
- export const SYNC_DOMAINS = ['governance', 'design', 'claude'] as const
5
+ export const SYNC_DOMAINS = ['governance', 'claude'] as const
7
6
 
8
7
  export type SyncDomain = (typeof SYNC_DOMAINS)[number]
9
8
 
@@ -14,7 +13,6 @@ export interface DomainState {
14
13
 
15
14
  const DOMAIN_MARKERS: Record<SyncDomain, string> = {
16
15
  governance: join('.claude', 'rules'),
17
- design: DESIGN_INSTALL_DIR,
18
16
  claude: '.claude',
19
17
  }
20
18
 
@@ -26,7 +24,6 @@ const DOMAIN_MARKERS: Record<SyncDomain, string> = {
26
24
  */
27
25
  const DOMAIN_PATHS: Record<SyncDomain, readonly string[]> = {
28
26
  governance: ['.claude/rules/', '.claude/GOV.md'],
29
- design: ['.claude/design/'],
30
27
  claude: ['.gitignore'],
31
28
  }
32
29
 
@@ -16,7 +16,7 @@ The claude stack installs the Claude Code workflow into a project through `canon
16
16
 
17
17
  - `.canon/tasks/` holds one file per task plus a generated `index.md`, local scratch only, with `archive/` holding the tasks that shipped
18
18
  - `.canon/plans/` holds execution detail for multi-step tasks, and `canon tasks archive` moves a `feature-*.md` plan into `archive/` when the `post-merge` hook archives its task
19
- - `.canon/review/` holds one subfolder per producer, with `design/` for `canon design render`
19
+ - `.canon/review/` holds the reports a review or measurement skill writes, such as `ux-measure-<slug>.md`
20
20
  - `.canon/tmp/` is ephemeral scratch, safe to delete without loss
21
21
  - `.claude/worktrees/` is where `EnterWorktree` creates a linked worktree, which is why it stays under `.claude/`
22
22
 
@@ -1,6 +1,6 @@
1
1
  # Design
2
2
 
3
- Authoring guidance: the design standard. Values live in `<your stylesheet>`, such as `src/styles/tokens.css` or a Tailwind `@theme` block. A project with none writes them under `.claude/design/project/`, beside the installed base stylesheet.
3
+ Authoring guidance: the design standard. Values live in `<your stylesheet>`, such as `src/styles/tokens.css` or a Tailwind `@theme` block. A project with none writes a stylesheet of its own and names it here.
4
4
 
5
5
  ## Overview
6
6
 
@@ -1,35 +0,0 @@
1
- ---
2
- name: canvas-board
3
- description: Scope boundary for driving the local design canvas, and the line between a drafted frame and the design it feeds
4
- ---
5
-
6
- # Canvas board requirement
7
-
8
- ## Gap
9
-
10
- Without this skill, a session asked to draw on the canvas writes frames nothing is serving and reports them drawn. It trusts a passing capture, though a blank shell passes every capture while the operator sees an empty tab. From a linked worktree it writes a copy the canvas never serves, and it captures after every edit.
11
-
12
- It reads "this one" from the conversation rather than the operator's selection, or acts on a stale element pick and edits the wrong element. It restyles through a class while the operator's inspector edit sits inline, so the change never shows. A picked frame stays on the canvas as the only record, and the project's design record never learns it.
13
-
14
- ## Must
15
-
16
- - The canvas is serving and its live shell loads at the address the server reported before any frame is written, and a refusal or a blank shell is reported rather than drawn into
17
- - Pages and frames go through the verbs, so the session and the operator's drags share one writer, and a frame from a linked worktree takes the main-root route
18
- - The operator's selection decides what "this one" means, and a stale element pick reads as unknown
19
- - A frame's inline styles are read before a restyle, the run names which operator edits it kept and dropped, and capture runs once per finished frame or page
20
- - A picked frame is handed to `design-record` by page and frame, while a picked layout stays on the canvas for the plan that builds it
21
-
22
- ## Must not
23
-
24
- - Present the canvas as the project's source of truth for design, or promise it survives a lost checkout
25
- - Write frames when `canon` or its canvas command is missing
26
- - Name any third-party design tool, or the canvas's predecessor surface
27
- - Be reached only by the author typing its name, since a request to draw, select, or capture on the canvas should reach it by description
28
-
29
- ## Out of scope
30
-
31
- - Drafting candidates for a taste decision and taking the pick, which `pick-by-eye` owns
32
- - What the canvas verbs do internally, which the CLI's own contract owns
33
- - The content format a frame and a layout take, which the canvas standard owns
34
- - Landing a pick in the reference folder and carrying its values into the design document, which `design-record` owns
35
- - Tracing reference images into design values and backing up the canvas folder, which no skill owns
@@ -1,143 +0,0 @@
1
- ---
2
- name: canvas-board
3
- description: Drives the local design canvas, where pages hold HTML frames the operator sees, drags, selects, and edits in the browser. Starts the canvas when it is not running, confirms the live shell loads, makes pages and frames, edits a frame through its file, reads what the operator selected or restyled, captures finished frames, and hands a picked frame to `design-record`. Use when asked to "open the canvas", "put this on the canvas", "draw a frame for X", "change the one I selected", "what did I pick", "capture this frame", or "move that frame on the canvas". Do NOT use to draft candidates for a taste decision, which is `pick-by-eye`.
4
- metadata:
5
- family: decide
6
- ---
7
-
8
- # Canvas board
9
-
10
- Drive the canvas the operator is looking at. A page is a folder of HTML frames and a frame is one file, so a session draws by writing files and the operator sees each write land in the browser. The content format is fixed by `${CLAUDE_SKILL_DIR}/references/canvas.md`. Read it before writing a frame.
11
-
12
- Every verb below is `canon canvas`. Run `canon canvas --help` for the verbs and their refusals, and read `${CLAUDE_SKILL_DIR}/references/canvas.md` for the content format.
13
-
14
- ## Guards
15
-
16
- - If `canon` is not on PATH, stop: `❌ canon CLI not found.`
17
- - If `canon canvas --help` reports no such command, stop: `❌ The installed canon carries no canvas command. Update the CLI and re-run.`
18
- - Write no frame before the canvas is serving and the shell has loaded. A frame nobody can see is a draft reported as delivered.
19
- - Never present a frame as the project's design. The canvas is a drafting surface and the project's design record holds what was decided.
20
- - Never promise the canvas survives a lost checkout. Its folder is gitignored and nothing backs it up.
21
-
22
- ## Step 1: start the canvas and confirm the shell
23
-
24
- Read the pages first, which also proves the verb resolves:
25
-
26
- ```bash
27
- canon canvas list --json
28
- ```
29
-
30
- The record carries `content`, the folder every page lives in. It sits at the main worktree root whichever worktree the session runs in, so write through that path rather than one composed from the current directory.
31
-
32
- Start the server in the background unless the operator named an address that already answers, since it runs until interrupted. No verb reports a running server, and a second one is harmless, since it walks to the next free port and serves the same folder:
33
-
34
- ```bash
35
- canon canvas serve --json
36
- ```
37
-
38
- Read `url` off the record rather than composing one, since the server walks past a port already in use. Report the refusal and its `reason` when `ok` is false, and stop there.
39
-
40
- ### Check the shell
41
-
42
- Fetch the printed address and check the shell itself, not a capture, since every capture goes through the frame route alone and passes on a blank shell.
43
-
44
- - The body is non-empty and carries a `<script` tag. An empty `200` means the shell failed to build, usually a missing dependency, so report a blank shell and stop.
45
- - `<url>/api/pages` answers with JSON listing the pages.
46
-
47
- Hand the operator the address as a markdown link carrying the URL as its text and its target.
48
-
49
- ## Step 2: make pages and frames
50
-
51
- - Add a page for each decision being drafted: `canon canvas page add <page> --json`.
52
- - Follow the house style in `${CLAUDE_SKILL_DIR}/references/canvas.md` before drafting an explanation frame.
53
- - Add each frame with `canon canvas frame add <page> <frame> --width <px> --height <px> --json`, which writes the file and its box in one locked step
54
-
55
- Give each frame the width it should render at, since a capture and the canvas both render at it. The verb places the frame after the last one, and a position the operator asks for goes through `frame move`.
56
-
57
- ### Layout and naming
58
-
59
- Lay frames out through the verbs rather than by hand, since the operator's drags write the same `layout.json` under a lock.
60
-
61
- - Move a frame with `canon canvas frame move <page> <frame> --x <px> --y <px> --json`
62
- - Name pages and frames for what they hold, such as `pricing` and `pricing-dense`, so the operator can say which one by name.
63
- - After an insert or a resize, re-lay the page with `canon canvas arrange <page> --json`. It keeps dragged rows, sets the standard gap, and drops entries whose file is gone.
64
- - Run `arrange --check` to report without writing
65
-
66
- ## Step 3: edit a frame through its file
67
-
68
- Write the frame's whole document to the `path` the add verb reported. The open canvas reloads the frame when its file changes.
69
-
70
- - From a linked worktree, `Edit` and `Write` refuse a main-root path. Write the whole file through one plain `Bash` heredoc, the route `session-worktree` states, never a worktree copy.
71
- - Change one property with `canon canvas edit <page>/<frame> --element <index> --set <property>=<value> --json` when its set covers the change, since it leaves every other byte as it was
72
- - Use the project's custom properties for every value the token stylesheet defines. The `serve` record names the source under `tokens`, and `none` means frames render unstyled, so say so.
73
-
74
- ### Fit the box
75
-
76
- Fit the box to the content once the write lands, with `canon canvas fit <page>/<frame> --json`, and read each row's `wide` and `font`.
77
-
78
- - A `wide` frame needs its box widened, which fit never does
79
- - A `font` frame names a family the canvas does not serve, so set its text to a served face and fit again
80
- - A fit that grew a frame leaves the rows below overlapped until `canvas arrange` runs
81
-
82
- ### Mark the frame
83
-
84
- Mark the frame before the first write, so the operator sees which frame is being worked on before anything lands, and clear the mark once the edits to it are done:
85
-
86
- ```bash
87
- canon canvas editing <page>/<frame> --json
88
- canon canvas editing <page>/<frame> --done --json
89
- ```
90
-
91
- - A mark lapses after five minutes, so mark the frame again during a longer edit.
92
- - Clear it on every exit from the edit, including one that stopped on a refusal. Clearing a frame that holds no mark succeeds.
93
- - On `unknown command 'editing'`, the CLI predates the mark, so go on without it. Never run `canvas edit` in its place, since that verb writes the frame.
94
-
95
- ## Step 4: read what the operator pointed at
96
-
97
- Read the selection when the operator says "this one", "the selected frame", or "that button":
98
-
99
- ```bash
100
- canon canvas selection --json
101
- ```
102
-
103
- - `selection: null` means nothing is selected or the selected frame is gone. Ask which one rather than guessing.
104
- - An `element` carrying `stale: true` was picked against an earlier file, so its `index` may land elsewhere. Ask the operator to pick again rather than matching its `tag` and `text` by eye.
105
- - A fresh `element` gives the `index` that `canvas edit` takes.
106
-
107
- ## Step 5: respect the operator's inspector edits
108
-
109
- The operator restyles elements from the inspector, and each edit lands as inline `style` on the element. Inline style beats a class, so a class change made afterwards does not show.
110
-
111
- - Read the frame's inline `style` attributes before restyling anything in it.
112
- - Keep an inline property the operator set unless the change replaces it, and drop it, through `canvas edit` with an empty value or by rewriting the file, when it does.
113
- - Say which operator edits were kept and which were dropped.
114
-
115
- ## Step 6: capture finished work
116
-
117
- Capture once per finished frame or page, not after every edit. The live canvas is the working view, and a capture is the record handed on.
118
-
119
- - Capture a frame with `canon canvas capture <page>/<frame> --json`. Each PNG is the whole document, so its height follows the content rather than the box.
120
- - Capture a page with `canon canvas capture <page> --composite --json`, which shows each frame clipped to its box at its layout position
121
- - Read each `path` off the record rather than composing one.
122
-
123
- A capture refused because a font is not installed names the family it read off the frame's text. Set that text's `font-family` to a named family the machine has, never a generic keyword such as `sans-serif`, and capture again rather than reporting the frame as captured.
124
-
125
- ## Step 7: hand a picked frame on
126
-
127
- When the operator picks a frame, the decision leaves the canvas. Hand it to `canon:design-record`, which lands the frame in the project's reference folder and carries its values into the design document. Report it rather than proceeding silently when that skill does not resolve.
128
-
129
- - Name the page and frame the decision came from in the handoff and the reply, and leave the frame on the canvas until the operator clears it
130
- - Name the frame in the plan that builds a picked layout, since the layout reaches code through that plan
131
- - Never cite a frame path from a tracked file. The folder is gitignored, so the citation resolves on this machine alone.
132
-
133
- ## Output
134
-
135
- ```plaintext
136
- ✅ Canvas: <url the serve verb reported>
137
- Page: <page>, <n> frames
138
- Selected: <page>/<frame>, element <index>, or none, or stale
139
- Capture: <path of each capture written>
140
- Handed: <the frame handed to design-record, the plan that cites it, or not yet>
141
- ```
142
-
143
- Write the canvas line's URL as a markdown link. Omit the capture line when nothing was captured and the handed line when no frame was picked. Emit every path from the project root.