@erclx/canon 4.90.0 → 4.92.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 (44) hide show
  1. package/claude/.claude-plugin/plugin.json +1 -1
  2. package/claude/skills/design-taste/REQUIREMENT.md +78 -0
  3. package/claude/skills/design-taste/SKILL.md +148 -0
  4. package/claude/skills/design-taste/references/craft.md +58 -0
  5. package/claude/skills/design-taste/references/kinds.md +52 -0
  6. package/claude/skills/design-taste/references/preflight.md +77 -0
  7. package/claude/skills/design-taste/references/systems.md +49 -0
  8. package/claude/skills/design-taste/references/tells.md +101 -0
  9. package/claude/skills/design-taste/references/vocabulary.md +62 -0
  10. package/claude/skills/draft-and-pick/REQUIREMENT.md +8 -2
  11. package/claude/skills/draft-and-pick/SKILL.md +13 -5
  12. package/claude/skills/draft-ready/REQUIREMENT.md +53 -0
  13. package/claude/skills/draft-ready/SKILL.md +118 -0
  14. package/claude/skills/draft-ready/references/assembly.md +67 -0
  15. package/claude/skills/session-compact/REQUIREMENT.md +44 -0
  16. package/claude/skills/session-compact/SKILL.md +60 -0
  17. package/claude/skills/session-compact/references/handoff-note.md +68 -0
  18. package/claude/skills/session-map/REQUIREMENT.md +3 -2
  19. package/claude/skills/session-map/SKILL.md +2 -2
  20. package/claude/skills/session-resume/SKILL.md +3 -3
  21. package/docs/agents/teach.md +2 -2
  22. package/docs/workflow/ai-workflow.md +3 -1
  23. package/governance/rules/claude/563-ready.md +4 -0
  24. package/governance/rules/ui/410-a11y.md +9 -0
  25. package/governance/rules/ui/420-forms.md +9 -0
  26. package/governance/rules/ui/460-design-taste.md +29 -0
  27. package/governance/rules/ui/470-motion.md +23 -0
  28. package/governance/stacks/astro.toml +1 -1
  29. package/governance/stacks/react.toml +1 -1
  30. package/package.json +1 -1
  31. package/src/claude/cases/authoring.ts +5 -0
  32. package/src/claude/cases/misc.ts +10 -0
  33. package/src/commands/capture.ts +1 -0
  34. package/src/commands/design.ts +5 -0
  35. package/src/commands/feedback.ts +1 -0
  36. package/src/commands/slides.ts +3 -0
  37. package/src/commands/transcripts.ts +1 -0
  38. package/src/design/base.css +25 -19
  39. package/src/design/css.ts +4 -0
  40. package/src/design/fonts.ts +17 -0
  41. package/src/design/tokens.ts +45 -30
  42. package/src/teach/workspace.ts +1 -0
  43. package/standards/ready.md +2 -0
  44. package/standards/session.md +1 -0
@@ -7,11 +7,11 @@ description: Resumes a previous session by reading the handoff it left behind, t
7
7
 
8
8
  ## Step 1: read tracked work
9
9
 
10
- Resolve `.canon/plans/`, `.canon/memory/`, and `.canon/tasks/` at the main worktree root the way `session-worktree` does.
10
+ Resolve `.canon/plans/`, `.canon/memory/`, `.canon/compact/`, and `.canon/tasks/` at the main worktree root the way `session-worktree` does.
11
11
 
12
12
  Read these in parallel, skipping any that do not exist:
13
13
 
14
- - the newest `.canon/tasks/session-*.md`: the handoff a previous session wrote before a compaction, per `${CLAUDE_SKILL_DIR}/../../standards/session.md`. It leads the report rather than the reads.
14
+ - the newest `.canon/compact/*.md`, then the newest `.canon/tasks/session-*.md`: the handoff a previous session wrote before a compaction. The first is the note `session-compact` writes, and the second is the board-side map per `${CLAUDE_SKILL_DIR}/../../standards/session.md`, which stays readable. Whichever exists leads the report rather than the reads, and both lead it when both exist.
15
15
  - `.canon/tasks/index.md`: the folder catalog. Read this before any individual task file, and take the task list from it by dropping the `index`, `priority`, `backlog`, and `session-` rows, which are siblings rather than tasks.
16
16
  - `.canon/plans/*.md`: execution detail for in-progress tasks
