canon-ds 0.1.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 (153) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +153 -0
  3. package/THIRD_PARTY_NOTICES.md +55 -0
  4. package/bin/canon.js +6 -0
  5. package/lib/build-manifest.mjs +54 -0
  6. package/lib/build.mjs +69 -0
  7. package/lib/cli.mjs +273 -0
  8. package/lib/color.js +160 -0
  9. package/lib/components/_shared.mjs +93 -0
  10. package/lib/components/accordion.mjs +136 -0
  11. package/lib/components/account-card.mjs +166 -0
  12. package/lib/components/activity-feed.mjs +176 -0
  13. package/lib/components/activity-gauge.mjs +98 -0
  14. package/lib/components/agent-presence.mjs +156 -0
  15. package/lib/components/alert.mjs +154 -0
  16. package/lib/components/app-store-button.mjs +108 -0
  17. package/lib/components/avatar-group.mjs +128 -0
  18. package/lib/components/avatar.mjs +225 -0
  19. package/lib/components/badge-group.mjs +55 -0
  20. package/lib/components/badge.mjs +174 -0
  21. package/lib/components/banner.mjs +116 -0
  22. package/lib/components/breadcrumb.mjs +170 -0
  23. package/lib/components/button-group.mjs +114 -0
  24. package/lib/components/button.mjs +197 -0
  25. package/lib/components/card-header.mjs +115 -0
  26. package/lib/components/card.mjs +106 -0
  27. package/lib/components/carousel.mjs +182 -0
  28. package/lib/components/chart-frame.mjs +101 -0
  29. package/lib/components/checkbox.mjs +163 -0
  30. package/lib/components/close-button.mjs +49 -0
  31. package/lib/components/code.mjs +123 -0
  32. package/lib/components/color-picker.mjs +122 -0
  33. package/lib/components/combobox.mjs +210 -0
  34. package/lib/components/command-palette.mjs +226 -0
  35. package/lib/components/content-divider.mjs +104 -0
  36. package/lib/components/counter.mjs +86 -0
  37. package/lib/components/credit-card.mjs +126 -0
  38. package/lib/components/date-picker.mjs +202 -0
  39. package/lib/components/description-list.mjs +88 -0
  40. package/lib/components/dialog.mjs +140 -0
  41. package/lib/components/divider.mjs +115 -0
  42. package/lib/components/drawer.mjs +150 -0
  43. package/lib/components/empty-state.mjs +112 -0
  44. package/lib/components/featured-card.mjs +124 -0
  45. package/lib/components/featured-icon.mjs +67 -0
  46. package/lib/components/field.mjs +122 -0
  47. package/lib/components/file-dropzone.mjs +166 -0
  48. package/lib/components/filter-bar.mjs +179 -0
  49. package/lib/components/header-navigation.mjs +94 -0
  50. package/lib/components/icon-button.mjs +179 -0
  51. package/lib/components/index.mjs +52 -0
  52. package/lib/components/inline-cta.mjs +139 -0
  53. package/lib/components/input-group.mjs +164 -0
  54. package/lib/components/input.mjs +197 -0
  55. package/lib/components/kanban.mjs +179 -0
  56. package/lib/components/kbd.mjs +70 -0
  57. package/lib/components/kicker.mjs +79 -0
  58. package/lib/components/link.mjs +108 -0
  59. package/lib/components/list.mjs +117 -0
  60. package/lib/components/media-frame.mjs +119 -0
  61. package/lib/components/menu.mjs +190 -0
  62. package/lib/components/message.mjs +249 -0
  63. package/lib/components/metric-group.mjs +210 -0
  64. package/lib/components/mobile-header.mjs +173 -0
  65. package/lib/components/multi-select.mjs +218 -0
  66. package/lib/components/notification.mjs +175 -0
  67. package/lib/components/number-input.mjs +149 -0
  68. package/lib/components/page-header.mjs +68 -0
  69. package/lib/components/pagination.mjs +236 -0
  70. package/lib/components/pin-input.mjs +120 -0
  71. package/lib/components/popover.mjs +126 -0
  72. package/lib/components/progress-circle.mjs +115 -0
  73. package/lib/components/progress.mjs +93 -0
  74. package/lib/components/prose.mjs +96 -0
  75. package/lib/components/radio.mjs +160 -0
  76. package/lib/components/rating.mjs +93 -0
  77. package/lib/components/rich-text-editor.mjs +178 -0
  78. package/lib/components/section-header.mjs +62 -0
  79. package/lib/components/segmented-control.mjs +121 -0
  80. package/lib/components/select.mjs +163 -0
  81. package/lib/components/sidebar-nav.mjs +172 -0
  82. package/lib/components/sidebar.mjs +124 -0
  83. package/lib/components/skeleton.mjs +66 -0
  84. package/lib/components/slider.mjs +122 -0
  85. package/lib/components/social-button.mjs +119 -0
  86. package/lib/components/spinner.mjs +69 -0
  87. package/lib/components/stat.mjs +120 -0
  88. package/lib/components/stepper.mjs +228 -0
  89. package/lib/components/switch.mjs +159 -0
  90. package/lib/components/table.mjs +212 -0
  91. package/lib/components/tabs.mjs +207 -0
  92. package/lib/components/tag.mjs +196 -0
  93. package/lib/components/tags-input.mjs +146 -0
  94. package/lib/components/textarea.mjs +117 -0
  95. package/lib/components/timeline.mjs +135 -0
  96. package/lib/components/toast.mjs +115 -0
  97. package/lib/components/tooltip.mjs +86 -0
  98. package/lib/components/topbar.mjs +207 -0
  99. package/lib/components/tree-view.mjs +156 -0
  100. package/lib/components/video-player.mjs +189 -0
  101. package/lib/connect.mjs +110 -0
  102. package/lib/design-files.mjs +175 -0
  103. package/lib/distribution.mjs +109 -0
  104. package/lib/editor.js +940 -0
  105. package/lib/engine.js +339 -0
  106. package/lib/generators/agents.mjs +68 -0
  107. package/lib/generators/connection.md +58 -0
  108. package/lib/generators/css.mjs +2 -0
  109. package/lib/generators/designmd.mjs +324 -0
  110. package/lib/generators/documentation.css +82 -0
  111. package/lib/generators/documentation.html +81 -0
  112. package/lib/generators/documentation.js +96 -0
  113. package/lib/generators/documentation.mjs +35 -0
  114. package/lib/generators/dtcg.mjs +60 -0
  115. package/lib/generators/preview.css +279 -0
  116. package/lib/generators/preview.mjs +225 -0
  117. package/lib/generators/react.mjs +162 -0
  118. package/lib/generators/tailwind.mjs +114 -0
  119. package/lib/install.mjs +108 -0
  120. package/lib/lint.mjs +538 -0
  121. package/lib/mcp.mjs +268 -0
  122. package/lib/open.mjs +7 -0
  123. package/lib/patterns/_app.mjs +56 -0
  124. package/lib/patterns/app-shell.mjs +25 -0
  125. package/lib/patterns/audit-log.mjs +37 -0
  126. package/lib/patterns/auth-pages.mjs +44 -0
  127. package/lib/patterns/billing-page.mjs +39 -0
  128. package/lib/patterns/calendar-page.mjs +55 -0
  129. package/lib/patterns/dashboard-page.mjs +28 -0
  130. package/lib/patterns/files-page.mjs +30 -0
  131. package/lib/patterns/first-run.mjs +31 -0
  132. package/lib/patterns/form-layout.mjs +24 -0
  133. package/lib/patterns/inbox-page.mjs +58 -0
  134. package/lib/patterns/index.mjs +29 -0
  135. package/lib/patterns/list-detail-page.mjs +24 -0
  136. package/lib/patterns/mobile-app.mjs +46 -0
  137. package/lib/patterns/modal-flows.mjs +32 -0
  138. package/lib/patterns/onboarding-page.mjs +42 -0
  139. package/lib/patterns/permissions-page.mjs +38 -0
  140. package/lib/patterns/profile-page.mjs +32 -0
  141. package/lib/patterns/settings-page.mjs +24 -0
  142. package/lib/project.mjs +28 -0
  143. package/lib/serve.mjs +279 -0
  144. package/lib/system.mjs +210 -0
  145. package/lib/tokens/base.js +251 -0
  146. package/lib/tokens/canon-preset.mjs +67 -0
  147. package/lib/tokens/index.mjs +1 -0
  148. package/lib/tokens/legacy-canon.json +559 -0
  149. package/lib/tokens/presets.mjs +161 -0
  150. package/lib/tokens/resolve.mjs +1 -0
  151. package/lib/types.mjs +4 -0
  152. package/lib/version.mjs +2 -0
  153. package/package.json +54 -0
