@reqvire-org/ads 0.3.0 → 0.3.1

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 (47) hide show
  1. package/README.md +1 -1
  2. package/dist/bootstrap-ds/ADS.md +18 -7
  3. package/dist/bootstrap-ds/README.md +12 -10
  4. package/dist/bootstrap-ds/design-system/index.ts +1 -1
  5. package/dist/bootstrap-ds/design-system/patterns/token-preview/Scenario.tsx +4 -0
  6. package/dist/bootstrap-ds/design-system/patterns/token-preview/TokenPreview.tsx +31 -0
  7. package/dist/bootstrap-ds/design-system/patterns/token-preview/fixture.ts +8 -0
  8. package/dist/bootstrap-ds/design-system/tokens/Foundation.fixture.ts +44 -43
  9. package/dist/bootstrap-ds/design-system/tokens/Foundation.scenario.tsx +3 -13
  10. package/dist/bootstrap-ds/ds.config.json +10 -0
  11. package/dist/check.mjs +2 -9
  12. package/dist/init.mjs +22 -5
  13. package/dist/mcp/apps.mjs +2 -2
  14. package/dist/mcp/context.mjs +1 -1
  15. package/dist/mcp/preview-widget.bundle.js +40 -38
  16. package/dist/mcp/project-widget.bundle.js +42 -38
  17. package/dist/mcp/prompts.mjs +1 -1
  18. package/dist/mcp/server.mjs +5 -5
  19. package/dist/preview-resources.mjs +1 -1
  20. package/dist/scenarios.mjs +16 -5
  21. package/dist/skills/ads-assimilate/SKILL.md +28 -12
  22. package/dist/skills/ads-assimilate/references/cli.md +2 -2
  23. package/dist/skills/ads-break-ui/SKILL.md +28 -12
  24. package/dist/skills/ads-break-ui/references/verification.md +4 -0
  25. package/dist/skills/ads-designer/SKILL.md +30 -14
  26. package/dist/skills/ads-designer/assets/bootstrap-ds/ADS.md +18 -7
  27. package/dist/skills/ads-designer/assets/bootstrap-ds/README.md +12 -10
  28. package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/index.ts +1 -1
  29. package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/patterns/token-preview/Scenario.tsx +4 -0
  30. package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/patterns/token-preview/TokenPreview.tsx +31 -0
  31. package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/patterns/token-preview/fixture.ts +8 -0
  32. package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/tokens/Foundation.fixture.ts +44 -43
  33. package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/tokens/Foundation.scenario.tsx +3 -13
  34. package/dist/skills/ads-designer/assets/bootstrap-ds/ds.config.json +10 -0
  35. package/dist/skills/ads-designer/references/cli.md +2 -2
  36. package/dist/skills/ads-import/SKILL.md +28 -12
  37. package/dist/skills/ads-import/references/cli.md +2 -2
  38. package/dist/skills/ads-motion/SKILL.md +28 -12
  39. package/dist/token-policy.mjs +12 -0
  40. package/dist/token-samples.mjs +8 -0
  41. package/dist/units.mjs +34 -10
  42. package/docs/ADS.md +23 -9
  43. package/docs/ARCHITECTURE.md +12 -0
  44. package/docs/CHANGELOG.md +16 -1
  45. package/docs/GUIDE.md +9 -6
  46. package/docs/MCP.md +44 -2
  47. package/package.json +2 -2
package/README.md CHANGED
@@ -272,7 +272,7 @@ The [dummy project](tests/fixtures/guard-project/README.md) tests the generic gu
272
272
  There are two different starting points in this checkout:
273
273
 
274
274
  - [bootstrap-ds/](bootstrap-ds/README.md) is an empty React application starter:
275
- token defaults, build setup, empty component/pattern folders, and a project preview compiler adapter. It is bundled with `ads-designer` so an agent can copy it when starting
275
+ token defaults, build setup, empty product component folders, a project-owned token review pattern, and a project preview compiler adapter. It is bundled with `ads-designer` so an agent can copy it when starting
276
276
  from zero. `ads init` uses its empty foundation files when a project has no ADS.
277
277
  - [design-system/](design-system/README.md) is this repository's working example,
278
278
  with real components, patterns, and scenarios inspected through the built-in showcase. It demonstrates the architecture
@@ -114,13 +114,24 @@ design-system/
114
114
  Flat owners may use `Button.scenario.tsx` and `Button.fixture.ts` instead.
115
115
  `Scenario.tsx` exports a default independently mountable view and literal metadata