17
17
  - `.canon/memory/index.md` and any memory files relevant to the top backlog item
@@ -48,4 +48,4 @@ Memory is updated only when a recorded fact becomes wrong, never on resume. A do
48
48
 
49
49
  This skill reads a handoff and never writes one. Reading and writing are two jobs, and the write happens at the close of a session rather than at its start.
50
50
 
51
- Name the standard when the session asks how to leave a handoff behind, and let the session follow it directly. Any session may write one, whatever role it holds, so nothing here routes the request to another skill. A role carrying sections of its own adds them over the core per that role's own runbook.
51
+ Route a session asking how to leave a handoff behind to `session-compact`, which writes a note under `.canon/compact/`. A session holding the orchestrator role takes `session-map` instead, which writes the board-side map the standard governs, and a role carrying sections of its own adds them over the core per that role's own runbook.
@@ -27,7 +27,7 @@ canon teach list regular-expressions --json
27
27
 
28
28
  With no topic it reports one line per workspace, carrying the lesson, learning-record, reference-page, and glossary-term counts, plus `next`, the ordinal an open would take. With one it reports the filenames behind each count and the glossary entries themselves.
29
29
 
30
- A folder not named `NN-<topic>` is still listed rather than dropped, since dropping it hides the one folder that needs a fix. Its ordinal reads as absent, it sorts last, and it moves no ordinal, so a malformed name cannot push a new workspace into a number a reader already cites.
30
+ A folder not named `NN-<topic>` is not a workspace, so the listing, `nav`, and every selector skip it and it moves no ordinal. That is what lets a teach root carry a sibling folder such as `evidence/` without it drawing as a stub row and receiving a contents page.
31
31
 
32
32
  The listing also names the required files a workspace does not carry, which is `MISSION.md`, `RESOURCES.md`, and `GLOSSARY.md`. That is a report rather than a refusal, because a workspace missing one is still a workspace a session can resume.
33
33
 
@@ -141,7 +141,7 @@ The order is drawn here rather than instructed, and that is the point of the ver
141
141
 
142
142
  ## Render
143
143
 
144
- `canon teach render` renders a lesson body's structural blocks to HTML, through the same components the fixture lesson under `examples/teach/00-fixture/` is generated from. It takes no topic and no `--root`, since the verb is a stateless transform reading nothing off a workspace on disk.
144
+ `canon teach render` renders a lesson body's structural blocks to HTML, through the components `src/teach/render-fixture.tsx` composes by hand. It takes no topic and no `--root`, since the verb is a stateless transform reading nothing off a workspace on disk.
145
145
 
146
146
  ```bash
147
147
  echo '[{"type":"heading","level":1,"text":"Compass bearings"}]' | canon teach render --json
@@ -280,7 +280,8 @@ This section is the corpus the coverage claim is measured against: every name `c
280
280
  | `canon:role-worker` | To assert the worker role for a cold session building one branch under one plan |
281
281
  | `canon:session-relay` | When a worker or planner owes its controller a message and holds no send tool |
282
282
  | `canon:session-resume` | At the start of a session, to pick up what a previous one left |
283
- | `canon:session-map` | At the close of a session, to write the handoff a compaction would destroy |
283
+ | `canon:session-compact` | Before a compaction, to write a plain session's handoff note outside the board |
284
+ | `canon:session-map` | At the close of an orchestrating session, to write its board-side handoff |
284
285
 
285
286
  ### Keep the project current with the toolkit
286
287
 
