@reqvire-org/ads 0.2.9 → 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 (48) hide show
  1. package/README.md +2 -2
  2. package/dist/bootstrap-ds/ADS.md +19 -8
  3. package/dist/bootstrap-ds/README.md +13 -11
  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/README.md +1 -1
  22. package/dist/skills/ads-assimilate/SKILL.md +28 -12
  23. package/dist/skills/ads-assimilate/references/cli.md +2 -2
  24. package/dist/skills/ads-break-ui/SKILL.md +28 -12
  25. package/dist/skills/ads-break-ui/references/verification.md +4 -0
  26. package/dist/skills/ads-designer/SKILL.md +30 -14
  27. package/dist/skills/ads-designer/assets/bootstrap-ds/ADS.md +19 -8
  28. package/dist/skills/ads-designer/assets/bootstrap-ds/README.md +13 -11
  29. package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/index.ts +1 -1
  30. package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/patterns/token-preview/Scenario.tsx +4 -0
  31. package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/patterns/token-preview/TokenPreview.tsx +31 -0
  32. package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/patterns/token-preview/fixture.ts +8 -0
  33. package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/tokens/Foundation.fixture.ts +44 -43
  34. package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/tokens/Foundation.scenario.tsx +3 -13
  35. package/dist/skills/ads-designer/assets/bootstrap-ds/ds.config.json +10 -0
  36. package/dist/skills/ads-designer/references/cli.md +2 -2
  37. package/dist/skills/ads-import/SKILL.md +28 -12
  38. package/dist/skills/ads-import/references/cli.md +2 -2
  39. package/dist/skills/ads-motion/SKILL.md +28 -12
  40. package/dist/token-policy.mjs +12 -0
  41. package/dist/token-samples.mjs +8 -0
  42. package/dist/units.mjs +34 -10
  43. package/docs/ADS.md +23 -9
  44. package/docs/ARCHITECTURE.md +12 -0
  45. package/docs/CHANGELOG.md +16 -1
  46. package/docs/GUIDE.md +16 -13
  47. package/docs/MCP.md +44 -2
  48. package/package.json +5 -5
@@ -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
  }
@@ -55,7 +55,7 @@ If no published distribution exists, build and pack the tooling checkout with
55
55
  From inside an existing test project:
56
56
 