116
116
  `export const scenario = { title: "Button states", covers: ["Button"] };`.
117
- Token entries under the configured token directory also declare `tokens`:
118
- `export const scenario = { title: "Typography", covers: ["TokenSamples"], tokens: fixture.tokens };`.
119
- Metadata may read literal const fixture fields; it cannot run expressions,
120
- functions, wildcard lists or token discovery. The same fixture list must be
121
- consumed by the default entry. `covers: []` is valid for a minimal token value
122
- inspection entry without public UI; styled samples use a real public review
123
- pattern. Names and sample text belong to the fixture, values to foundation CSS.
117
+ Token entries under the configured token directory declare
118
+ `export const scenario = { title: fixture.title, covers: ["TokenSamples"], samples: fixture.samples };`.
119
+ `TokenSamples` is any project-owned public review pattern, not a required name.
120
+ Its colocated fixture declares literal `samples: [{ token, property, probe }]`.
121
+ Each token must exist and have sample coverage; duplicate bindings, missing probes,
122
+ empty covers and value-only inspectors fail. The default entry passes the declared
123
+ samples to a rendered public pattern's `samples` prop. No token-name/property mapping
124
+ or sample values are embedded in the tool. Display values come from foundation CSS;
125
+ probes are isolated verification inputs owned by the fixture.
126
+
127
+ The renderer marks the actual styled element with `data-ads-token` and
128
+ `data-ads-property`. In a browser, the App validates the declared property/value and
129
+ probe using `CSS.supports`, checks the sample is rendered, then temporarily changes
130
+ that token to its fixture probe. Its computed sample property must respond. Values
131
+ are restored before yielding to painting; hardcoded lookalikes fail. Source gates
132
+ prove the contract structure, not successful rendering. The browser check covers
133
+ the mounted scenario and theme; visual quality and other states still need review.
134
+
124
135
  It imports real public UI through `@ds` and its local fixture. The adapter supplies
125
136
  initial state, callbacks and simulated services. The default entry needs no
126
137
  mandatory caller inputs or prior page initialization. Related parts may share a
@@ -1,8 +1,8 @@
1
1
  # Empty native design system
2
2
 
3
- This private React/TypeScript workspace contains semantic CSS tokens, an empty
4
- public barrel, Linaria extraction and a project preview compiler adapter. It has
5
- no components, patterns, icons, fonts or example screens. The minimal showcase
3
+ This private React/TypeScript workspace contains semantic CSS tokens, a project-owned
4
+ token review pattern, a public barrel, Linaria extraction and a preview compiler
5
+ adapter. It has no product components, icons, fonts or example screens. The minimal showcase
6
6
  shell and MCP client come from ADS tooling.
7
7
 
8
8
  Read the local [contract](ADS.md) and the
@@ -13,7 +13,7 @@ Read the local [contract](ADS.md) and the
13
13
  Copy the contents of `bootstrap-ds/`, including dotfiles, into a new project.
14
14
  The installed `ads-designer` skill contains the same files in
15
15
  `assets/bootstrap-ds/`. Set the project name in `package.json`. Use Node 22.12 or
16
- newer and ADS 0.3 or later for the shared-resource preview compiler.
16
+ newer and ADS 0.3.1 or later for the shared-resource preview compiler.
17
17
 
18
18
  ```sh
19
19
  npx @reqvire-org/ads init --framework react
@@ -25,7 +25,7 @@ npm run dev:showcase
25
25
  For unreleased tooling, run init using the real local package:
26
26
 
27
27
  ```sh
28
- ads_tool_package="/absolute/path/to/reqvire-org-ads-0.3.0.tgz"
28
+ ads_tool_package="/absolute/path/to/reqvire-org-ads-0.3.1.tgz"
29
29
  npx --yes --package "$ads_tool_package" ads init --framework react --tool-package "$ads_tool_package"
30
30
  npm install
31
31
  npm run build