@@ -304,6 +305,7 @@ This section is the corpus the coverage claim is measured against: every name `c
304
305
  | `canon:create-snippet` | For a reusable prompt |
305
306
  | `canon:create-standard` | For a new authoring convention |
306
307
  | `canon:draft-docs` | For a brand-new `docs/*.md` page, drafted against `standards/docs.md` |
308
+ | `canon:draft-ready` | For finished files a worker should copy, written as a ready folder with its overview, thin plan, and task |
307
309
  | `canon:draft-context` | For a brand-new `canon/context/<domain>.md` entry, drafted against `standards/context.md` |
308
310
  | `canon:draft-wireframes` | For a brand-new `canon/wireframes/<surface>.md` file, drafted against `standards/wireframes.md` |
309
311
  | `canon:draft-figure` | For a hand-drawn figure inside an existing doc, drafted against `standards/figures.md` and rendered through Mermaid or freehand SVG |
@@ -6,6 +6,10 @@ paths:
6
6
 
7
7
  # Ready standards
8
8
 
9
+ ## Writing a ready folder
10
+
11
+ - Load the `canon:draft-ready` skill to write a ready folder, its overview, the thin plan, and the task row. It owns the procedure. Report it rather than proceeding silently when it does not resolve, since it ships with the plugin and this rule ships with the CLI.
12
+
9
13
  ## Authority
10
14
 
11
15
  - Follow the ready standard for the folder layout, ordinal naming, the overview frontmatter, the mirrored tree, and the thin-plan contract. It is the single source. Read it with `canon standards ready`.
@@ -24,6 +24,12 @@ paths:
24
24
  - Arrow keys: navigate within composite widgets. Do not let them exit the widget.
25
25
  - Escape: closes any overlay, popover, dialog, or dropdown. Cancels in-progress edits.
26
26
  - Do not introduce non-standard key bindings for common actions.
27
+ - Give every author-defined drag a single-pointer alternative and a keyboard path, such as move buttons or arrow-key reordering (WCAG 2.2 Dragging Movements, 2.5.7).
28
+
29
+ ## Pointer targets
30
+
31
+ - Size a web pointer target at least 24 by 24 CSS pixels, or space it so a 24 pixel circle centered on it touches no other target (WCAG 2.2 Target Size Minimum, 2.5.8).
32
+ - Size a native touch target at the platform figure, 44 points on iOS and 48 dp on Android. Do not apply the web figure to native, and do not apply the native figure as the web minimum.
27
33
 
28
34
  ## Composite widgets
29
35
 
@@ -34,6 +40,7 @@ paths:
34
40
 
35
41
  - Move focus into any overlay, popover, or dialog on open. Return focus to the trigger on close.
36
42
  - Never move focus on hover. Only on keyboard input or explicit pointer activation.
43
+ - Do not let a sticky header, footer, or overlay fully cover the focused control. Scroll it into view clear of them (WCAG 2.2 Focus Not Obscured, 2.4.11).
37
44
 
38
45
  ## ARIA semantics
39
46
 
@@ -46,3 +53,5 @@ paths:
46
53
  - Always provide `alt` on `<img>`. Use `alt=""` for decorative images.
47
54
  - Describe the content or function of the image, not its appearance. Do not prefix with "Image of" or "Photo of".
48
55
  - Alt text for functional images (icons, buttons) should describe the action, not the graphic.
56
+ - Give the visual boundary of a control and every graphic that carries meaning at least 3:1 contrast against adjacent colors (WCAG Non-text Contrast, 1.4.11).
57
+ - Never make color the only carrier of a state, error, or distinction. Pair it with text, an icon, or a pattern (WCAG Use of Color, 1.4.1).
@@ -26,6 +26,15 @@ paths:
26
26
  - Show the error directly under the field that caused it, not under an unrelated field.
27
27
  - When a change to field A causes a conflict involving field B, show the error under field A (the field the user edited), not under field B.
28
28
 
29
+ ## Submission errors
30
+
31
+ - When a failed submission carries several errors, move focus to a summary that links to each failing field. Keep the inline error beside each field.
32
+
33
+ ## Authentication and re-entry
34
+
35
+ - Let a password manager fill sign-in fields and accept a pasted value. Do not block paste or autofill on a password or code field (WCAG 2.2 Accessible Authentication, 3.3.8).
36
+ - Do not ask for information the user already gave earlier in the same flow. Prefill it or offer it for selection (WCAG 2.2 Redundant Entry, 3.3.7).
37
+
29
38
  ## Input sanitization
30
39
 
31
40
  - Trim leading and trailing whitespace from text inputs on blur or submit, not on every keystroke.
@@ -0,0 +1,29 @@
1
+ ---
2
+ description: Route edits that decide how a surface looks to the design-taste skill for the layer ordering, the coherence locks, and the defaults a model reaches for
3
+ paths:
4
+ - '**/*.css'
5
+ - '**/*.scss'
6
+ - 'canon/DESIGN.md'
7
+ ---
8
+
9
+ # Design taste standards
10
+
11
+ ## Authority
12
+
13
+ - Load the `canon:design-taste` skill for which design decision settles first, what must stay constant across a surface, and the defaults to reach past. It is the single source for all three.
14
+ - Load it before drafting, not after revising. A revision recovers a color and never recovers the composition a draft already settled.
15
+ - Report it rather than proceeding silently when the skill does not resolve. It ships with the plugin and this rule ships with the CLI, so a project that installed governance alone does not have it.
16
+ - Do not work the layer ordering or the coherence locks from memory.
17
+
18
+ ## When this fires and when it does not
19
+
20
+ - This rule scopes to the files where an undecided visual choice lands, which is the stylesheet and the design document. Building a surface somebody already decided is implementation rather than design, and the rules below carry what implementation owes.
21
+ - Load the skill by name when a component edit makes a visual choice nobody has taken yet, such as a new section's shape, a spacing relationship, or a radius. The glob cannot see that intent and no glob can.
22
+ - Do not load it to ship a decided design. A worker wiring a picked composition pays the read and gets nothing back.
23
+
24
+ ## Boundaries
25
+
26
+ - Accessibility, keyboard interaction and ARIA are a separate topic. `410-a11y` routes them, on every rendered path.
27
+ - States, empty and loading coverage, and destructive confirmation are a separate topic. `430-ux-completeness` routes them.
28
+ - Rendered copy casing, button labels and error wording are a separate topic. `400-ui` routes them.
29
+ - The floor those three carry is not restated in the skill, and it is not traded against a preference where the two collide.
@@ -0,0 +1,23 @@
1
+ ---
2
+ description: Moving content controls and the reduced-motion preference for rendered UI
3
+ paths:
4
+ - '**/*.tsx'
5
+ - '**/*.jsx'
6
+ - '**/*.astro'
7
+ - '**/*.html'
8
+ - '**/*.css'
9
+ ---
10
+
11
+ # Motion standards
12
+
13
+ ## Moving content
14
+
15
+ - Give any content that moves, blinks, or scrolls automatically for more than five seconds a control to pause, stop, or hide it (WCAG Pause, Stop, Hide, 2.2.2).
16
+ - Halt an auto-advancing carousel or ticker while it holds keyboard focus or pointer hover.
17
+ - Do not flash content more than three times in any one second.
18
+
19
+ ## Reduced motion
20
+
21
+ - Honor `prefers-reduced-motion: reduce` for every animation and transition that conveys no state. Remove it or replace it with an instant change.
22
+ - Keep essential state changes visible under reduced motion, using a fade or a static indicator instead of movement.
23
+ - Do not start video or animated backgrounds on their own when the preference is set.
@@ -1,2 +1,2 @@
1
1
  extends = "node"