57
57
  ```sh
58
- ads_tool_package="/absolute/path/to/reqvire-org-ads-0.3.0.tgz"
58
+ ads_tool_package="/absolute/path/to/reqvire-org-ads-0.3.1.tgz"
59
59
  npx --yes --package "$ads_tool_package" ads --help
60
60
  npx --yes --package "$ads_tool_package" ads init --framework react --tool-package "$ads_tool_package"
61
61
  npm install
@@ -93,7 +93,7 @@ component rules through the project's plugin adapter and `ds.config.json`, then
93
93
  run its actual build. Use `lint-config` directly for custom pipeline integration
94
94
  or debugging, not as a separate routine setup step.
95
95
 
96
- Use ADS 0.3 or later for the shared-resource preview compiler. `mcp` starts stdio
96
+ Use ADS 0.3.1 or later for the shared-resource preview compiler. `mcp` starts stdio
97
97
  plus its local website; `showcase` starts the browser-only MCP runtime. Open the existing runtime URL from `ads_status` when
98
98
  connected; otherwise use `npm run dev:showcase` or the installed `ads showcase`.
99
99
  The tool client discovers entries and renders MCP Apps. Init adds the launch
@@ -21,9 +21,15 @@ relevant ranges with `ads_read_file` and send only a Git unified diff through
21
21
  `ads_apply_patch({ unit, patch, dryRun })`. A unit includes its own implementation,
22
22
  scenarios and fixtures; imported peers remain separate. `tokens` is the token
23
23
  foundation unit. Unit discovery and file ownership are distinct from preview IDs.
24
- Include only that owner's required re-exports in the public entry. Use
25
- `dryRun: true` to inspect validation; applying re-runs the gates. Fix rejected
26
- patches in their owner. Shared styles/helpers use `foundation`, root guides use
24
+ Include only that owner's required re-exports in the public entry; preserve
25
+ unrelated statements. Its real re-export may accompany removal of a redundant
26
+ `export {};` placeholder; do not add empty exports. A `tokens` manifest lists the foundation
27
+ entry in `companionFiles`: when creating/deleting a canonical token file such as
28
+ `fonts.css`, include only its required import change there in the same patch.
29
+ Preserve other imports/comments and the canonical order. Use `dryRun: true` to
30
+ inspect validation; applying re-runs the gates. Fix rejected patches in their
31
+ owner. Report unsupported coupled changes instead of bypassing a rejection with
32
+ direct source writes. Shared styles/helpers use `foundation`, root guides use
27
33
  `documentation`, and branding/fonts use their discovered `assets/*` unit.
28
34
  Intentional configuration, rule exceptions, registered guard code and compiler
29
35
  changes use `configuration`; validate the proposed settings and keep mandatory
@@ -266,15 +272,25 @@ entry independently and verify real behavior at narrow/wide sizes in both themes
266
272
  The older `showcase-coverage` policy separately checks application references.
267
273
 
268
274
  Token previews use that same scenario pipeline. Colocate a token scenario and
269
- fixture under the configured token directory. Use static metadata
270
- `{ title: "Typography", covers: ["TokenSamples"], tokens: fixture.tokens }` with
271
- literal const fixture fields; names/sample text are fixture data, values come
272
- from the real foundation CSS. Reuse a public project review pattern for styled
273
- samples. The minimal starter's foundation value scenario can have `covers: []`.
274
- Every declared token needs reachable scenario coverage; adding/removing a token
275
- requires fixture updates. Fix `token-scenario-reference`, `token-scenario-render`
276
- and `token-scenario-coverage` findings. Metadata and unused fixture fields do not
277
- count. Discover exact token IDs with `ads_scenarios({kind:"tokens"})`; mount them
275
+ fixture under the configured token directory. Declare metadata
276
+ `{ title: fixture.title, covers: ["TokenSamples"], samples: fixture.samples }`.
277
+ The renderer name is project-owned; use its real public API. Each literal fixture
278
+ sample declares `{ token, property, probe }`: a real token, the CSS property its
279
+ sample demonstrates, and a valid test value different from that token's current
280
+ value. Pass `samples` into the rendered public review pattern. Names, sample text,
281
+ bindings and probes are fixture data; displayed values come from foundation CSS.
282
+ There is no property catalogue, token-name mapping or probe palette in ADS.
283
+ Every token needs sample coverage; fabricated tokens, missing bindings/probes,
284
+ unused fixtures and value-only lists fail the static gates. `covers: []` is invalid.
285
+ During App rendering, the browser checks sample markers `data-ads-token` and
286
+ `data-ads-property`, validates CSS with its own `CSS.supports`, and temporarily
287
+ probes each token to verify that the declared sample property responds. It restores
288
+ values immediately. Missing, hidden, invalid or hardcoded samples produce a preview
289
+ error instead of a successful review. These browser checks apply to the mounted
290
+ scenario/theme, not every application state or pixel-level design quality.
291
+ Keep styles in the project renderer; do not add inline-style exemptions to scenarios.
292
+ Fix `token-scenario-reference`, `token-scenario-render` and `token-scenario-coverage`
293
+ findings. Metadata and unused fixture fields do not count. Discover exact token IDs with `ads_scenarios({kind:"tokens"})`; mount them
278
294
  with `ads_show_scenario({id})` or `ads_show_tokens({category:id})`. With the shared
279
295
  resource compiler, omitting category opens the first project token scenario.
280
296
  Legacy compilers retain the generic definition inspector. Use `ads_tokens` for
@@ -55,7 +55,7 @@ If no published distribution exists, build and pack the tooling checkout with
55
55
  From inside an existing test project:
56
56
 
57
57
  ```sh
58
- ads_tool_package="/absolute/path/to/reqvire-org-ads-0.3.0.tgz"
58
+ ads_tool_package="/absolute/path/to/reqvire-org-ads-0.3.1.tgz"
59
59
  npx --yes --package "$ads_tool_package" ads --help
60
60
  npx --yes --package "$ads_tool_package" ads init --framework react --tool-package "$ads_tool_package"
61
61
  npm install
@@ -93,7 +93,7 @@ component rules through the project's plugin adapter and `ds.config.json`, then
93
93
  run its actual build. Use `lint-config` directly for custom pipeline integration
94
94
  or debugging, not as a separate routine setup step.
95
95
 
96
- Use ADS 0.3 or later for the shared-resource preview compiler. `mcp` starts stdio
96
+ Use ADS 0.3.1 or later for the shared-resource preview compiler. `mcp` starts stdio
97
97
  plus its local website; `showcase` starts the browser-only MCP runtime. Open the existing runtime URL from `ads_status` when
98
98
  connected; otherwise use `npm run dev:showcase` or the installed `ads showcase`.
99
99
  The tool client discovers entries and renders MCP Apps. Init adds the launch
@@ -42,9 +42,15 @@ relevant ranges with `ads_read_file` and send only a Git unified diff through
42
42
  `ads_apply_patch({ unit, patch, dryRun })`. A unit includes its own implementation,
43
43
  scenarios and fixtures; imported peers remain separate. `tokens` is the token
44
44
  foundation unit. Unit discovery and file ownership are distinct from preview IDs.
45
- Include only that owner's required re-exports in the public entry. Use
46
- `dryRun: true` to inspect validation; applying re-runs the gates. Fix rejected
47
- patches in their owner. Shared styles/helpers use `foundation`, root guides use
45
+ Include only that owner's required re-exports in the public entry; preserve
46
+ unrelated statements. Its real re-export may accompany removal of a redundant
47
+ `export {};` placeholder; do not add empty exports. A `tokens` manifest lists the foundation
48
+ entry in `companionFiles`: when creating/deleting a canonical token file such as
49
+ `fonts.css`, include only its required import change there in the same patch.
50
+ Preserve other imports/comments and the canonical order. Use `dryRun: true` to
51
+ inspect validation; applying re-runs the gates. Fix rejected patches in their
52
+ owner. Report unsupported coupled changes instead of bypassing a rejection with
53
+ direct source writes. Shared styles/helpers use `foundation`, root guides use
48
54
  `documentation`, and branding/fonts use their discovered `assets/*` unit.
49
55
  Intentional configuration, rule exceptions, registered guard code and compiler
50
56
  changes use `configuration`; validate the proposed settings and keep mandatory
@@ -156,15 +162,25 @@ entry independently and verify real behavior at narrow/wide sizes in both themes
156
162
  The older `showcase-coverage` policy separately checks application references.
157
163
 
158
164
  Token previews use that same scenario pipeline. Colocate a token scenario and
159
- fixture under the configured token directory. Use static metadata
160
- `{ title: "Typography", covers: ["TokenSamples"], tokens: fixture.tokens }` with
161
- literal const fixture fields; names/sample text are fixture data, values come
162
- from the real foundation CSS. Reuse a public project review pattern for styled
163
- samples. The minimal starter's foundation value scenario can have `covers: []`.
164
- Every declared token needs reachable scenario coverage; adding/removing a token
165
- requires fixture updates. Fix `token-scenario-reference`, `token-scenario-render`
166
- and `token-scenario-coverage` findings. Metadata and unused fixture fields do not
167
- count. Discover exact token IDs with `ads_scenarios({kind:"tokens"})`; mount them
165
+ fixture under the configured token directory. Declare metadata
166
+ `{ title: fixture.title, covers: ["TokenSamples"], samples: fixture.samples }`.
167
+ The renderer name is project-owned; use its real public API. Each literal fixture
168
+ sample declares `{ token, property, probe }`: a real token, the CSS property its
169
+ sample demonstrates, and a valid test value different from that token's current
170
+ value. Pass `samples` into the rendered public review pattern. Names, sample text,
171
+ bindings and probes are fixture data; displayed values come from foundation CSS.
172
+ There is no property catalogue, token-name mapping or probe palette in ADS.
173
+ Every token needs sample coverage; fabricated tokens, missing bindings/probes,
174
+ unused fixtures and value-only lists fail the static gates. `covers: []` is invalid.
175
+ During App rendering, the browser checks sample markers `data-ads-token` and
176
+ `data-ads-property`, validates CSS with its own `CSS.supports`, and temporarily
177
+ probes each token to verify that the declared sample property responds. It restores
178
+ values immediately. Missing, hidden, invalid or hardcoded samples produce a preview
179
+ error instead of a successful review. These browser checks apply to the mounted
180
+ scenario/theme, not every application state or pixel-level design quality.
181
+ Keep styles in the project renderer; do not add inline-style exemptions to scenarios.
182
+ Fix `token-scenario-reference`, `token-scenario-render` and `token-scenario-coverage`
183
+ findings. Metadata and unused fixture fields do not count. Discover exact token IDs with `ads_scenarios({kind:"tokens"})`; mount them
168
184
  with `ads_show_scenario({id})` or `ads_show_tokens({category:id})`. With the shared
169
185
  resource compiler, omitting category opens the first project token scenario.
170
186
  Legacy compilers retain the generic definition inspector. Use `ads_tokens` for
@@ -1,4 +1,6 @@
1
1
  import { readFileSync } from "node:fs";
2
+ import path from "node:path";
3
+ import valueParser from "postcss-value-parser";
2
4
 
3
5
  export const tokenContract = JSON.parse(readFileSync(new URL("./contracts/tokens.json", import.meta.url), "utf8"));
4
6
 
@@ -8,3 +10,13 @@ export function isSharedToken(name) {
8
10
  }
9
11
 
10
12
  export const foundationFiles = ["fonts.css", "colors.css", "typography.css", "spacing.css", "elevation.css", "base.css"];
13
+
14
+ /** The foundation entry accepts only a single, unconditional local import. */
15
+ export function foundationImportTarget(node, file) {
16
+ if (node.type !== "atrule" || node.name !== "import" || node.nodes) return null;
17
+ const parts = valueParser(node.params).nodes.filter(part => part.type !== "space" && part.type !== "comment");
18
+ if (parts.length !== 1) return null;
19
+ const part = parts[0];
20
+ const value = part.type === "string" ? part.value : part.type === "function" && part.value === "url" && part.nodes.length === 1 ? part.nodes[0].value : null;
21
+ return value ? path.resolve(path.dirname(file), value) : null;
22
+ }
@@ -0,0 +1,8 @@
1
+ /** Projects declare bindings and verification inputs; ADS supplies no sample catalogue. */
2
+ export function validTokenSamples(samples) {
3
+ return Array.isArray(samples) && samples.length > 0 && samples.every(sample => sample && typeof sample === "object"
4
+ && typeof sample.token === "string" && /^--[\w-]+$/.test(sample.token)
5
+ && typeof sample.property === "string" && /^-?[a-z][a-z0-9-]*$/.test(sample.property)
6
+ && typeof sample.probe === "string" && sample.probe.trim().length > 0)
7
+ && new Set(samples.map(sample => `${sample.token}:${sample.property}`)).size === samples.length;
8
+ }
package/dist/units.mjs CHANGED
@@ -1,9 +1,11 @@
1
1
  import { existsSync, readFileSync, readdirSync } from "node:fs";