@@ -44,7 +44,7 @@ native Preact app without a saved ADS framework, use `ads init --framework preac
44
44
  Init records that choice and generates native hook imports without migrating the
45
45
  app or rewriting its compiler. Subsequent init runs reuse the saved choice.
46
46
  Init adds missing foundation files and connects architectural checks; it does
47
- not configure Linaria or generate component source. Generated `.ds/` files are
47
+ not configure Linaria or generate product component source. Generated `.ds/` files are
48
48
  ignored; original artwork and provenance belong in `assets/brand/`.
49
49
 
50
50
  ## Inspect and add UI
@@ -56,10 +56,12 @@ Its MCP client discovers entries, invokes preview tools and follows notification
56
56
  There are no required TokensPage/ComponentsPage/PatternsPage/CompositionPage
57
57
  files, and no separate project browser app to implement or rebuild.
58
58
 
59
- The existing `tokens/Foundation.scenario.tsx` and colocated fixture inspect all
60
- starter token values. Add/remove fixture names alongside token definitions;
61
- `ads check` rejects missing coverage and invented names. Styled token samples
62
- belong to your public review patterns and mount through the same scenario pipeline.
59
+ The existing `tokens/Foundation.scenario.tsx` and colocated fixture visually demonstrate all
60
+ starter token values. The fixture owns `{ token, property, probe }` bindings;
61
+ the public `TokenPreview` pattern owns their presentation and applies the real CSS
62
+ variables. Add/remove samples alongside token definitions. `ads check` rejects
63
+ missing coverage and invented names; the App verifies the declared CSS bindings
64
+ in the browser. Extend or replace the project-owned pattern as your tokens evolve.
63
65
 
64
66
  Create a component under `design-system/components/`, export it through `@ds`,
65
67
  and colocate `Scenario.tsx` plus `fixture.ts` (or `<Owner>.scenario.tsx` and
@@ -2,4 +2,4 @@
2
2
  import "./styles.css";
3
3
 
4
4
  // Add public component and pattern exports here as they are implemented.
5
- export {};
5
+ export { TokenPreview } from "./patterns/token-preview/TokenPreview";
@@ -0,0 +1,4 @@
1
+ import { TokenPreview } from "@ds";
2
+ import { fixture } from "./fixture";
3
+ export const scenario = { title: fixture.title, covers: ["TokenPreview"] };
4
+ export default function Scenario() { return <TokenPreview {...fixture} />; }
@@ -0,0 +1,31 @@
1
+ import { useLayoutEffect, useRef } from "react";
2
+ import { css, cx } from "@linaria/atomic";
3
+
4
+ export interface TokenSample { token: `--${string}`; property: string; }
5
+ export interface TokenPreviewProps { title: string; samples: readonly TokenSample[]; text: string; }
6
+ const sampleStyle = css`
7
+ display: inline-block;
8
+ position: relative;
9
+ white-space: pre-line;
10
+ &[data-ads-property="animation-duration"] { animation-duration: var(--ux-token-preview-value); }
11
+ &[data-ads-property="animation-timing-function"] { animation-timing-function: var(--ux-token-preview-value); }
12
+ &[data-ads-property="background-color"] { background-color: var(--ux-token-preview-value); }
13
+ &[data-ads-property="border-radius"] { border-radius: var(--ux-token-preview-value); }
14
+ &[data-ads-property="box-shadow"] { box-shadow: var(--ux-token-preview-value); }
15
+ &[data-ads-property="color"] { color: var(--ux-token-preview-value); }
16
+ &[data-ads-property="font-family"] { font-family: var(--ux-token-preview-value); }
17
+ &[data-ads-property="font-size"] { font-size: var(--ux-token-preview-value); }
18
+ &[data-ads-property="font-weight"] { font-weight: var(--ux-token-preview-value); }
19
+ &[data-ads-property="line-height"] { line-height: var(--ux-token-preview-value); }
20
+ &[data-ads-property="width"] { width: var(--ux-token-preview-value); }
21
+ &[data-ads-property="z-index"] { z-index: var(--ux-token-preview-value); }
22
+ `;
23
+ function Sample({ sample, text }: { sample: TokenSample; text: string }) {
24
+ const ref = useRef<HTMLSpanElement>(null);
25
+ useLayoutEffect(() => { ref.current?.style.setProperty("--ux-token-preview-value", `var(${sample.token})`); }, [sample.token]);
26
+ return <span ref={ref} className={cx("ux-token-preview__sample", sampleStyle)} data-ads-token={sample.token} data-ads-property={sample.property}>{text}</span>;
27
+ }
28
+ /** Project-owned review UI. Values always come from the real foundation CSS. */
29
+ export function TokenPreview({ title, samples, text }: TokenPreviewProps) {
30
+ return <section className="ux-token-preview"><h1>{title}</h1>{samples.map(sample => <div key={`${sample.token}:${sample.property}`} className="ux-token-preview__item"><code className="ux-token-preview__name">{sample.token}</code><Sample sample={sample} text={text}/></div>)}</section>;
31
+ }
@@ -0,0 +1,8 @@
1
+ export const fixture = {
2
+ "title": "Token preview",
3
+ "text": "Aa",
4
+ samples: [
5
+ {"token": "--accent", "property": "background-color", "probe": "rgb(31, 97, 163)"},
6
+ {"token": "--space-4", "property": "width", "probe": "13.375px"},
7
+ ]
8
+ } as const;
@@ -1,47 +1,48 @@
1
1
  export const fixture = {
2
2
  "title": "Foundation",
3
- "tokens": [
4
- "--accent",
5
- "--accent-fg",
6
- "--accent-hover",
7
- "--bg-canvas",
8
- "--bg-hover",
9
- "--bg-surface",
10
- "--border-default",
11
- "--border-focus",
12
- "--border-w",
13
- "--dur-fast",
14
- "--dur-normal",
15
- "--ease-standard",
16
- "--focus-offset",
17
- "--focus-w",
18
- "--font-mono",
19
- "--font-sans",
20
- "--leading-normal",
21
- "--leading-tight",
22
- "--radius-lg",
23
- "--radius-md",
24
- "--radius-sm",
25
- "--shadow-raised",
26
- "--shadow-xs",
27
- "--space-0",
28
- "--space-12",
29
- "--space-16",
30
- "--space-2",
31
- "--space-4",
32
- "--space-6",
33
- "--space-8",
34
- "--text-base",
35
- "--text-body",
36
- "--text-inverse",
37
- "--text-lg",
38
- "--text-muted",
39
- "--text-sm",
40
- "--text-strong",
41
- "--text-xl",
42
- "--weight-medium",
43
- "--weight-normal",
44
- "--weight-semibold",
45
- "--z-overlay"
3
+ "text": "Aa",
4
+ samples: [
5
+ {"token": "--accent", "property": "background-color", "probe": "rgb(31, 97, 163)"},
6
+ {"token": "--accent-fg", "property": "background-color", "probe": "rgb(31, 97, 163)"},
7
+ {"token": "--accent-hover", "property": "background-color", "probe": "rgb(31, 97, 163)"},
8
+ {"token": "--bg-canvas", "property": "background-color", "probe": "rgb(31, 97, 163)"},
9
+ {"token": "--bg-hover", "property": "background-color", "probe": "rgb(31, 97, 163)"},
10
+ {"token": "--bg-surface", "property": "background-color", "probe": "rgb(31, 97, 163)"},
11
+ {"token": "--border-default", "property": "background-color", "probe": "rgb(31, 97, 163)"},
12
+ {"token": "--border-focus", "property": "background-color", "probe": "rgb(31, 97, 163)"},
13
+ {"token": "--border-w", "property": "width", "probe": "13.375px"},
14
+ {"token": "--dur-fast", "property": "animation-duration", "probe": "0.375s"},
15
+ {"token": "--dur-normal", "property": "animation-duration", "probe": "0.375s"},
16
+ {"token": "--ease-standard", "property": "animation-timing-function", "probe": "cubic-bezier(.13,.27,.61,.83)"},
17
+ {"token": "--focus-offset", "property": "width", "probe": "13.375px"},
18
+ {"token": "--focus-w", "property": "width", "probe": "13.375px"},
19
+ {"token": "--font-mono", "property": "font-family", "probe": "monospace, serif"},
20
+ {"token": "--font-sans", "property": "font-family", "probe": "monospace, serif"},
21
+ {"token": "--leading-normal", "property": "line-height", "probe": "2.375"},
22
+ {"token": "--leading-tight", "property": "line-height", "probe": "2.375"},
23
+ {"token": "--radius-lg", "property": "border-radius", "probe": "13.375px"},
24
+ {"token": "--radius-md", "property": "border-radius", "probe": "13.375px"},
25
+ {"token": "--radius-sm", "property": "border-radius", "probe": "13.375px"},
26
+ {"token": "--shadow-raised", "property": "box-shadow", "probe": "1px 3px 5px rgb(31, 97, 163)"},
27
+ {"token": "--shadow-xs", "property": "box-shadow", "probe": "1px 3px 5px rgb(31, 97, 163)"},
28
+ {"token": "--space-0", "property": "width", "probe": "13.375px"},
29
+ {"token": "--space-12", "property": "width", "probe": "13.375px"},
30
+ {"token": "--space-16", "property": "width", "probe": "13.375px"},
31
+ {"token": "--space-2", "property": "width", "probe": "13.375px"},
32
+ {"token": "--space-4", "property": "width", "probe": "13.375px"},
33
+ {"token": "--space-6", "property": "width", "probe": "13.375px"},
34
+ {"token": "--space-8", "property": "width", "probe": "13.375px"},
35
+ {"token": "--text-base", "property": "font-size", "probe": "41.375px"},
36
+ {"token": "--text-body", "property": "color", "probe": "rgb(31, 97, 163)"},
37
+ {"token": "--text-inverse", "property": "color", "probe": "rgb(31, 97, 163)"},
38
+ {"token": "--text-lg", "property": "font-size", "probe": "41.375px"},
39
+ {"token": "--text-muted", "property": "color", "probe": "rgb(31, 97, 163)"},
40
+ {"token": "--text-sm", "property": "font-size", "probe": "41.375px"},
41
+ {"token": "--text-strong", "property": "color", "probe": "rgb(31, 97, 163)"},
42
+ {"token": "--text-xl", "property": "font-size", "probe": "41.375px"},
43
+ {"token": "--weight-medium", "property": "font-weight", "probe": "327"},
44
+ {"token": "--weight-normal", "property": "font-weight", "probe": "327"},
45
+ {"token": "--weight-semibold", "property": "font-weight", "probe": "327"},
46
+ {"token": "--z-overlay", "property": "z-index", "probe": "367"},
46
47
  ]
47
48
  } as const;
@@ -1,14 +1,4 @@
1
- import { useEffect, useState } from "react";
1
+ import { TokenPreview } from "@ds";
2
2
  import { fixture } from "./Foundation.fixture";
3
- export const scenario = { title: fixture.title, covers: [], tokens: fixture.tokens };
4
- export default function Scenario() {
5
- const [values, setValues] = useState<Record<string, string>>({});
6
- useEffect(() => {
7
- const read = () => { const style = getComputedStyle(document.documentElement); setValues(Object.fromEntries(fixture.tokens.map(name => [name, style.getPropertyValue(name).trim()]))); };
8
- read();
9
- const observer = new MutationObserver(read);
10
- observer.observe(document.documentElement, {attributes: true, attributeFilter: ["data-theme"]});
11
- return () => observer.disconnect();
12
- }, []);
13
- return <section><h1>{fixture.title}</h1><dl>{fixture.tokens.map(name => <div key={name}><dt><code>{name}</code></dt><dd>{values[name]}</dd></div>)}</dl></section>;
14
- }
3
+ export const scenario = { title: fixture.title, covers: ["TokenPreview"], samples: fixture.samples };
4
+ export default function Scenario() { return <TokenPreview {...fixture} />; }
@@ -9,5 +9,15 @@
9
9
  "--watch"
10
10
  ]
11
11
  }
12
+ },
13
+ "boundaries": {
14
+ "styleWrites": [
15
+ {
16
+ "file": "design-system/patterns/token-preview/TokenPreview.tsx",
17
+ "properties": [
18
+ "--ux-token-preview-value"
19
+ ]
20
+ }
21
+ ]
12
22
  }
13
23
  }
package/dist/check.mjs CHANGED
@@ -5,7 +5,7 @@ import postcss from "postcss";
5
5
  import valueParser from "postcss-value-parser";
6
6
  import { importViolation, isInside, loadConfig, sourceFiles, isDevelopmentFile, isTestFile } from "./config.mjs";
7
7
  import { inspectTokens, references } from "./tokens.mjs";
8
- import { foundationFiles, isSharedToken } from "./token-policy.mjs";
8
+ import { foundationFiles, foundationImportTarget, isSharedToken } from "./token-policy.mjs";
9
9
  import { inspectAssets } from "./assets.mjs";
10
10
  import { appImportViolation, showcaseImportViolation, checkComponentVariable, importResolver, projectCompilerOptions, isUiCssProperties, styleWriteViolation } from "./boundaries.mjs";
11
11
  import { inspectPublicProps } from "./public-props.mjs";
@@ -51,14 +51,7 @@ export function checkProject(directory, { trackingRoot } = {}) {
51
51
  const css = postcss.parse(readFileSync(foundationEntry, "utf8"), { from: foundationEntry });
52
52
  const imports = css.nodes.filter((node) => node.type !== "comment");
53
53
  const expected = foundationFiles.filter((name) => name !== "fonts.css" || existsSync(path.join(dsRoot, config.tokenDir, name))).map((name) => path.join(dsRoot, config.tokenDir, name));
54
- const actual = imports.map((node) => {
55
- if (node.type !== "atrule" || node.name !== "import" || node.nodes) return null;
56
- const parts = valueParser(node.params).nodes.filter((part) => part.type !== "space" && part.type !== "comment");
57
- if (parts.length !== 1) return null;
58
- const part = parts[0];
59
- const value = part.type === "string" ? part.value : part.type === "function" && part.value === "url" && part.nodes.length === 1 ? part.nodes[0].value : null;
60
- return value ? path.resolve(path.dirname(foundationEntry), value) : null;
61
- });
54
+ const actual = imports.map(node => foundationImportTarget(node, foundationEntry));
62
55
  if (actual.length !== expected.length || actual.some((file, index) => file !== expected[index] || !existsSync(file))) report(foundationEntry, "styles-entry", "Foundation CSS must import only fonts (when present), colors, typography, spacing, elevation, and base, in that order.");
63
56
  } catch (error) { report(foundationEntry, "css-syntax", error.reason ?? error.message, error.line ?? 1); }
64
57
  function checkCss(file, text, offset = { line: 1, column: 1 }) {
package/dist/init.mjs CHANGED
@@ -1,6 +1,7 @@
1
1
  import { existsSync, lstatSync, mkdirSync, mkdtempSync, readFileSync, readdirSync, renameSync, rmSync, rmdirSync, writeFileSync, chmodSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import { fileURLToPath } from "node:url";
4
+ import ts from "typescript";
4
5
  import { isInside, loadConfig, sourceFiles, supportedFrameworks } from "./config.mjs";
5
6
  import { createLintConfig } from "./lint-config.mjs";
6
7
  import { foundationFiles } from "./token-policy.mjs";
@@ -152,10 +153,16 @@ export function initProject(directory = process.cwd(), { toolPackage, packageMan
152
153
  });
153
154
  if (!existingTokens.length) {
154
155
  for (const name of ["Foundation.scenario.tsx", "Foundation.fixture.ts"]) {
155
- let content = readFileSync(path.join(starter, "design-system/tokens", name), "utf8");
156
- if (selectedFramework === "preact" && name === "Foundation.scenario.tsx") content = content.replace('from "react"', 'from "preact/hooks"');
156
+ const content = readFileSync(path.join(starter, "design-system/tokens", name), "utf8").replaceAll('"@ds"', JSON.stringify(config.publicAlias));
157
157
  addMissing(path.join(tokenRoot, name), content);
158
158
  }
159
+ for (const name of ["TokenPreview.tsx", "Scenario.tsx", "fixture.ts"]) {
160
+ let content = readFileSync(path.join(starter, "design-system/patterns/token-preview", name), "utf8");
161
+ if (selectedFramework === "preact") content = content.replace('from "react"', 'from "preact/hooks"');
162
+ addMissing(path.join(dsRoot, "patterns/token-preview", name), content.replaceAll('"@ds"', JSON.stringify(config.publicAlias)));
163
+ }
164
+ const reviewFile = path.relative(root, path.join(dsRoot, "patterns/token-preview/TokenPreview.tsx")).replaceAll("\\", "/");
165
+ if (!config.boundaries.styleWrites.some(item => item.file === reviewFile)) config.boundaries.styleWrites.push({ file: reviewFile, properties: ["--ux-token-preview-value"] });
159
166
  }
160
167
  tokenFiles.sort((a, b) => foundationFiles.indexOf(path.basename(a)) - foundationFiles.indexOf(path.basename(b)) || a.localeCompare(b));
161
168
  const stylesFile = path.join(dsRoot, config.stylesEntry);
@@ -165,7 +172,17 @@ export function initProject(directory = process.cwd(), { toolPackage, packageMan
165
172
  };
166
173
  addMissing(stylesFile, tokenFiles.map((file) => `@import ${JSON.stringify(relativeImport(stylesFile, file))};`).join("\n") + "\n");
167
174
  const entryFile = path.join(dsRoot, config.publicEntry);
168
- addMissing(entryFile, `// Export your project's public components and types here.\nimport ${JSON.stringify(relativeImport(entryFile, stylesFile))};\nexport {};\n`);
175
+ const reviewExport = !existingTokens.length ? `export { TokenPreview } from ${JSON.stringify(relativeImport(entryFile, path.join(dsRoot, "patterns/token-preview/TokenPreview")))};\n` : "";
176
+ addMissing(entryFile, `// Export your project's public components and types here.\nimport ${JSON.stringify(relativeImport(entryFile, stylesFile))};\n${reviewExport}`);
177
+ if (reviewExport && existsSync(entryFile) && !readFileSync(entryFile, "utf8").includes("patterns/token-preview/TokenPreview")) {
178
+ let current = readFileSync(entryFile, "utf8");
179
+ if (/\bTokenPreview\b/.test(current)) throw new Error("TokenPreview is already owned by this project. Add a token review scenario using its public API before initializing the foundation.");
180
+ const ast = ts.createSourceFile(entryFile, current, ts.ScriptTarget.Latest, true);
181
+ for (const statement of [...ast.statements].reverse()) if (ts.isExportDeclaration(statement) && !statement.moduleSpecifier && statement.exportClause && ts.isNamedExports(statement.exportClause) && !statement.exportClause.elements.length) {
182
+ current = current.slice(0, statement.getStart(ast)) + current.slice(statement.end);
183
+ }
184
+ updates.set(entryFile, current + "\n" + reviewExport);
185
+ }
169
186
  for (const section of ["components", "patterns", "composition"]) addMissing(path.join(dsRoot, section, ".gitkeep"), "");