2
- rules = ["210-astro", "350-security-web", "400-ui", "410-a11y", "430-ux-completeness", "440-surface-capture", "450-link-behavior"]
2
+ rules = ["210-astro", "350-security-web", "400-ui", "410-a11y", "420-forms", "430-ux-completeness", "440-surface-capture", "450-link-behavior", "460-design-taste", "470-motion"]
@@ -1,2 +1,2 @@
1
1
  extends = "node"
2
- rules = ["200-react", "230-nextjs", "250-tailwind", "300-testing-ts", "305-e2e-reliability", "306-test-scope", "310-zod", "350-security-web", "400-ui", "410-a11y", "420-forms", "430-ux-completeness", "440-surface-capture", "450-link-behavior"]
2
+ rules = ["200-react", "230-nextjs", "250-tailwind", "300-testing-ts", "305-e2e-reliability", "306-test-scope", "310-zod", "350-security-web", "400-ui", "410-a11y", "420-forms", "430-ux-completeness", "440-surface-capture", "450-link-behavior", "460-design-taste", "470-motion"]
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@erclx/canon",
3
3
  "type": "module",
4
- "version": "4.90.0",
4
+ "version": "4.92.0",
5
5
  "description": "Infrastructure and quality tooling for developer workflows",
6
6
  "license": "MIT",