package/lib/engine.js ADDED
@@ -0,0 +1,339 @@
1
+ // The canon engine: pure functions shared by the Node build and the browser
2
+ // editor. No Node APIs here. Everything that turns a System (tokens +
3
+ // component specs) into CSS lives in this file so the two never disagree.
4
+
5
+ export const REF_RE = /\{([a-zA-Z0-9_.-]+)\}/g;
6
+
7
+ /**
8
+ * Index every token as a resolved record keyed by its short reference
9
+ * ("space.3", "color.bg-canvas", "neutral.500", "font.size.sm", …).
10
+ * @returns {Map<string, {ref:string, cssVar:string, light:string, dark:string, themed:boolean, group:string, path:string, description?:string}>}
11
+ */
12
+ export function indexTokens(tokens, prefix) {
13
+ const idx = new Map();
14
+ const v = (path) => `--${prefix}-${path.replace(/\./g, '-')}`;
15
+ const add = (ref, group, path, light, dark, themed, description, cssVar) =>
16
+ idx.set(ref, { ref: `{${ref}}`, cssVar: cssVar ?? v(`${group}.${path}`), light, dark, themed, group, path, description });
17
+
18
+ for (const [name, val] of Object.entries(tokens.color.primitive)) {
19
+ if (typeof val === 'string') add(name, 'color', name, val, val, false, undefined, v(name));
20
+ else for (const [step, hex] of Object.entries(val)) add(`${name}.${step}`, 'color', `${name}.${step}`, hex, hex, false, undefined, v(`${name}.${step}`));
21
+ }
22
+ for (const [name, val] of Object.entries(tokens.color.semantic)) add(`color.${name}`, 'color', name, val.light, val.dark, true, val.description);
23
+ const FONT_PATH = { family: 'family', size: 'size', weight: 'weight', lineHeight: 'leading', letterSpacing: 'tracking', control: 'control' };
24
+ for (const [sub, map] of Object.entries(tokens.font)) {
25
+ for (const [k, val] of Object.entries(map)) add(`font.${sub}.${k}`, 'font', `${FONT_PATH[sub] ?? sub}.${k}`, val, val, false);
26
+ }
27
+ for (const [k, val] of Object.entries(tokens.space)) add(`space.${k}`, 'space', k, val, val, false);
28
+ for (const [k, val] of Object.entries(tokens.radius)) add(`radius.${k}`, 'radius', k, val, val, false);
29
+ for (const [k, val] of Object.entries(tokens.border.width)) add(`border.width.${k}`, 'border', `width.${k}`, val, val, false);
30
+ for (const [k, val] of Object.entries(tokens.shadow)) add(`shadow.${k}`, 'shadow', k, val.light, val.dark, true, val.description);
31
+ for (const [sub, map] of Object.entries(tokens.size)) {
32
+ for (const [k, val] of Object.entries(map)) add(`size.${sub}.${k}`, 'size', `${sub}.${k}`, val, val, false);
33
+ }
34
+ for (const [k, val] of Object.entries(tokens.z)) add(`z.${k}`, 'z', k, val, val, false);
35
+ for (const [k, val] of Object.entries(tokens.motion.duration)) add(`motion.duration.${k}`, 'motion', `duration.${k}`, val, val, false);
36
+ for (const [k, val] of Object.entries(tokens.motion.easing)) add(`motion.easing.${k}`, 'motion', `easing.${k}`, val, val, false);
37
+ for (const [k, val] of Object.entries(tokens.breakpoint)) add(`breakpoint.${k}`, 'breakpoint', k, val, val, false);
38
+ for (const [k, val] of Object.entries(tokens.opacity)) add(`opacity.${k}`, 'opacity', k, val, val, false);
39
+ for (const [k, ts] of Object.entries(tokens.type)) {
40
+ add(`type.${k}.family`, 'type', `${k}.family`, ts.family, ts.family, false);
41
+ add(`type.${k}.size`, 'type', `${k}.size`, ts.size, ts.size, false);
42
+ add(`type.${k}.weight`, 'type', `${k}.weight`, ts.weight, ts.weight, false);
43
+ add(`type.${k}.lineHeight`, 'type', `${k}.leading`, ts.lineHeight, ts.lineHeight, false);
44
+ add(`type.${k}.letterSpacing`, 'type', `${k}.tracking`, ts.letterSpacing, ts.letterSpacing, false);
45
+ add(`type.${k}.transform`, 'type', `${k}.transform`, ts.transform ?? 'none', ts.transform ?? 'none', false);
46
+ }
47
+ const resolveLiteral = (value, theme, depth = 0) => {
48
+ if (depth > 12) throw new Error(`Token reference cycle in "${value}"`);
49
+ return value.replace(REF_RE, (_m, ref) => {
50
+ const t = idx.get(ref);
51
+ if (!t) throw new Error(`Unknown token reference {${ref}}`);
52
+ return resolveLiteral(theme === 'light' ? t.light : t.dark, theme, depth + 1);
53
+ });
54
+ };
55
+ for (const t of idx.values()) {
56
+ const l = resolveLiteral(t.light, 'light');
57
+ const d = resolveLiteral(t.dark, 'dark');
58
+ t.themed = t.themed || l !== d;
59
+ t.light = l; t.dark = d;
60
+ }
61
+ return idx;
62
+ }
63
+
64
+ /** Replace {refs} in a CSS value with var(--prefix-…). Unknown refs throw. */
65
+ export function toCssVars(value, idx) {
66
+ return value.replace(REF_RE, (_m, ref) => {
67
+ const t = idx.get(ref);
68
+ if (!t) throw new Error(`Unknown token reference {${ref}}`);
69
+ // Breakpoints are used inside @media/@container, where var() is not allowed.
70
+ if (t.group === 'breakpoint') return t.light;
71
+ return `var(${t.cssVar})`;
72
+ });
73
+ }
74
+
75
+ /** Replace {refs} with literal values for a theme. */
76
+ export function toLiteral(value, idx, theme = 'light') {
77
+ return value.replace(REF_RE, (_m, ref) => {
78
+ const t = idx.get(ref);
79
+ if (!t) throw new Error(`Unknown token reference {${ref}}`);
80
+ return theme === 'light' ? t.light : t.dark;
81
+ });
82
+ }
83
+
84
+ export function isThemed(value, idx) {
85
+ let themed = false;
86
+ value.replace(REF_RE, (_m, ref) => { if (idx.get(ref)?.themed) themed = true; return ''; });
87
+ return themed;
88
+ }
89
+
90
+ export function refsIn(value) {
91
+ const out = [];
92
+ value.replace(REF_RE, (_m, ref) => { out.push(ref); return ''; });
93
+ return out;
94
+ }
95
+
96
+ /** Substitute the canonical "cn-" class prefix used inside specs with the system prefix. */
97
+ export function rePrefix(s, prefix) {
98
+ return prefix === 'cn' ? s : s.replace(/\bcn-/g, `${prefix}-`);
99
+ }
100
+
101
+ function applyState(base, stateSelector) {
102
+ return stateSelector
103
+ .split(/,\s*/)
104
+ .map((piece) => (piece.startsWith('&') ? base + piece.slice(1) : base + piece))
105
+ .join(', ');
106
+ }
107
+
108
+ /**
109
+ * Flatten a component spec into ordered CSS rules (selectors + raw declarations with {refs}).
110
+ * Each rule carries `origin` so the editor can map a rule back to the spec location.
111
+ * @returns {{selector:string, declarations:Record<string,string>, origin:{scope:string, part:string, state?:string}}[]}
112
+ */
113
+ export function componentRules(spec, prefix) {
114
+ const root = `.${prefix}-${spec.slug}`;
115
+ const partSel = (part) => (part === 'root' ? '' : ` .${prefix}-${spec.slug}__${part}`);
116
+ const rules = [];
117
+ const emit = (base, block, scope) => {
118
+ if (!block) return;
119
+ for (const [part, decls] of Object.entries(block)) {
120
+ if (part === '@states' || !decls) continue;
121
+ const sel = base.split(/,\s*/).map((b) => b + partSel(part)).join(', ');
122
+ rules.push({ selector: sel, declarations: decls, origin: { scope, part } });
123
+ }
124
+ const states = block['@states'] ?? {};
125
+ for (const [state, parts] of Object.entries(states)) {
126
+ const st = spec.states[state];
127
+ if (!st) throw new Error(`${spec.name}: block references unknown state "${state}"`);
128
+ const stateSel = rePrefix(st.selector, prefix);
129
+ const stateBase = applyState(base, stateSel);
130
+ for (const [part, decls] of Object.entries(parts)) {
131
+ // A state selector that already names the part (" .p-x__item:hover") targets it directly.
132
+ const targetsPart = part !== 'root' && stateSel.includes(`__${part}`);
133
+ const sel = stateBase.split(/,\s*/).map((b) => b + (targetsPart ? '' : partSel(part))).join(', ');
134
+ rules.push({ selector: sel, declarations: decls, origin: { scope, part, state } });
135
+ }
136
+ }
137
+ };
138
+ emit(root, spec.base, 'base');
139
+ for (const [axis, values] of Object.entries(spec.variants ?? {})) {
140
+ for (const [value, block] of Object.entries(values)) emit(`${root}[data-${axis}="${value}"]`, block, `variants.${axis}.${value}`);
141
+ }
142
+ (spec.compound ?? []).forEach((c, i) => {
143
+ const sel = root + Object.entries(c.when).map(([a, v]) => `[data-${a}="${v}"]`).join('');
144
+ emit(sel, c.block, `compound.${i}`);
145
+ });
146
+ return rules;
147
+ }
148
+
149
+ function renderDecls(decls, idx, annotate, prefix = 'cn') {
150
+ return Object.entries(decls)
151
+ .map(([prop, value]) => {
152
+ value = rePrefix(value, prefix);
153
+ const css = toCssVars(value, idx);
154
+ if (!annotate || css === value) return ` ${prop}: ${css};`;
155
+ const l = toLiteral(value, idx, 'light');
156
+ const d = toLiteral(value, idx, 'dark');
157
+ return ` ${prop}: ${css}; /* ${l}${d !== l ? ` · dark: ${d}` : ''} */`;
158
+ })
159
+ .join('\n');
160
+ }
161
+
162
+ export function renderRules(rules, idx, annotate = false, prefix = 'cn') {
163
+ return rules.filter((r) => Object.keys(r.declarations).length).map((r) => `${r.selector} {\n${renderDecls(r.declarations, idx, annotate, prefix)}\n}`).join('\n');
164
+ }
165
+
166
+ export function componentCss(spec, idx, prefix, annotate = false) {
167
+ const head = `/* ---- ${spec.name} (.${prefix}-${spec.slug}) ---- */`;
168
+ const body = renderRules(componentRules(spec, prefix), idx, annotate, prefix);
169
+ const extra = spec.extraCss ? '\n' + withContainerQueries(toCssVars(rePrefix(spec.extraCss.trim(), prefix), idx)) : '';
170
+ return `${head}\n${body}${extra}\n`;
171
+ }
172
+
173
+ function groupBy(arr, key) {
174
+ const out = {};
175
+ for (const x of arr) (out[key(x)] ??= []).push(x);
176
+ return out;
177
+ }
178
+
179
+ export function tokensCss(system, idx) {
180
+ const def = system.meta.defaultTheme;
181
+ const other = def === 'light' ? 'dark' : 'light';
182
+ const all = [...idx.values()];
183
+ const line = (t, theme) => ` ${t.cssVar}: ${theme === 'light' ? t.light : t.dark};`;
184
+ const groups = groupBy(all, (t) => t.group);
185
+ const defaultBlock = Object.entries(groups)
186
+ .map(([g, ts]) => ` /* ${g} */\n` + ts.map((t) => line(t, def)).join('\n'))
187
+ .join('\n');
188
+ const themed = all.filter((t) => t.themed);
189
+ const otherBlock = themed.map((t) => line(t, other)).join('\n');
190
+ return [
191
+ `/* ${system.meta.name} — design tokens. Generated by canon; edit design/tokens.json, not this file. */`,
192
+ `:root {\n color-scheme: ${def};\n${defaultBlock}\n}`,
193
+ `:root[data-theme="${other}"] {\n color-scheme: ${other};\n${otherBlock}\n}`,
194
+ `@media (prefers-color-scheme: ${other}) {\n :root:not([data-theme="${def}"]) {\n color-scheme: ${other};\n${otherBlock.replace(/^/gm, ' ')}\n }\n}`,
195
+ '',
196
+ ].join('\n\n');
197
+ }
198
+
199
+ export function baseCss(system, idx) {
200
+ return withContainerQueries(baseCssRaw(system, idx));
201
+ }
202
+ function baseCssRaw(system, idx) {
203
+ const p = system.meta.prefix;
204
+ const v = (ref) => toCssVars(`{${ref}}`, idx);
205
+ const typeUtils = Object.entries(system.tokens.type)
206
+ .map(([name, ts]) =>
207
+ `.${p}-text-${name} {\n font-family: ${v(`type.${name}.family`)};\n font-size: ${v(`type.${name}.size`)};\n font-weight: ${v(`type.${name}.weight`)};\n line-height: ${v(`type.${name}.lineHeight`)};\n letter-spacing: ${v(`type.${name}.letterSpacing`)};${ts.transform ? `\n text-transform: ${ts.transform};` : ''}${name.startsWith('numeric') ? '\n font-variant-numeric: tabular-nums;' : ''}\n}`,
208
+ )
209
+ .join('\n');
210
+ return `/* ---- base: reset + primitives ---- */
211
+ *, *::before, *::after { box-sizing: border-box; }
212
+ html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; scrollbar-gutter: stable; }
213
+ body {
214
+ margin: 0;
215
+ background-color: ${v('color.bg-canvas')};
216
+ color: ${v('color.fg-default')};
217
+ font-family: ${v('font.family.sans')};
218
+ font-size: ${v('font.size.md')};
219
+ line-height: ${v('font.lineHeight.normal')};
220
+ -webkit-font-smoothing: antialiased;
221
+ -moz-osx-font-smoothing: grayscale;
222
+ font-feature-settings: "cv11", "ss01";
223
+ }
224
+ h1, h2, h3, h4, h5, h6, p, figure, blockquote, dl, dd { margin: 0; }
225
+ ul, ol { margin: 0; padding: 0; list-style: none; }
226
+ img, svg, video, canvas { display: block; max-width: 100%; }
227
+ button, input, select, textarea { font: inherit; color: inherit; letter-spacing: inherit; }
228
+ a { color: inherit; text-decoration: none; }
229
+ hr { border: 0; border-top: 1px solid ${v('color.border-default')}; margin: 0; }
230
+ ::selection { background-color: ${v('color.bg-inverse')}; color: ${v('color.fg-inverse')}; }
231
+ :focus-visible { outline: 2px solid ${v('color.border-action')}; outline-offset: 2px; }
232
+ .${p}-icon { width: 1em; height: 1em; flex-shrink: 0; }
233
+ .${p}-sr-only { position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0, 0, 0, 0); white-space: nowrap; border: 0; }
234
+ .${p}-tabular { font-variant-numeric: tabular-nums; }
235
+ .${p}-truncate { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
236
+ .${p}-container { width: 100%; max-width: ${v('size.container.xl')}; margin-inline: auto; padding-inline: ${v('space.8')}; }
237
+ .${p}-container[data-size="sm"] { max-width: ${v('size.container.sm')}; }
238
+ .${p}-container[data-size="md"] { max-width: ${v('size.container.md')}; }
239
+ .${p}-container[data-size="lg"] { max-width: ${v('size.container.lg')}; }
240
+ .${p}-container[data-size="2xl"] { max-width: ${v('size.container.2xl')}; }
241
+ .${p}-section { padding-block: ${v('space.24')}; background-color: ${v('color.bg-canvas')}; }
242
+ .${p}-section[data-padding="sm"] { padding-block: ${v('space.12')}; }
243
+ .${p}-section[data-padding="md"] { padding-block: ${v('space.16')}; }
244
+ .${p}-section[data-padding="none"] { padding-block: 0; }
245
+ .${p}-section[data-bg="subtle"] { background-color: ${v('color.bg-subtle')}; }
246
+ .${p}-section[data-bg="surface"] { background-color: ${v('color.bg-surface')}; }
247
+ .${p}-section[data-bg="inverse"] { background-color: ${v('color.bg-inverse')}; color: ${v('color.fg-inverse')}; }
248
+ .${p}-section[data-bg="brand"] { background-color: ${v('color.bg-action')}; color: ${v('color.fg-on-action')}; }
249
+ .${p}-section[data-bg="brand-subtle"] { background-color: ${v('color.bg-action-subtle')}; }
250
+ .${p}-stack { display: flex; flex-direction: column; gap: ${v('space.4')}; min-width: 0; }
251
+ .${p}-row { display: flex; flex-direction: row; align-items: center; gap: ${v('space.4')}; min-width: 0; }
252
+ .${p}-row[data-wrap] { flex-wrap: wrap; }
253
+ .${p}-row[data-align="start"], .${p}-stack[data-align="start"] { align-items: flex-start; }
254
+ .${p}-row[data-align="end"], .${p}-stack[data-align="end"] { align-items: flex-end; }
255
+ .${p}-row[data-align="stretch"], .${p}-stack[data-align="stretch"] { align-items: stretch; }
256
+ .${p}-row[data-align="center"], .${p}-stack[data-align="center"] { align-items: center; }
257
+ .${p}-row[data-justify="between"] { justify-content: space-between; }
258
+ .${p}-row[data-justify="center"], .${p}-stack[data-justify="center"] { justify-content: center; }
259
+ .${p}-row[data-justify="end"] { justify-content: flex-end; }
260
+ .${p}-grid { display: grid; gap: ${v('space.6')}; min-width: 0; grid-template-columns: repeat(var(--${p}-cols, 3), minmax(0, 1fr)); }
261
+ .${p}-grid[data-cols="1"] { --${p}-cols: 1; } .${p}-grid[data-cols="2"] { --${p}-cols: 2; } .${p}-grid[data-cols="3"] { --${p}-cols: 3; } .${p}-grid[data-cols="4"] { --${p}-cols: 4; } .${p}-grid[data-cols="5"] { --${p}-cols: 5; } .${p}-grid[data-cols="6"] { --${p}-cols: 6; }
262
+ .${p}-split { display: grid; grid-template-columns: 1fr 1fr; gap: ${v('space.16')}; align-items: center; min-width: 0; }
263
+ .${p}-split[data-ratio="1:2"] { grid-template-columns: 1fr 2fr; } .${p}-split[data-ratio="2:1"] { grid-template-columns: 2fr 1fr; } .${p}-split[data-ratio="1:3"] { grid-template-columns: 1fr 3fr; } .${p}-split[data-align="start"] { align-items: start; }
264
+ .${p}-stack[data-gap="0"], .${p}-row[data-gap="0"], .${p}-grid[data-gap="0"] { gap: 0; }
265
+ .${p}-stack[data-gap="1"], .${p}-row[data-gap="1"], .${p}-grid[data-gap="1"] { gap: ${v('space.1')}; }
266
+ .${p}-stack[data-gap="2"], .${p}-row[data-gap="2"], .${p}-grid[data-gap="2"] { gap: ${v('space.2')}; }
267
+ .${p}-stack[data-gap="3"], .${p}-row[data-gap="3"], .${p}-grid[data-gap="3"] { gap: ${v('space.3')}; }
268
+ .${p}-stack[data-gap="4"], .${p}-row[data-gap="4"], .${p}-grid[data-gap="4"] { gap: ${v('space.4')}; }
269
+ .${p}-stack[data-gap="5"], .${p}-row[data-gap="5"], .${p}-grid[data-gap="5"] { gap: ${v('space.5')}; }
270
+ .${p}-stack[data-gap="6"], .${p}-row[data-gap="6"], .${p}-grid[data-gap="6"] { gap: ${v('space.6')}; }
271
+ .${p}-stack[data-gap="8"], .${p}-row[data-gap="8"], .${p}-grid[data-gap="8"] { gap: ${v('space.8')}; }
272
+ .${p}-stack[data-gap="10"], .${p}-row[data-gap="10"], .${p}-grid[data-gap="10"] { gap: ${v('space.10')}; }
273
+ .${p}-stack[data-gap="12"], .${p}-row[data-gap="12"], .${p}-grid[data-gap="12"] { gap: ${v('space.12')}; }
274
+ .${p}-stack[data-gap="16"], .${p}-row[data-gap="16"], .${p}-grid[data-gap="16"] { gap: ${v('space.16')}; }
275
+ .${p}-center { text-align: center; align-items: center; }
276
+ .${p}-measure { max-width: ${v('size.container.prose')}; }
277
+ .${p}-measure[data-size="sm"] { max-width: 480px; }
278
+ .${p}-measure[data-size="md"] { max-width: 640px; }
279
+ .${p}-measure[data-size="lg"] { max-width: 768px; }
280
+ .${p}-mx-auto { margin-inline: auto; }
281
+ .${p}-flex-1 { flex: 1 1 0%; min-width: 0; }
282
+ .${p}-hide-mobile { display: initial; }
283
+ .${p}-show-mobile { display: none; }
284
+ .${p}-placeholder { display: block; width: 100%; background-color: ${v('color.bg-muted')}; border-radius: ${v('radius.card')}; overflow: hidden; position: relative; }
285
+ .${p}-placeholder > svg { display: block; width: 100%; height: 100%; }
286
+ .${p}-placeholder[data-radius="none"] { border-radius: 0; }
287
+ .${p}-placeholder[data-radius="full"] { border-radius: ${v('radius.full')}; }
288
+ .${p}-placeholder[data-shadow] { box-shadow: ${v('shadow.2xl')}; }
289
+ @media (max-width: ${v('breakpoint.lg')}) { .${p}-grid[data-cols="4"], .${p}-grid[data-cols="5"], .${p}-grid[data-cols="6"] { --${p}-cols: 2; } }
290
+ @media (max-width: ${v('breakpoint.md')}) {
291
+ .${p}-container { padding-inline: ${v('space.4')}; }
292
+ .${p}-section { padding-block: ${v('space.16')}; }
293
+ .${p}-section[data-padding="md"] { padding-block: ${v('space.12')}; }
294
+ .${p}-section[data-padding="sm"] { padding-block: ${v('space.8')}; }
295
+ .${p}-grid { --${p}-cols: 1 !important; }
296
+ .${p}-grid[data-cols="2"][data-keep], .${p}-grid[data-cols="4"][data-keep] { --${p}-cols: 2 !important; }
297
+ .${p}-split, .${p}-split[data-ratio] { grid-template-columns: 1fr; gap: ${v('space.10')}; }
298
+ .${p}-row[data-stack] { flex-direction: column; align-items: stretch; }
299
+ .${p}-hide-mobile { display: none !important; }
300
+ .${p}-show-mobile { display: initial; }
301
+ }
302
+ ${typeUtils}
303
+ @media (prefers-reduced-motion: reduce) {
304
+ *, *::before, *::after { animation-duration: 0.01ms !important; animation-iteration-count: 1 !important; transition-duration: 0.01ms !important; scroll-behavior: auto !important; }
305
+ }
306
+ `;
307
+ }
308
+
309
+
310
+ /**
311
+ * Duplicate every `@media (max-width: …)` block as an `@container (max-width: …)`
312
+ * block so layouts also respond inside a sized container (the gallery's phone
313
+ * frame, embedded previews). Real pages without a container keep the media rules.
314
+ */
315
+ export function withContainerQueries(css) {
316
+ const out = [];
317
+ const re = /@media \((max-width|min-width):\s*([^)]+)\)\s*\{/g;
318
+ let m;
319
+ while ((m = re.exec(css))) {
320
+ let depth = 1, i = re.lastIndex;
321
+ while (i < css.length && depth) { if (css[i] === '{') depth++; else if (css[i] === '}') depth--; i++; }
322
+ const body = css.slice(re.lastIndex, i - 1);
323
+ out.push(`@container (${m[1]}: ${m[2]}) {${body}}`);
324
+ }
325
+ return out.length ? css + '\n' + out.join('\n') : css;
326
+ }
327
+
328
+ export function patternsCss(system, idx) {
329
+ return system.patterns.filter((p) => p.css).map((p) => `/* ---- pattern: ${p.name} ---- */\n${withContainerQueries(toCssVars(rePrefix(p.css.trim(), system.meta.prefix), idx))}\n`).join('\n');
330
+ }
331
+
332
+ export function fullCss(system, idx) {
333
+ const tokens = tokensCss(system, idx);
334
+ const base = baseCss(system, idx);
335
+ const components = system.components.map((c) => componentCss(c, idx, system.meta.prefix)).join('\n');
336
+ const patterns = patternsCss(system, idx);
337
+ const all = `${tokens}\n${base}\n/* ================= components ================= */\n${components}\n${patterns}`;
338
+ return { tokens, base, components: components + '\n' + patterns, all };
339
+ }
@@ -0,0 +1,68 @@
1
+ const rulesBlock = (system) => {
2
+ const p = system.meta.prefix;
3
+ const dist = `design/${system.meta.out || 'dist'}`;
4
+ return `## ${system.meta.name} — UI implementation contract
5
+
6
+ Use Canon as this project's design system for UI tasks within the user's requested scope. Follow the repository's conventions from quick prototypes through polished production work.
7
+
8
+ **When connecting Canon:** inspect and reuse the existing setup, preserve the project's stack, routes, behavior, data and design definitions, and run \`canon connect <Studio-URL-or-full-snapshot-JSON-path> --root <project-root> [--design <existing-custom-path>] [--no-hooks]\`. A bound project reuses its recorded source without fetching or resetting it; for a legacy installation, discover and pass its existing \`--design\` path. Never replace a failed snapshot import with \`canon init\` or a preset. Connect the generated CSS through the framework's real style entry point even for a connection-only request, without redesigning screens. An empty folder is not permission to scaffold an app. Then start or reuse \`canon studio --root <project-root> --port 0 --open\` as a background process, verify its URL and return it.
9
+
10
+ 1. **Own implementation and setup.** Inspect repository instructions, framework, routes, shared UI, data flow and available checks. The user sets product intent; make routine technical decisions from repository conventions. Install needed dependencies with its package manager, configure integration and wire CSS/wrappers into the app. Let the goal determine depth: a prototype needs a small working flow with separate, clearly identified fixtures; production work needs useful domain/data/presentation boundaries plus requested validation, persistence and failure handling. Add architecture or services only when needed.
11
+ 2. **Retrieve a small working set.** At the beginning of UI work, read the current \`DESIGN.compact.md\` and installed Canon instruction block. With Canon MCP, call \`design_rules\` once, then only relevant \`get_component\`, \`get_pattern\`, \`get_token\` or grouped \`list_tokens\`; use \`search\` or \`suggest_token\` when needed. Without MCP, read matching source JSON or selected sections of \`DESIGN.md\`. MCP supplies context and lint; execute setup, commands and preview processes through terminal tools.
12
+ 3. **Reuse the contract.** Reuse stable application components and compose the documented components and patterns. Preserve semantic elements, anatomy, \`.${p}-<component>\` / \`.${p}-<component>__<part>\` classes and allowed \`data-*\` props; adapt content, framework bindings, routes and events to the product. Use semantic tokens (\`var(--${p}-…)\` or generated Tailwind utilities) for colors, typography, spacing, radii and shadows. Use composite text styles and token-based layout CSS.
13
+ 4. **Compose with intent.** Choose hierarchy, density, grouping and responsive layout from the user's task, supplied content and visual references. Keep at most one primary action per region. Use meaningful copy; distinguish fixtures from verified product facts. Never invent testimonials or metrics as evidence. Avoid generic card grids and decoration that do not help the content.
14
+ 5. **Make the flow work.** Generated React wrappers provide styled markup and typed attributes, not application behavior. Implement the requested interactions and relevant loading, error, empty, success and disabled states. Use semantic HTML, labels, accessible names, visible focus, keyboard operation and focus management; respect reduced motion. Check responsive layouts and supported themes with realistic content.
15
+ 6. **Keep the app light.** Preserve the framework's rendering model; keep client code local to interactions. Avoid gratuitous dependencies, importing an entire icon set, repeated CSS imports and oversized unoptimized media. Use individual icons and appropriately sized media; defer noncritical work when useful. Measure significant pages with the project's bundle, load and interaction tooling before claiming a performance improvement.
16
+ 7. **Change the right source.** Product routes, domain logic, data access and screen composition belong in application files. Shared tokens and reusable visual behavior belong in the configured Canon source: extend an existing spec or add \`design/components/<slug>.json\` (and a pattern when useful) instead of scattering overrides. \`canon add <slug>\` only restores an existing catalogue component. Studio Save atomically updates design files, generated CSS and installed references; MCP refreshes on its next data request after the build. Save does not edit application HTML, routes or behavior or deploy the app. After source edits made outside Studio, run \`canon sync\` to build outputs and refresh installed instructions. \`canon build\` only regenerates outputs. Never patch generated files.
17
+ 8. **Verify and deliver.** Run \`canon lint <changed files>\` and \`canon check\`; fix violations introduced by the change. Run applicable repository type checks/tests and inspect changed screens in a browser at narrow and wide widths, exercising keyboard and relevant states. Canon lint checks token/class/prop usage; check also verifies generated output freshness. Neither proves visual quality, accessibility or performance. For runnable UI work, start or reuse the application's dev server, verify the affected route and leave the preview running. Use the project's dev command for the app; \`canon studio\` opens the design-system editor. Return the preview URL, checks actually run and remaining limitations.
18
+
19
+ Art direction: ${system.meta.direction.summary}
20
+ Avoid: ${system.meta.direction.never.slice(0, 6).join(' · ')}
21
+
22
+ CSS: a prototype can import \`${dist}/${p}.css\` once. For a smaller product bundle, explicitly select \`${dist}/tokens.css\` + \`${dist}/base.css\` + used \`${dist}/css/components/<slug>.css\` and \`${dist}/css/patterns/<slug>.css\`, including every component/pattern dependency referenced by that UI. Use one strategy to avoid duplicate CSS; selection is explicit.
23
+ Files: Tailwind \`${dist}/tailwind.theme.css\` · React \`${dist}/react/\` · Studio \`${dist}/preview.html\` (\`canon studio\`). The app consumes these local generated files through its normal reload/build and does not depend on the originating Studio after import. Paths assume \`design/\`; use the project's recorded binding or configured \`--design\` and \`--root\` when different.`;
24
+ };
25
+ export function agentsMd(system) {
26
+ return `<!-- canon:start -->\n${rulesBlock(system)}\n<!-- canon:end -->\n`;
27
+ }
28
+ export function skillMd(system) {
29
+ return `---
30
+ name: design-system
31
+ description: ${system.meta.name} design system rules, tokens, components and patterns. Use BEFORE writing or editing any UI (components, pages, CSS, Tailwind, React/HTML markup, styling) in this project, and when asked about colors, spacing, typography, buttons, forms, layouts or "how should this look".
32
+ ---
33
+
34
+ # ${system.meta.name} design system
35
+
36
+ ${rulesBlock(system)}
37
+ `;
38
+ }
39
+ export function cursorMdc(system) {
40
+ return `---
41
+ description: ${system.meta.name} design system — mandatory rules for UI code
42
+ globs: ["**/*.tsx", "**/*.jsx", "**/*.vue", "**/*.svelte", "**/*.html", "**/*.css", "**/*.scss"]
43
+ alwaysApply: true
44
+ ---
45
+ ${rulesBlock(system)}
46
+ `;
47
+ }
48
+ export function promptMd(system) {
49
+ return `# System prompt block — ${system.meta.name} design system (paste into any LLM)
50
+
51
+ Use the ${system.meta.name} design system in this project. Start with DESIGN.compact.md and retrieve the component and pattern specifications needed for the current task. Preserve work in progress and adopt the system within the requested scope.
52
+
53
+ ${rulesBlock(system)}
54
+
55
+ When outputting code, include the markup and behavior needed for the requested flow, using the project's framework and the documented component contract. State any fixture data and unverified assumptions.
56
+ `;
57
+ }
58
+ export function mcpJson() {
59
+ return JSON.stringify({ mcpServers: { canon: { command: 'canon', args: ['mcp', '--design', 'design'] } } }, null, 2) + '\n';
60
+ }
61
+ export function generate(system, _idx, write) {
62
+ write('agents/AGENTS.md', agentsMd(system));
63
+ write('agents/CLAUDE.md', agentsMd(system));
64
+ write('agents/SKILL.md', skillMd(system));
65
+ write('agents/design-system.mdc', cursorMdc(system));
66
+ write('agents/PROMPT.md', promptMd(system));
67
+ write('agents/mcp.json', mcpJson());
68
+ }
@@ -0,0 +1,58 @@
1
+ # Connect Canon to the current project
2
+
3
+ You are connecting the saved Canon design from this Studio to the user's current project. Complete the connection in the current repository, integrate the generated styles with its existing UI stack, then open the Studio bound to that project. The user does not need to describe the application again.
4
+
5
+ ## 1. Inspect and preserve the project
6
+
7
+ Read repository instructions and identify the project root, framework, package manager, shared UI, style entry points and available checks. Preserve existing work, routes, behavior, data and design definitions. Connecting Canon does not authorize recreating the app, redesigning its screens or migrating the whole interface.
8
+
9
+ Look for `.canon/project.json` first. It records the Canon design already bound to this project. For a legacy installation without that file, locate its existing `system.json` and use that directory as `--design <existing-path>` when connecting. Do not choose a new default path over an existing Canon source.
10
+
11
+ ## 2. Make the CLI available
12
+
13
+ Reuse the project's existing Canon installation or a Canon checkout already available to the agent. Otherwise:
14
+
15
+ 1. Use Node.js 22.18.0 or newer. Prepare required dependencies with the project's package manager.
16
+ 2. If you read this document over HTTP, resolve `./canon-package.tgz` beside the URL from which you loaded it, preserving its scheme, host, port and directory. For example, `/canon/CONNECT.md` uses `/canon/canon-package.tgz`. Download that package into `.canon/canon-ds.tgz` in the project.
17
+ 3. Install the retained local file as a development dependency. With npm, run `npm install --save-dev ./.canon/canon-ds.tgz`; use the equivalent with the project's existing package manager. Keep the archive so future installs do not depend on the original Studio.
18
+ 4. Verify the local CLI with `npx --no-install canon --version` or the package manager's local binary command. Use that invocation below. Do not assume a public registry package or invent a download URL.
19
+
20
+ If you read this as a local file, use the available CLI, checkout or package archive. Locate the Canon source (`system.json`, `tokens.json`, components and patterns) above the generated documentation and start a source Studio with `canon studio --design <source-directory> --port 0` in the background. Use its verified URL for connection below. If a full saved snapshot JSON is already available, its local path also works. If neither source nor package is accessible, request an accessible Studio address or the missing package/source. A localhost address on another computer is not reachable by a remote agent.
21
+
22
+ ## 3. Connect the saved design
23
+
24
+ Run this from the application repository, substituting the exact project root and connection source:
25
+
26
+ ```text
27
+ canon connect <Studio-URL-or-full-snapshot-JSON-path> --root <project-root> [--design <existing-custom-path>] [--no-hooks]
28
+ ```
29
+
30
+ Use the URL from which this document was loaded when it came from Studio. The command imports the saved snapshot from `./api/system` beside that Studio's connection document only when the project has no configured Canon source. If `.canon/project.json` already binds the project, it automatically reuses that design. For a legacy installation, pass the existing path with `--design`; the new binding will record it.
31
+
32
+ The command builds the source, installs current references and records `.canon/project.json`. On reconnect it must preserve the project's later token, component, pattern, prefix and path changes without fetching or resetting them. If snapshot download, parsing or validation fails, stop and report the error. Never substitute `canon init`, a preset or catalogue defaults for the failed import.
33
+
34
+ Read `DESIGN.compact.md` and the Canon blocks in `AGENTS.md` or `CLAUDE.md` at the beginning of UI work. They are the current project instructions. When MCP is available, call `design_rules` once and retrieve only the component, pattern and token specifications needed for the task. MCP refreshes its data on the next request after a successful build; the user does not need a sync or reload prompt.
35
+
36
+ ## 4. Integrate the application
37
+
38
+ Connect Canon's generated CSS and, where useful, wrappers through the existing framework's real style and application entry points. A connection-only request still requires the application to consume the generated stylesheet; verify that import or link in the running app without redesigning screens. Reuse stable components, avoid duplicate CSS and inspect global resets or theme rules before applying them broadly.
39
+
40
+ If the folder has no application or style entry point, do not scaffold one just to demonstrate Canon. Report that the design and references are connected, and that stylesheet consumption awaits an application task.
41
+
42
+ Adopt Canon only in UI the user asks to create or change. Product routes, screen layout, behavior and data remain application work. Reusable tokens and visual component definitions belong in the configured Canon source. For source edits made by an agent outside Studio, run `canon sync --root <project-root>` and include `--design <custom-path>` for a legacy unbound setup. Never patch generated files.
43
+
44
+ The app uses its local generated CSS through the framework's normal development reload or build. It does not depend on the original Studio after the first import, and Studio Save does not rewrite application HTML, routes or behavior or deploy the app.
45
+
46
+ ## 5. Open the project's Studio and verify
47
+
48
+ Start or reuse this command as a background process:
49
+
50
+ ```text
51
+ canon studio --root <project-root> --port 0 --open
52
+ ```
53
+
54
+ Capture the assigned local URL, verify it responds and leave the process running. This bound Studio edits the project's saved Canon source. Save atomically updates the design files, generated CSS and installed agent references; the application sees CSS changes through its normal reload/build.
55
+
56
+ Run `canon check --root <project-root>` and applicable repository checks. For changed UI files, run `canon lint <changed-files> --root <project-root>` and inspect affected screens in the browser. Distinguish pre-existing failures from new issues.
57
+
58
+ Return the verified project Studio URL, the design path, the application stylesheet entry point, the checks run and any real limitation. If there is an active UI task, continue it using the current references and verify its application preview too.
@@ -0,0 +1,2 @@
1
+ // Thin TS façade over the shared engine (src/engine.js) so generators keep typed imports.
2
+ export { rePrefix, componentRules, renderRules, componentCss, tokensCss, baseCss, patternsCss, fullCss } from '../engine.js';