170
187
  addMissing(path.join(dsRoot, "assets/brand/README.md"), readFileSync(path.join(starter, "design-system/assets/brand/README.md")));
171
188
  addMissing(path.join(dsRoot, "assets/fonts/.gitkeep"), "");
@@ -175,9 +192,9 @@ export function initProject(directory = process.cwd(), { toolPackage, packageMan
175
192
  else {
176
193
  const original = readFileSync(configFile, "utf8");
177
194
  const saved = JSON.parse(original);
178
- if (saved.framework === undefined) {
195
+ if (saved.framework === undefined || !existingTokens.length) {
179
196
  const indent = original.match(/\n([\t ]+)"/)?.[1] ?? " ";
180
- updates.set(configFile, `${JSON.stringify({ ...saved, framework: selectedFramework }, null, indent)}\n`);
197
+ updates.set(configFile, `${JSON.stringify({ ...saved, framework: selectedFramework, ...(!existingTokens.length ? { boundaries: config.boundaries } : {}) }, null, indent)}\n`);
181
198
  }
182
199
  }
183
200
  const lintConfigFile = path.join(root, ".ds/oxlintrc.json");
package/dist/mcp/apps.mjs CHANGED
@@ -12,7 +12,7 @@ const bridge = (project = false) => readFileSync(new URL(project ? "./project-wi
12
12
 
13
13
  export function previewApp(selection) {
14
14
  const script = readFileSync(new URL("./preview-widget.bundle.js", import.meta.url), "utf8").replaceAll("</script", "<\\/script");
15
- return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>ADS preview</title><style>html,body,#root{height:100%;margin:0}body{overflow:hidden}#ads-preview-status{position:fixed;inset:12px 12px auto;z-index:2147483647;padding:12px;border:1px solid GrayText;background:Canvas;color:CanvasText;font:13px system-ui;white-space:pre-wrap}#ads-preview-status[hidden]{display:none}</style></head><body><aside id="ads-preview-status" role="status" hidden></aside><div id="root"></div>${selection ? `<script type="application/json" id="ads-preview-selection">${safeJson(selection)}</script>` : ""}<script type="module">${script}</script></body></html>`;
15
+ return `<!doctype html><html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>ADS preview</title><style>html,body,#root{height:100%;margin:0}body{overflow:auto}#ads-preview-status{position:fixed;inset:12px 12px auto;z-index:2147483647;padding:12px;border:1px solid GrayText;background:Canvas;color:CanvasText;font:13px system-ui;white-space:pre-wrap}#ads-preview-status[hidden]{display:none}</style></head><body><aside id="ads-preview-status" role="status" hidden></aside><div id="root"></div>${selection ? `<script type="application/json" id="ads-preview-selection">${safeJson(selection)}</script>` : ""}<script type="module">${script}</script></body></html>`;
16
16
  }
17
17
 
18
18
  export function tokenWidgetData(inventory) {
@@ -44,7 +44,7 @@ export function projectApp(options, app, build) {
44
44
  if (statSync(file).size > 2 * 1024 * 1024) throw new Error("Preview exceeds 2 MiB. Build a focused component/pattern example.");
45
45
  const html = readFileSync(file, "utf8");
46
46
  if (!/<\/body\s*>/i.test(html) || /<script\b[^>]*\bsrc\s*=|<link\b[^>]*\brel\s*=\s*["']?stylesheet\b/i.test(html)) throw new Error("Preview must be built as self-contained HTML, with JavaScript and styles inlined.");
47
- const seed = { id: app.id, session: build?.session ?? null, revision: build?.current ? build.revision : null, state: build?.state ?? "unreported" };
47
+ const seed = { id: app.id, kind: app.kind, ...(app.samples ? { samples: app.samples } : {}), session: build?.session ?? null, revision: build?.current ? build.revision : null, state: build?.state ?? "unreported" };
48
48
  return html.replace(/<\/body\s*>/i, () => `<script type="application/json" id="ads-preview-seed">${safeJson(seed)}</script><script type="module">${bridge(true)}</script></body>`);
49
49
  }
50
50
 
@@ -56,7 +56,7 @@ Use this server for UI components/patterns, tokens/themes/branding, layout, inte
56
56
  3. Select one workflow with ads_skills, then ads_read_skill. ${route.skills.map((skill) => `${skill.task}: ${skill.name}`).join("; ")}. Native skill activation is host-controlled. Relative skill references resolve from its SKILL.md URI. Read support files only when relevant.
57
57
  4. ${route.preview} Token samples: ads_scenarios(kind: tokens), then ads_show_scenario or ads_show_tokens(category: id). Token source inspection: ads_tokens with a focused query. Registered previews: ${previews.map((preview) => `${preview.id}: ${preview.tool ?? "ads_show_scenario"}`).join(", ") || "none"}. Showcase URL: ${context.showcase.url ?? "not configured; use the project's showcase command"}.
58
58
  Inspection connects a public export to its owning source, available guides, token values and discovered preview IDs. Bounded tools keep discovery focused; explicit resource reads supply detail. Source metadata does not prove layout, interaction or motion. A scenario renders the real public implementation and its fixture context in either review surface.
59
- 5. Check ads_status.mutations. When enabled, discover one owner with ads_units, read relevant source ranges with ads_read_file and submit only a Git unified diff through ads_apply_patch({unit, patch, dryRun}). Include that owner's scenarios, fixtures and required public re-exports, never peer units. tokens is the foundation unit. Dry-run to inspect validation; apply only a passing patch. Fix rejected patches in the owning unit. Intentional project setup, registered guard/compiler code and exceptions use configuration; shared globals use foundation, guides documentation and assets their discovered assets/* unit. Keep mandatory ADS gates. Binary/large Git diffs may use patchFile under .ds/patches/ to avoid model-context bytes. In read-only mode, source edits use the client editor/filesystem tools. Reuse the local public API (${context.publicEntry}). Edit presentation in its ADS owner; keep application data/services/routing in application code and development fixtures beside their scenario; larger scenarios live in composition/. Linaria extracts owned styling. Inspect relevant tokens with ads_tokens and source/owning guides before edits.
59
+ 5. Check ads_status.mutations. When enabled, discover one owner with ads_units, read relevant source ranges with ads_read_file and submit only a Git unified diff through ads_apply_patch({unit, patch, dryRun}). Include that owner's scenarios, fixtures and required public re-exports, never peer units. tokens is the foundation unit; its manifest lists companionFiles for required canonical foundation imports when token files are created/deleted in the same patch. Preserve unrelated imports/comments and public-entry statements, including export {};. Dry-run to inspect validation; apply only a passing patch. Fix rejected patches in the owning unit; report unsupported coupled changes instead of bypassing rejection with direct writes. Intentional project setup, registered guard/compiler code and exceptions use configuration; shared globals use foundation, guides documentation and assets their discovered assets/* unit. Keep mandatory ADS gates. Binary/large Git diffs may use patchFile under .ds/patches/ to avoid model-context bytes. In read-only mode, source edits use the client editor/filesystem tools. Reuse the local public API (${context.publicEntry}). Edit presentation in its ADS owner; keep application data/services/routing in application code and development fixtures beside their scenario; larger scenarios live in composition/. Linaria extracts owned styling. Inspect relevant tokens with ads_tokens and source/owning guides before edits.
60
60
  6. ${route.validation} Registered commands: ${context.commands.map((command) => command.args?.join(" ") ?? `choose the project package manager for ${command.script}`).join("; ") || "none discovered; inspect the project's build setup"}. Run them from ${context.projectRoot}.
61
61
  7. After edits, inspect ads_status.build or ads://project/build explicitly. Running/unreported/old output is not a current successful preview. Fix diagnostics in owned source, wait for rebuilding and invoke the preview tool again. Notifications may not start an agent turn.
62
62