7
7
  "bin": {
@@ -42,6 +42,11 @@ export const AUTHORING_CASES: readonly SkillCase[] = [
42
42
  'Write a brand-new docs page for the capture command, nothing under docs/ covers it yet.',
43
43
  expect: 'draft-docs',
44
44
  },
45
+ {
46
+ prompt:
47
+ 'I already wrote the finished skill files, package them as a ready folder with a plan and a task for a worker to copy.',
48
+ expect: 'draft-ready',
49
+ },
45
50
  {
46
51
  prompt: 'This project has no README.md at all, write one from scratch.',
47
52
  expect: 'draft-readme',
@@ -12,6 +12,11 @@ export const MISC_CASES: readonly SkillCase[] = [
12
12
  'Show me a few different treatments for this callout so I can pick one by looking.',
13
13
  expect: 'draft-and-pick',
14
14
  },
15
+ {
16
+ prompt:
17
+ 'Which layer of this landing page should I settle first, and what has to stay constant across it?',
18
+ expect: 'design-taste',
19
+ },
15
20
  {
16
21
  prompt:
17
22
  'This project has no logo yet. Draft one and give me a social card to go with it.',
@@ -29,6 +34,11 @@ export const MISC_CASES: readonly SkillCase[] = [
29
34
  {
30
35
  prompt:
31
36
  "We're about to hit the context limit, write the handoff before we lose state.",
37
+ expect: 'session-compact',
38
+ },
39
+ {
40
+ prompt:
41
+ 'I am the orchestrating session and about to compact, so write the board handoff as a session map on the task board.',
32
42
  expect: 'session-map',
33
43
  },
34
44
  {
@@ -42,6 +42,7 @@ type CaptureResult = Awaited<ReturnType<Renderer['captureSources']>>[number]
42
42
  export function register(program: Command): void {
43
43
  program
44
44
  .command('capture')
45
+ .helpOption('-h, --help', 'Show this help message')
45
46
  .description('Render HTML capture sources to PNG')
46
47
  .argument(
47
48
  '[source]',
@@ -26,12 +26,14 @@ import { mainWorktreeRoot } from '@/worktree'
26
26
  export function register(program: Command): void {
27
27
  const design = program
28
28
  .command('design')
29
+ .helpOption('-h, --help', 'Show this help message')
29
30
  .description(
30
31
  'Design system commands (regen, css, render, board, install, sync)',
31
32
  )
32
33
 
33
34
  design
34
35
  .command('regen')
36
+ .helpOption('-h, --help', 'Show this help message')
35
37
  .description(
36
38
  'Rewrite canon/DESIGN.md and the base stylesheet from src/design/tokens.ts',
37
39
  )
@@ -84,6 +86,7 @@ export function register(program: Command): void {
84
86
 
85
87
  design
86
88
  .command('css')
89
+ .helpOption('-h, --help', 'Show this help message')
87
90
  .description('Emit the design tokens and components as CSS on stdout')
88
91
  .option(
89
92
  '--no-components',
@@ -104,6 +107,7 @@ export function register(program: Command): void {
104
107
 
105
108
  design
106
109
  .command('render')
110
+ .helpOption('-h, --help', 'Show this help message')
107
111
  .description('Render DESIGN.md tokens to HTML and CSS preview')
108
112
  .option(
109
113
  '-s, --source <path>',
@@ -137,6 +141,7 @@ export function register(program: Command): void {
137
141
 
138
142
  design
139
143
  .command('board')
144
+ .helpOption('-h, --help', 'Show this help message')
140
145
  .description(
141
146
  'Generate the design board, an index over a project’s design surfaces',
142
147
  )
@@ -71,6 +71,7 @@ function writeLocal(body: string, mismatch: string | undefined): string {
71
71
  export function register(program: Command): void {
72
72
  program
73
73
  .command('feedback')
74
+ .helpOption('-h, --help', 'Show this help message')
74
75
  .description(
75
76
  'Write toolkit feedback from stdin to .canon/feedback/, or open a GitHub issue with --github',
76
77
  )
@@ -11,10 +11,12 @@ import { intro, outro, palette } from '@/ui'
11
11
  export function register(program: Command): void {
12
12
  const slides = program
13
13
  .command('slides')
14
+ .helpOption('-h, --help', 'Show this help message')
14
15
  .description('Slide deck commands (render, list)')
15
16
 
16
17
  slides
17
18
  .command('render')
19
+ .helpOption('-h, --help', 'Show this help message')
18
20
  .description('Render a SLIDES.md source into a PowerPoint deck')
19
21
  .option('-s, --source <path>', 'Source SLIDES.md path', '.claude/SLIDES.md')
20
22
  .option(
@@ -82,6 +84,7 @@ export function register(program: Command): void {
82
84
 
83
85
  slides
84
86
  .command('list')
87
+ .helpOption('-h, --help', 'Show this help message')
85
88
  .description('List the available slide layouts')
86
89
  .option('--json', 'Output the layout catalog as JSON')
87
90
  .action((opts: { json?: boolean }) => {
@@ -25,6 +25,7 @@ export async function resolveOutDir(opts: TranscriptOptions): Promise<string> {
25
25
  export function register(program: Command): void {
26
26
  program
27
27
  .command('transcripts <url>')
28
+ .helpOption('-h, --help', 'Show this help message')
28
29
  .description('Fetch a YouTube transcript with metadata frontmatter')
29
30
  .option(
30
31
  '-o, --out <path>',
@@ -24,31 +24,37 @@
24
24
  --color-light-accent: #ad4a4b;
25
25
  --color-light-success: #2d6b22;
26
26
  --color-light-border: #d5d4d1;
27
- --space-xs: 6px;
28
- --space-sm: 12px;
29
- --space-md: 18px;
30
- --space-lg: 24px;
31
- --space-xl: 30px;
32
- --space-frame-top: 44px;
33
- --space-frame-inline: 52px;
34
- --space-frame-bottom: 38px;
35
- --type-display-family: Noto Sans Mono, DejaVu Sans Mono, monospace;
36
- --type-display-size: 34px;
27
+ --space-xs: 0.25rem;
28
+ --space-sm: 0.5rem;
29
+ --space-md: 0.875rem;
30
+ --space-lg: 1.5rem;
31
+ --space-xl: 2.5rem;
32
+ --space-2xl: 4rem;
33
+ --space-3xl: 6rem;
34
+ --t0: 3.175rem;
35
+ --t1: 2.375rem;
36
+ --t2: 1.375rem;
37
+ --t3: 1.125rem;
38
+ --t4: 0.9375rem;
39
+ --t5: 0.8125rem;
40
+ --t6: 0.6875rem;
41
+ --type-display-family: Geist Variable, DejaVu Sans, sans-serif;
42
+ --type-display-size: 2.375rem;
37
43
  --type-display-lh: 1.3;
38
- --type-page-display-family: Noto Sans, DejaVu Sans, sans-serif;
39
- --type-page-display-size: 52px;
44
+ --type-page-display-family: Geist Variable, DejaVu Sans, sans-serif;
45
+ --type-page-display-size: 3.175rem;
40
46
  --type-page-display-lh: 1.1;
41
- --type-heading-family: Noto Sans Mono, DejaVu Sans Mono, monospace;
42
- --type-heading-size: 19px;
47
+ --type-heading-family: Geist Variable, DejaVu Sans, sans-serif;
48
+ --type-heading-size: 1.375rem;
43
49
  --type-heading-lh: 1.3;
44
- --type-body-family: Noto Sans Mono, DejaVu Sans Mono, monospace;
45
- --type-body-size: 16px;
50
+ --type-body-family: Geist Variable, DejaVu Sans, sans-serif;
51
+ --type-body-size: 0.9375rem;
46
52
  --type-body-lh: 1.65;
47
- --type-label-family: Noto Sans Mono, DejaVu Sans Mono, monospace;
48
- --type-label-size: 12px;
53
+ --type-label-family: Geist Variable, DejaVu Sans, sans-serif;
54
+ --type-label-size: 0.8125rem;
49
55
  --type-label-lh: 1.45;
50
56
  --type-code-family: Noto Sans Mono, DejaVu Sans Mono, monospace;
51
- --type-code-size: 14.5px;
57
+ --type-code-size: 0.8125rem;
52
58
  --type-code-lh: 1.3;
53
59
  --radius-frame: 12px;
54
60
  --radius-panel: 10px;
package/src/design/css.ts CHANGED
@@ -77,6 +77,10 @@ function tokenProperties(tokens: DesignTokens): string[] {
77
77
  lines.push(` --space-${slug(step.step)}: ${step.value};`)
78
78
  }
79
79
 
80
+ for (const step of tokens.typeScale) {
81
+ lines.push(` --${slug(step.step)}: ${step.size};`)
82
+ }
83
+
80
84
  for (const role of tokens.typography) {
81
85
  lines.push(` --type-${slug(role.role)}-family: ${role.family};`)
82
86
  lines.push(` --type-${slug(role.role)}-size: ${role.size};`)