2
2
  import path from "node:path";
3
3
  import ts from "typescript";
4
+ import postcss from "postcss";
4
5
  import { isInside } from "./config.mjs";
5
6
  import { importResolver } from "./boundaries.mjs";
6
7
  import { isProjectDocument, projectGuideFiles } from "./project-documents.mjs";
8
+ import { foundationFiles, foundationImportTarget } from "./token-policy.mjs";
7
9
 
8
10
  const source = /\.[cm]?[jt]sx?$/;
9
11
  const slash = file => file.replaceAll("\\", "/");
@@ -89,6 +91,7 @@ export function inspectUnits(options) {
89
91
  }))].sort((a, b) => b.length - a.length);
90
92
  const publicEntry = slash(path.relative(dsRoot, path.join(dsRoot, config.publicEntry)));
91
93
  const tokenDir = slash(path.relative(dsRoot, path.join(dsRoot, config.tokenDir)));
94
+ const stylesEntry = slash(path.relative(dsRoot, path.join(dsRoot, config.stylesEntry)));
92
95
  function owner(relative) {
93
96
  const projectFile = slash(path.relative(root, path.resolve(dsRoot, relative)));
94
97
  if (setup.has(projectFile)) return "configuration";
@@ -107,6 +110,8 @@ export function inspectUnits(options) {
107
110
  const units = new Map();
108
111
  units.set("configuration", { id: "configuration", kind: "configuration", files: [...setup].filter(file => existsSync(path.join(root, file))).sort() });
109
112
  for (const id of ["foundation", "documentation", "tokens", "assets/brand", "assets/fonts"]) units.set(id, { id, kind: id.split("/")[0], files: [] });
113
+ units.get("tokens").companionFiles = [{ path: slash(path.relative(root, path.join(dsRoot, stylesEntry))),
114
+ allowedChanges: "Only add/remove canonical foundation imports required by token foundation files created/deleted in this patch; preserve other imports and comments." }];
110
115
  units.get("documentation").files = projectGuideFiles(root);
111
116
  for (const relative of files) {
112
117
  const id = owner(relative);
@@ -115,7 +120,7 @@ export function inspectUnits(options) {
115
120
  const file = slash(path.relative(root, path.join(dsRoot, relative)));
116
121
  if (!units.get(id).files.includes(file)) units.get(id).files.push(file);
117
122
  }
118
- return { units: [...units.values()].sort((a, b) => a.id.localeCompare(b.id)), owner, publicEntry,
123
+ return { units: [...units.values()].sort((a, b) => a.id.localeCompare(b.id)), owner, publicEntry, stylesEntry, tokenDir,
119
124
  relative: file => slash(path.relative(dsRoot, path.resolve(root, file))) };
120
125
  }
121
126
 
@@ -130,6 +135,7 @@ export function checkUnitFiles(options, unit, changes, inventory = inspectUnits(
130
135
  const relative = inventory.relative(change.path);
131
136
  if (allowNew && unit === "configuration") continue; // The candidate must register every setup/extension file.
132
137
  if (relative === inventory.publicEntry) continue;
138
+ if (unit === "tokens" && relative === inventory.stylesEntry) continue; // Controlled foundation import bookkeeping.
133
139
  if (allowNew && change.originalContent === null && (relative.startsWith(unit + "/") || relative.startsWith(unit + "."))) continue;
134
140
  const owner = inventory.owner(relative);
135
141
  if (owner !== unit) throw fail(`This patch targets ${unit}; ${change.path} belongs to ${owner ?? "a different ADS layer"}. Submit a separate unit patch.`, change.path);
@@ -147,22 +153,40 @@ export function checkUnitCandidate(afterOptions, unit, changes, before) {
147
153
  if (unit === "configuration" && (entry.id === "configuration" || after.owner(relative) === "configuration")) continue;
148
154
  if (after.owner(relative) !== entry.id) throw fail("A patch cannot change ownership of existing peer units. Choose an unoccupied owner path.", file);
149
155
  }
156
+ const foundationChange = changes.find(change => before.relative(change.path) === before.stylesEntry);
157
+ if (unit === "tokens" && foundationChange) {
158
+ const { originalContent: current, content: proposed } = foundationChange;
159
+ if (typeof current !== "string" || typeof proposed !== "string") throw fail("A token patch cannot create or delete the foundation entry.", foundationChange.path);
160
+ const canonical = new Set(foundationFiles.map(name => slash(path.join(before.tokenDir, name))));
161
+ const affected = new Set(changes.filter(change => (change.originalContent === null || change.content === null) && canonical.has(before.relative(change.path)))
162
+ .map(change => path.join(afterOptions.dsRoot, before.relative(change.path))));
163
+ if (!affected.size) throw fail("Foundation companion changes require creating or deleting a canonical token foundation file in the same token patch.", foundationChange.path);
164
+ const file = path.join(afterOptions.dsRoot, afterOptions.config.stylesEntry);
165
+ function unrelated(content) {
166
+ try {
167
+ return postcss.parse(content, { from: file }).nodes.filter(node => !affected.has(foundationImportTarget(node, file))).map(node => node.toString());
168
+ } catch (error) { throw fail(`Invalid foundation import companion: ${error.reason ?? error.message}`, foundationChange.path); }
169
+ }
170
+ if (JSON.stringify(unrelated(current)) !== JSON.stringify(unrelated(proposed))) throw fail("Foundation companion patches may change only canonical imports for token files created/deleted in this patch; preserve all other imports and comments.", foundationChange.path);
171
+ }
150
172
  const exportChange = changes.find(change => before.relative(change.path) === before.publicEntry);
151
173
  if (exportChange) {
152
174
  if (!["components", "patterns"].includes(unit.split("/")[0])) throw fail("Only a component/pattern unit may update its public re-exports.", exportChange.path);
153
175
  const current = exportChange.originalContent ?? "", proposed = exportChange.content;
154
176
  if (proposed === null) throw fail("The shared public entry cannot be deleted.", exportChange.path);
155
177
  const resolve = importResolver(afterOptions);
156
- function unrelated(content) {
157
- const file = path.join(afterOptions.dsRoot, afterOptions.config.publicEntry);
158
- const ast = ts.createSourceFile(file, content, ts.ScriptTarget.Latest, true);
159
- return ast.statements.filter(statement => {
160
- if (!ts.isExportDeclaration(statement) || !statement.moduleSpecifier || !ts.isStringLiteralLike(statement.moduleSpecifier)) return true;
161
- const targets = resolve(statement.moduleSpecifier.text, file);
162
- return !targets.length || targets.some(target => !isInside(afterOptions.dsRoot, target) || after.owner(slash(path.relative(afterOptions.dsRoot, target))) !== unit);
163
- }).map(statement => statement.getText(ast)).join("\n");
178
+ const file = path.join(afterOptions.dsRoot, afterOptions.config.publicEntry);
179
+ const original = ts.createSourceFile(file, current, ts.ScriptTarget.Latest, true);
180
+ const candidate = ts.createSourceFile(file, proposed, ts.ScriptTarget.Latest, true);
181
+ const emptyExport = statement => ts.isExportDeclaration(statement) && !statement.moduleSpecifier && statement.exportClause && ts.isNamedExports(statement.exportClause) && !statement.exportClause.elements.length;
182
+ function ownedExport(statement) {
183
+ if (!ts.isExportDeclaration(statement) || !statement.moduleSpecifier || !ts.isStringLiteralLike(statement.moduleSpecifier)) return false;
184
+ const targets = resolve(statement.moduleSpecifier.text, file);
185
+ return targets.length && targets.every(target => isInside(afterOptions.dsRoot, target) && after.owner(slash(path.relative(afterOptions.dsRoot, target))) === unit);
164
186
  }
165
- if (unrelated(current) !== unrelated(proposed)) throw fail("Public-entry patches may change only re-exports from the selected unit; preserve all other statements.", exportChange.path);
187
+ const cleanup = candidate.statements.some(ownedExport) && !candidate.statements.some(emptyExport);
188
+ const unrelated = ast => ast.statements.filter(statement => !ownedExport(statement) && !(cleanup && emptyExport(statement))).map(statement => statement.getText(ast)).join("\n");
189
+ if (unrelated(original) !== unrelated(candidate)) throw fail("Public-entry patches may change only re-exports from the selected unit and remove a redundant empty export alongside its real re-export; preserve all other statements.", exportChange.path);
166
190
  }
167
191
  if (!before.units.some(entry => entry.id === unit) && !after.units.some(entry => entry.id === unit)) throw fail("The patched files do not establish the selected ADS unit.");
168
192
  }
package/docs/ADS.md CHANGED
@@ -13,6 +13,8 @@ before application. This includes
13
13
  foundation, documentation, original assets and an explicit configuration unit for
14
14
  project settings, registered guard code and compiler integration. Configuration
15
15
  patches validate the proposed policy; mandatory core gates remain enabled.
16
+ Required public re-exports and canonical foundation import changes may accompany
17
+ their owner under the narrow companion rules in the MCP editing contract.
16
18
  Source checks do not establish visual correctness; normal lint/build gates
17
19
  remain required for edits made through other tools.
18
20
  See the [MCP editing contract](MCP.md#guarded-editing).
@@ -89,13 +91,24 @@ design-system/
89
91
  Flat owners may use `Button.scenario.tsx` and `Button.fixture.ts` instead.
90
92
  `Scenario.tsx` exports a default independently mountable view and literal metadata
91
93
  `export const scenario = { title: "Button states", covers: ["Button"] };`.
92
- Token entries under the configured token directory also declare `tokens`:
93
- `export const scenario = { title: "Typography", covers: ["TokenSamples"], tokens: fixture.tokens };`.
94
- Metadata may read literal const fixture fields; it cannot run expressions,
95
- functions, wildcard lists or token discovery. The same fixture list must be
96
- consumed by the default entry. `covers: []` is valid for a minimal token value
97
- inspection entry without public UI; styled samples use a real public review
98
- pattern. Names and sample text belong to the fixture, values to foundation CSS.
94
+ Token entries under the configured token directory declare
95
+ `export const scenario = { title: fixture.title, covers: ["TokenSamples"], samples: fixture.samples };`.
96
+ `TokenSamples` is any project-owned public review pattern, not a required name.
97
+ Its colocated fixture declares literal `samples: [{ token, property, probe }]`.
98
+ Each token must exist and have sample coverage; duplicate bindings, missing probes,
99
+ empty covers and value-only inspectors fail. The default entry passes the declared
100
+ samples to a rendered public pattern's `samples` prop. No token-name/property mapping
101
+ or sample values are embedded in the tool. Display values come from foundation CSS;
102
+ probes are isolated verification inputs owned by the fixture.
103
+
104
+ The renderer marks the actual styled element with `data-ads-token` and
105
+ `data-ads-property`. In a browser, the App validates the declared property/value and
106
+ probe using `CSS.supports`, checks the sample is rendered, then temporarily changes
107
+ that token to its fixture probe. Its computed sample property must respond. Values
108
+ are restored before yielding to painting; hardcoded lookalikes fail. Source gates
109
+ prove the contract structure, not successful rendering. The browser check covers
110
+ the mounted scenario and theme; visual quality and other states still need review.
111
+
99
112
  It imports real public UI through `@ds` and its local fixture. The adapter supplies
100
113
  initial state, callbacks and simulated services. The default entry needs no
101
114
  mandatory caller inputs or prior page initialization. Related parts may share a
@@ -279,8 +292,9 @@ Validate accepted and rejected cases, configuration, and false-positive limits.
279
292
  Root [bootstrap-ds/](../bootstrap-ds/README.md) is the canonical empty scaffold.
280
293
  Its identical copy under `skills/ads-designer/assets/bootstrap-ds/` makes the
281
294
  skill self-contained. Run `npm run sync:skill-starter` after edits and
282
- `npm run check:skill-starter` to verify the copy. Both contain empty component
283
- and pattern directories, not a catalogue.
295
+ `npm run check:skill-starter` to verify the copy. Both contain empty product component/composition directories and a minimal project-owned
296
+ token review pattern. The scaffold pattern can be renamed or replaced; the tool
297
+ does not require that name or its styles and ships no product UI catalogue.
284
298
 
285
299
  Tool releases should version contract changes, rules, skills, bootstrap support,
286
300
  and migration guidance. Existing project implementations and external source
@@ -273,6 +273,11 @@ replace the managed process while retaining the MCP connection. Normal build gat
273
273
  remain in place for source changes made through other editors; preview review remains
274
274
  necessary after either editing path.
275
275
 
276
+ Required shared-entry bookkeeping can accompany its owner: component/pattern
277
+ re-exports and canonical foundation imports for token files created or deleted in
278
+ the same patch. ADS restricts these companion edits and validates the complete
279
+ candidate before writing, so coupled changes do not need a guard bypass.
280
+
276
281
  The architecture makes that inspection practical. A public export has an owning
277
282
  source and addressable preview coverage. Token definitions expose its visual
278
283
  vocabulary; local guides explain contracts when present. An agent can discover a
@@ -309,3 +314,10 @@ For concrete instructions, use:
309
314
  - [Tool guide](GUIDE.md) for prerequisites, supported formats, configuration, and build wiring.
310
315
  - [Working example guide](../design-system/README.md) for Linaria composition, extraction, and project commands.
311
316
  - Component-local READMEs for APIs, lifecycle, tests, and provenance.
317
+
318
+ Token preview completeness has two checks. Static guards require explicit fixture
319
+ bindings (`token`, CSS `property`, and project-selected `probe`) consumed by a public
320
+ review pattern. The App then verifies rendered samples in the actual browser by
321
+ probing those bindings and restoring them. The tool supplies no token/property
322
+ classification or visual sample palette. A source build alone is not a rendered
323
+ verification, and the browser check does not judge design quality.
package/docs/CHANGELOG.md CHANGED
@@ -1,6 +1,21 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## 0.3.1
4
+
5
+ - Stop generating redundant empty public exports. Allow an owning unit's public
6
+ re-export companion to remove an obsolete placeholder without changing peers.
7
+ - Require token fixtures to declare real CSS sample bindings and project-owned
8
+ probes, consumed by a public review pattern. Reject empty covers and plain lists.
9
+ Apps verify rendered property dependencies in the browser before reporting ready.
10
+ The tool infers no token roles and contains no property/probe catalogue. Existing
11
+ projects need a token scenario migration and preview rebuild.
12
+ - Restore native scrolling inside resource preview Apps for scenarios taller or
13
+ wider than their viewport, while retaining full-height composition layouts.
14
+ - Allow a `tokens` mutation to add/remove the required foundation import together
15
+ with its canonical token file. Discover the entry through `ads_units` companion
16
+ metadata; preserve unrelated imports/comments and validate before writing.
17
+
18
+ ## 0.3.0
4
19
 
5
20
  - Rebrand the tool as ADS — Agentic Design System: `@reqvire-org/ads`, the `ads`
6
21
  CLI, `ads_*` MCP tools, `ads://` resources and `ads-*` skills. Keep project
package/docs/GUIDE.md CHANGED
@@ -12,7 +12,7 @@ fonts, mock screens, or UI runtime dependency.
12
12
  Linaria is required for component and pattern styling. Projects choose their
13
13
  UI framework and visual design; `init` preserves framework configuration and
14
14
  adds no React runtime dependency. See the
15
- [architecture](https://github.com/reqvire-org/design-system/blob/main/docs/ARCHITECTURE.md#linaria-is-the-implementation-mechanism)
15
+ [architecture](https://github.com/reqvire-org/ads/blob/main/docs/ARCHITECTURE.md#linaria-is-the-implementation-mechanism)
16
16
  for the rationale.
17
17
 
18
18
  Current source checking supports `.js`, `.jsx`, `.ts`, `.tsx`, their module
@@ -45,9 +45,9 @@ project's existing build:
45
45
  result. Static source checks do not establish complete extraction or
46
46
  unused-style removal.
47
47
 
48
- The [empty starter's dependencies](https://github.com/reqvire-org/design-system/blob/main/bootstrap-ds/package.json),
49
- [entry build](https://github.com/reqvire-org/design-system/blob/main/bootstrap-ds/vite.config.ts),
50
- and [preview compiler](https://github.com/reqvire-org/design-system/blob/main/bootstrap-ds/scripts/build-mcp-previews.mjs)
48
+ The [empty starter's dependencies](https://github.com/reqvire-org/ads/blob/main/bootstrap-ds/package.json),
49
+ [entry build](https://github.com/reqvire-org/ads/blob/main/bootstrap-ds/vite.config.ts),
50
+ and [preview compiler](https://github.com/reqvire-org/ads/blob/main/bootstrap-ds/scripts/build-mcp-previews.mjs)
51
51
  provide the React/Vite setup. Merge the needed integration into an existing
52
52
  project instead of replacing its configuration.
53
53
 
@@ -78,8 +78,8 @@ attributes when the project/file JSX runtime or factory is native Preact. Preact
78
78
  and exports DOM types such as `HTMLAttributes` directly; Preact 10's `JSX` type
79
79
  namespace is also recognized by guards. See the [Preact upgrade guide](https://preactjs.com/guide/v11/upgrade-guide/).
80
80
 
81
- The [working example](https://github.com/reqvire-org/design-system/tree/main/design-system)
82
- and its [compiler adapter](https://github.com/reqvire-org/design-system/blob/main/scripts/build-mcp-previews.mjs)
81
+ The [working example](https://github.com/reqvire-org/ads/tree/main/design-system)
82
+ and its [compiler adapter](https://github.com/reqvire-org/ads/blob/main/scripts/build-mcp-previews.mjs)
83
83
  show the native setup. When converting the copied React starter to Preact,
84
84
  update its runtime dependencies, JSX settings, framework transform, mount adapter,
85
85
  and existing `framework` field together. Change the existing foundation scenario's
@@ -215,7 +215,7 @@ refresh generated resource manifests. Existing `DS.md` project guides are still
215
215
  discovered; the tool's contract and new scaffold use `ADS.md`.
216
216
 
217
217
  npm has no in-place package rename: publish `@reqvire-org/ads` as a new package.
218
- Set up its own trusted publisher for `reqvire-org/design-system`, workflow
218
+ Set up its own trusted publisher for `reqvire-org/ads`, workflow
219
219
  `release.yml`, allowing `npm publish` and `npm dist-tag`. Once ADS is available,
220
220
  deprecate the old package with a migration message:
221
221
 
@@ -302,7 +302,7 @@ For iterative development, use the linking workflow above. From the repository r
302
302
  npm install
303
303
  npm run build
304
304
  npm run pack:tool
305
- ads_tool_package="$PWD/output/npm/reqvire-org-ads-0.3.0.tgz"
305
+ ads_tool_package="$PWD/output/npm/reqvire-org-ads-0.3.1.tgz"
306
306
  ```
307
307
 
308
308
  Then change into an existing test project containing `package.json`:
@@ -430,19 +430,22 @@ export default function Scenario() { return <Button>{fixture.label}</Button>; }
430
430
  IDs derive from filenames; do not maintain another registry or hand-assigned IDs.
431
431
  Token scenarios live beside foundation CSS under the configured token directory.
432
432
  The starter includes `tokens/Foundation.scenario.tsx` and its local fixture for
433
- plain value inspection without a component library. For styled samples, use a
434
- public project review pattern and keep its presentation in that pattern:
433
+ a project-owned token review pattern. Keep its presentation in that pattern:
435
434
 
436
435
  ```tsx
437
436
  import { TokenSamples } from "@ds";
438
437
  import { fixture } from "./Typography.fixture";
439
- export const scenario = { title: fixture.title, covers: ["TokenSamples"], tokens: fixture.tokens };
438
+ export const scenario = { title: fixture.title, covers: ["TokenSamples"], samples: fixture.samples };
440
439
  export default function Scenario() { return <TokenSamples {...fixture} />; }
441
440
  ```
442
441
 
443
442
  `TokenSamples` is an example project-owned pattern, not an npm tool component.
444
- The fixture defines literal names and sample text, with no copied token values.
445
- Add/remove names when foundation tokens change. `token-scenario-reference`,
443
+ The fixture defines literal `samples: [{ token, property, probe }]` and sample text.
444
+ Token values come from foundation CSS; the fixture owns a distinct, valid CSS probe
445
+ for verifying each declared property. Add/remove samples when tokens change.
446
+ The renderer marks its styled elements with `data-ads-token` and `data-ads-property`.
447
+ The App probes their computed properties in the browser; name/value labels alone
448
+ do not pass. No CSS-property list or test palette is built into ADS. `token-scenario-reference`,
446
449
  `token-scenario-render` and `token-scenario-coverage` enforce real references and
447
450
  coverage for every token. The same source/style/import checks apply under tokens/.
448
451