@reqvire-org/ads 0.3.0 → 0.3.2

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 (56) hide show
  1. package/README.md +1 -1
  2. package/dist/bootstrap-ds/ADS.md +47 -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/design-system/tokens/base.css +4 -1
  11. package/dist/bootstrap-ds/design-system/tokens/colors.css +2 -2
  12. package/dist/bootstrap-ds/ds.config.json +10 -0
  13. package/dist/check.mjs +29 -13
  14. package/dist/contracts/foundation.json +211 -0
  15. package/dist/foundation.mjs +114 -0
  16. package/dist/init.mjs +22 -5
  17. package/dist/mcp/apps.mjs +2 -2
  18. package/dist/mcp/build-status.mjs +22 -9
  19. package/dist/mcp/context.mjs +1 -1
  20. package/dist/mcp/preview-watch.mjs +35 -12
  21. package/dist/mcp/preview-widget.bundle.js +40 -38
  22. package/dist/mcp/project-widget.bundle.js +42 -38
  23. package/dist/mcp/prompts.mjs +1 -1
  24. package/dist/mcp/server.mjs +5 -5
  25. package/dist/preview-resources.mjs +1 -1
  26. package/dist/scenarios-compiler.mjs +86 -43
  27. package/dist/scenarios.mjs +16 -5
  28. package/dist/skills/ads-assimilate/SKILL.md +47 -12
  29. package/dist/skills/ads-assimilate/references/cli.md +3 -2
  30. package/dist/skills/ads-break-ui/SKILL.md +47 -12
  31. package/dist/skills/ads-break-ui/references/verification.md +4 -0
  32. package/dist/skills/ads-designer/SKILL.md +49 -14
  33. package/dist/skills/ads-designer/assets/bootstrap-ds/ADS.md +47 -7
  34. package/dist/skills/ads-designer/assets/bootstrap-ds/README.md +12 -10
  35. package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/index.ts +1 -1
  36. package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/patterns/token-preview/Scenario.tsx +4 -0
  37. package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/patterns/token-preview/TokenPreview.tsx +31 -0
  38. package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/patterns/token-preview/fixture.ts +8 -0
  39. package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/tokens/Foundation.fixture.ts +44 -43
  40. package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/tokens/Foundation.scenario.tsx +3 -13
  41. package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/tokens/base.css +4 -1
  42. package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/tokens/colors.css +2 -2
  43. package/dist/skills/ads-designer/assets/bootstrap-ds/ds.config.json +10 -0
  44. package/dist/skills/ads-designer/references/cli.md +3 -2
  45. package/dist/skills/ads-import/SKILL.md +47 -12
  46. package/dist/skills/ads-import/references/cli.md +3 -2
  47. package/dist/skills/ads-motion/SKILL.md +47 -12
  48. package/dist/token-policy.mjs +12 -0
  49. package/dist/token-samples.mjs +8 -0
  50. package/dist/units.mjs +34 -10
  51. package/docs/ADS.md +52 -9
  52. package/docs/ARCHITECTURE.md +44 -3
  53. package/docs/CHANGELOG.md +32 -1
  54. package/docs/GUIDE.md +17 -6
  55. package/docs/MCP.md +62 -9
  56. package/package.json +3 -2
@@ -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
@@ -182,6 +198,25 @@ Use local simulated services in fixtures. Tests may import fixtures; load real
182
198
  foundation CSS rather than assigning shared token values in JavaScript. Keep
183
199
  full-app integration harnesses and workflow coverage in application test support.
184
200
 
201
+ Foundation CSS is checked, not an escape hatch. Value sheets define shared tokens
202
+ on the document root/themes; `fonts.css` holds font-face metadata. `base.css` has
203
+ three closed roles: root/body typography, canvas and theme defaults; limited native
204
+ normalization; and global focus indicators/reduced-motion rules. Every selector
205
+ branch, property and value must fit its role. Token-backed control skins, layout,
206
+ interactive state rules and owner hooks are rejected even through `:where()`.
207
+ Do not copy an unchecked upstream preflight or turn a component repair into a reset.
208
+ Put skin/state/animation in the Linaria owner, and scenario layout variations in
209
+ the public review pattern's typed API. Browser review still verifies the effects.
210
+
211
+ Invalid metadata and source failures keep the managed compiler alive for repairs.
212
+ Read `ads_status.build` or `ads://project/build`, fix the owner, then wait for current
213
+ successful output. If the builder exited, fix its command and invoke the preview
214
+ again; ADS starts one fresh child without an MCP reconnect. Compiler-code changes
215
+ also reload on the next preview invocation. Reconnect the client when upgrading
216
+ the ADS server package itself. Persistent SSE/notification streams make
217
+ `networkidle` unsuitable: wait for the App's current ready/error state instead.
218
+
219
+
185
220
 
186
221
 
187
222
  Register component/project Oxlint plugins and rules in `ds.config.json`. Use
@@ -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
@@ -156,6 +169,35 @@ custom styling in fixture files. The ADS frame pattern uses `--ux-*`. Foreign br
156
169
  CSS imports the approved token files in order; shared assignments belong in
157
170
  those token files, never in consumer styles or component state overrides.
158
171
 
172
+ Foundation roles are enforced by `foundation-role`, `foundation-selector` and
173
+ `foundation-import`, alongside the regular token checks. Shared definitions
174
+ belong on the document root or explicit root themes; ordinary declarations belong
175
+ in `base.css`, and font-face descriptors in optional `fonts.css`. The entry imports
176
+ canonical sheets; those sheets cannot import another styling layer. Component
177
+ classes, IDs, data/ARIA hooks, contextual selectors, keyframes and scoped UI rules
178
+ are rejected in the foundation, including nested functional selectors. Raw visual
179
+ values and undefined token references are checked in native defaults too; literal
180
+ font-face metadata keeps its separate asset role.
181
+
182
+ `base.css` has three closed roles:
183
+
184
+ - Document defaults: root/body typography, canvas colors and theme behavior,
185
+ global text selection and document scrollbars.
186
+ - Native resets: permitted normalization properties and values, such as zero
187
+ spacing, inherited control typography, list resets and responsive replaced media.
188
+ - Global accessibility: universal `:focus-visible` outline rules, and reduced-motion
189
+ overrides inside `@media (prefers-reduced-motion: reduce)`.
190
+
191
+ The `foundation-role` check validates every selector branch, property and value.
192
+ Native selectors are not a bypass: token-backed button backgrounds, flex/grid
193
+ layout, component-specific focus rules and hover/disabled skins are rejected.
194
+ Shared token definitions belong in value sheets, not `base.css`. No project token
195
+ names are embedded in this policy. A zero-specificity `:where()` wrapper does not
196
+ change ownership. Put component design and animation in Linaria owners, and
197
+ scenario layout variations in typed public review-pattern props. Browser review
198
+ still checks the effects and accessibility of permitted defaults.
199
+
200
+
159
201
  Store original branding and its usage, credits, and licenses in
160
202
  `assets/brand/`, and fonts in `assets/fonts/`. Configure generated icons and
161
203
  asset mounts as described in the [branding guide](GUIDE.md#branding).
@@ -279,8 +321,9 @@ Validate accepted and rejected cases, configuration, and false-positive limits.
279
321
  Root [bootstrap-ds/](../bootstrap-ds/README.md) is the canonical empty scaffold.
280
322
  Its identical copy under `skills/ads-designer/assets/bootstrap-ds/` makes the
281
323
  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.
324
+ `npm run check:skill-starter` to verify the copy. Both contain empty product component/composition directories and a minimal project-owned
325
+ token review pattern. The scaffold pattern can be renamed or replaced; the tool
326
+ does not require that name or its styles and ships no product UI catalogue.
284
327
 
285
328
  Tool releases should version contract changes, rules, skills, bootstrap support,
286
329
  and migration guidance. Existing project implementations and external source
@@ -191,6 +191,29 @@ and the UI rendered inside Apps belong to the project. This keeps the tool usefu
191
191
  with an empty ADS while making every implementation addressable once its scenario
192
192
  exists.
193
193
 
194
+ ### The foundation has a narrow styling role
195
+
196
+ Shared token sheets define values on the document root and its themes. The
197
+ import-only entry loads them in canonical order. `fonts.css` declares font faces;
198
+ `base.css` establishes shared native defaults and normalization. Components and
199
+ patterns own their layout, skin, states and animations in Linaria.
200
+
201
+ `base.css` has a closed contract with three roles: root/body typography, canvas
202
+ colors and theme behavior; limited native normalization; and global accessibility
203
+ through focus indicators and reduced-motion rules. Selector, property and value
204
+ checks reject component skins and layout even when they use native selectors and
205
+ valid tokens. A `button` background or `body` grid belongs to a Linaria owner.
206
+ Document defaults also cover shared text selection and document scrollbars;
207
+ local control/scroll-container styling stays with its owner.
208
+
209
+ Foundation CSS passes the ordinary token-reference and visual-value checks too.
210
+ Parsed selectors reject component hooks, local shared-token overrides and
211
+ contextual selectors, including hooks concealed in `:where()` or `:is()`. The
212
+ policy describes CSS roles and normalization keywords, with no project classes,
213
+ token catalogue or sample templates. Lower specificity cannot bypass ownership.
214
+ Static gates establish this source contract; browser review still checks its
215
+ actual effects and accessibility.
216
+
194
217
  ### A preview scenario is the unit of review
195
218
 
196
219
  A component export alone may need inputs, parent context, local state or callbacks
@@ -201,8 +224,9 @@ page or initializing the application's router, store or services first.
201
224
  Scenarios and their fixtures live beside the tokens, component or pattern they exercise.
202
225
  Token scenarios choose real token names and sample inputs; a project-owned
203
226
  review pattern applies the foundation variables. The tool contains no project
204
- token list, sample templates or assumed visual identity. A minimal foundation
205
- scenario can inspect values before a project has any components.
227
+ token list, sample templates or assumed visual identity. Even an initial token
228
+ scenario uses a public project-owned review pattern to apply its declared samples;
229
+ a definitions-only inspector does not establish preview coverage.
206
230
  Larger scenarios live under `composition/`. They use the same public screen
207
231
  patterns as production: application adapters supply live data and effects;
208
232
  scenario adapters supply fixture data, local state and simulated services.
@@ -222,7 +246,12 @@ After source changes, validation and compilation must both succeed before a new
222
246
  graph becomes current. MCP announces build/resource updates. The built-in client
223
247
  refreshes discovery and sends the current selection to the existing App, whose
224
248
  loader mounts the new module graph. Failed builds retain the previous view with
225
- visible stale diagnostics. Compilation remains necessary for TSX and Linaria;
249
+ visible stale diagnostics. Invalid scenario metadata at startup and later source
250
+ failures stay in the watcher's recoverable diagnostic path. Fixing source triggers validation again.
251
+ If the compiler process exits, the next preview request starts one fresh child;
252
+ MCP inspection remains available. Each child generation has its own status report,
253
+ so independent MCP clients do not overwrite each other's build state.
254
+ Compilation remains necessary for TSX and Linaria;
226
255
  there is one preview pipeline with automatic updates.
227
256
 
228
257
  Scenario entries, adapters and fixtures are development source even when
@@ -273,6 +302,11 @@ replace the managed process while retaining the MCP connection. Normal build gat
273
302
  remain in place for source changes made through other editors; preview review remains
274
303
  necessary after either editing path.
275
304
 
305
+ Required shared-entry bookkeeping can accompany its owner: component/pattern
306
+ re-exports and canonical foundation imports for token files created or deleted in
307
+ the same patch. ADS restricts these companion edits and validates the complete
308
+ candidate before writing, so coupled changes do not need a guard bypass.
309
+
276
310
  The architecture makes that inspection practical. A public export has an owning
277
311
  source and addressable preview coverage. Token definitions expose its visual
278
312
  vocabulary; local guides explain contracts when present. An agent can discover a
@@ -309,3 +343,10 @@ For concrete instructions, use:
309
343
  - [Tool guide](GUIDE.md) for prerequisites, supported formats, configuration, and build wiring.
310
344
  - [Working example guide](../design-system/README.md) for Linaria composition, extraction, and project commands.
311
345
  - Component-local READMEs for APIs, lifecycle, tests, and provenance.
346
+
347
+ Token preview completeness has two checks. Static guards require explicit fixture
348
+ bindings (`token`, CSS `property`, and project-selected `probe`) consumed by a public
349
+ review pattern. The App then verifies rendered samples in the actual browser by
350
+ probing those bindings and restoring them. The tool supplies no token/property
351
+ classification or visual sample palette. A source build alone is not a rendered
352
+ verification, and the browser check does not judge design quality.
package/docs/CHANGELOG.md CHANGED
@@ -1,6 +1,37 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## 0.3.2
4
+
5
+ - Enforce foundation CSS roles, parsed selector boundaries, root-only shared token
6
+ assignments, token references and visual values. Keep native defaults in
7
+ `base.css`, definitions in value sheets and font-face metadata in `fonts.css`;
8
+ reject hidden owner styling, extra imports and animation/scoping escapes.
9
+ Close `base.css` to document defaults, limited normalization and global
10
+ accessibility; native selectors and valid tokens cannot hide component skins
11
+ or layout. Move the demo link hover styling into its TextLink owner.
12
+ - Keep scenario discovery and build failures recoverable in the watcher, including
13
+ invalid metadata at startup. Reattach replaced source-directory watches.
14
+ - Retry exited builders on the next preview request without reconnecting MCP;
15
+ reload changed compiler code through a fresh child and preserve inspection.
16
+ Isolate status reports per child session across concurrent MCP clients.
17
+ - Align the starter, example foundation and skills with the enforced boundaries.
18
+
19
+ ## 0.3.1
20
+
21
+ - Stop generating redundant empty public exports. Allow an owning unit's public
22
+ re-export companion to remove an obsolete placeholder without changing peers.
23
+ - Require token fixtures to declare real CSS sample bindings and project-owned
24
+ probes, consumed by a public review pattern. Reject empty covers and plain lists.
25
+ Apps verify rendered property dependencies in the browser before reporting ready.
26
+ The tool infers no token roles and contains no property/probe catalogue. Existing
27
+ projects need a token scenario migration and preview rebuild.
28
+ - Restore native scrolling inside resource preview Apps for scenarios taller or
29
+ wider than their viewport, while retaining full-height composition layouts.
30
+ - Allow a `tokens` mutation to add/remove the required foundation import together
31
+ with its canonical token file. Discover the entry through `ads_units` companion
32
+ metadata; preserve unrelated imports/comments and validate before writing.
33
+
34
+ ## 0.3.0
4
35
 
5
36
  - Rebrand the tool as ADS — Agentic Design System: `@reqvire-org/ads`, the `ads`
6
37
  CLI, `ads_*` MCP tools, `ads://` resources and `ads-*` skills. Keep project
package/docs/GUIDE.md CHANGED
@@ -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.2.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
 
@@ -494,6 +497,14 @@ import-only: fonts (when present), colors, typography, spacing, elevation, base.
494
497
  Fixed artwork colors and geometry are permitted inside SVG assets; they do not
495
498
  permit raw visual values in component or pattern styles.
496
499
 
500
+ Foundation sheets are checked source. Keep root token definitions in value sheets,
501
+ font faces in `fonts.css`, and closed-role defaults in `base.css` (document
502
+ typography/canvas/theme, limited native resets, global focus/reduced motion).
503
+ Owner hooks, contextual UI selectors, keyframes, extra CSS imports, raw visual
504
+ values and missing token references fail checks there too. Move component-specific
505
+ repairs into their Linaria owner; see the [foundation contract](ADS.md#shared-token-contract).
506
+
507
+
497
508
  Select one SVG master with an ADS-relative path:
498
509
 
499
510
  ```json
package/docs/MCP.md CHANGED
@@ -43,7 +43,7 @@ Switching entries overlays a centered loading spinner, then reveals the new
43
43
  scenario inside the existing App. Loading and diagnostic overlays do not move the
44
44
  preview. Token presentation comes from project-owned scenarios, fixtures and
45
45
  public review patterns, using the project's real foundation CSS and extracted
46
- styles. The tool contains no token sample templates or named token ordering.
46
+ styles. The App runtime contains no token sample templates or named token ordering.
47
47
  The generic definition inspector remains available without a scenario build.
48
48
 
49
49
  There are no required browser-page files in the project. Colocated scenarios and
@@ -200,6 +200,15 @@ remain separate owners. `tokens` is one foundation unit covering token definitio
200
200
  themes and token scenarios/fixtures, whose coverage spans shared source files.
201
201
  Preview IDs remain separate: several token previews belong to `tokens`.
202
202
 
203
+ The `tokens` manifest also returns `companionFiles` with the configured foundation
204
+ entry path. When creating or deleting a canonical token foundation file, include
205
+ its required import change in the same `tokens` patch. For example, add
206
+ `tokens/fonts.css` and its first import in `styles.css` together; remove both
207
+ together when removing that font stylesheet. This companion may only change
208
+ imports for canonical files created/deleted in that patch. Preserve other imports
209
+ and comments, retain the foundation entry, and follow the required import order.
210
+ Both files pass the same candidate checks before either is written.
211
+
203
212
  The same discovery covers the rest of the project ADS:
204
213
 
205
214
  | Unit | Scope |
@@ -259,7 +268,11 @@ so coverage is checked against a complete candidate. New units use a new
259
268
  `components/`, `patterns/` or `composition/` owner ID following the same colocation
260
269
  convention. A patch cannot include a peer unit, capture existing peers beneath
261
270
  a new directory owner, or change unrelated public-entry statements. Shared
262
- public-entry changes are limited to re-exports from the selected unit.
271
+ public-entry changes are limited to re-exports from the selected unit. Alongside
272
+ its real re-export, a patch can remove an obsolete `export {};` placeholder;
273
+ it cannot add one or change unrelated statements. Init generates no empty-export
274
+ placeholder. Report unsupported coupled changes rather than bypassing a rejected
275
+ mutation with direct source writes.
263
276
 
264
277
  Set `dryRun` to `false` to apply. ADS reconstructs complete candidate files locally
265
278
  and validates a temporary project copy with core guards and registered Oxlint
@@ -485,11 +498,17 @@ success cache is republished in the new watcher session without recompilation;
485
498
  merely finding an older HTML file is insufficient. A timeout leaves the watcher running: inspect the
486
499
  logs and invoke the preview again. `ads_status` distinguishes watcher process
487
500
  state from reported build state. Token inspection and other ordinary tools
488
- remain available after a build failure. An exited watcher needs reconnection;
489
- an active watcher can recover on the next successful rebuild. Changes to
490
- `previewWatch`, the compiler adapter or ADS tooling require reconnection to load
491
- new process code. The shared scheduler rejects publication if its loaded adapter
492
- files change; normal component, fixture and style edits remain automatic.
501
+ remain available after a build failure. Invalid scenario metadata at startup and
502
+ later source/validation failures remain reported while the compiler watches for
503
+ repairs. A successful edit recovers without reconnecting MCP. If the child process
504
+ exits, fix the builder and invoke the preview again: concurrent requests share one
505
+ fresh child and session. ADS does not run an unattended crash/restart loop.
506
+
507
+ Changes to loaded compiler code or dependencies produce `ADS_PREVIEW_RESTART`;
508
+ the next preview invocation restarts only the builder and validates current inputs.
509
+ Managed configuration changes replace the compiler through the same supervisor.
510
+ Updating ADS server code itself still requires reconnecting the MCP client to load
511
+ the new package. Normal component, fixture and style edits remain automatic.
493
512
 
494
513
  ADS stops the managed watcher and its compiler subprocesses when stdin closes or
495
514
  it receives SIGINT/SIGTERM. The project must keep its watcher in the foreground,
@@ -516,7 +535,10 @@ No ADS package release or skill reinstall is needed for project component change
516
535
  ### When code or styles break
517
536
 
518
537
  A preview publisher reports `building`, `ready` or `error` to
519
- `.ds/build-status.json`. `ads_status.build` and the `ads://project/build` resource
538
+ `.ds/build-status.json` for standalone builds. A managed compiler receives its own
539
+ `.ds/build-status-<session>.json` through `ADS_MCP_BUILD_STATUS`; publishers must
540
+ honor that path and `ADS_MCP_BUILD_SESSION`. Independent MCP clients therefore do
541
+ not clobber each other's reports. `ads_status.build` and the `ads://project/build` resource
520
542
  expose those states, each preview's build revision, last successful revision,
521
543
  availability, freshness and diagnostics. Diagnostic positions are one-based.
522
544
  Tool summaries include the first three diagnostics per preview, capped at 1,000
@@ -540,7 +562,9 @@ failed build even when older output remains on disk. The reusable bootstrap and
540
562
  retained immutable assets stay readable so an existing App can report freshness;
541
563
  readability alone does not establish a current preview. After a successful rebuild the same
542
564
  tool works again without restarting MCP. If the watcher itself exits, its
543
- failure is reported and the client must reconnect after fixing the command.
565
+ failure is reported; after fixing the command, invoke the preview again to start
566
+ a fresh builder. Partial or empty scenario discovery also exposes project-level
567
+ diagnostics through `ads://project/build`.
544
568
 
545
569
  Already-mounted Apps show rebuilding, failure or a newer available revision
546
570
  when the host permits server resource reads. They check build status every two
@@ -688,3 +712,32 @@ verify tools, explicit resources, an App entry tool and its UI handshake,
688
712
  Refresh/theme behavior, notifications, prompts, then native Skills separately.
689
713
  Record the client build and negotiated protocol; HTML delivery or ordinary
690
714
  instruction reads alone do not establish Apps/Skills activation.
715
+
716
+ ### Token sample verification
717
+
718
+ Token scenarios declare `samples: fixture.samples` and render a project-owned public
719
+ review pattern with that `samples` input. Each fixture record has `{ token, property,
720
+ probe }`; the tool discovers the resulting token names. Plain token lists and empty
721
+ covers do not satisfy review coverage. The same bindings travel in the compiled
722
+ resource manifest or self-contained App seed.
723
+
724
+ The renderer identifies its styled sample elements through `data-ads-token` and
725
+ `data-ads-property`. After mounting and font loading, the App uses the browser's CSS
726
+ engine to validate each declared binding and project-supplied probe, then checks
727
+ that changing the token changes the sample's computed property. It restores token
728
+ values synchronously. Failures remain visible and are emitted through App logging
729
+ when the host supports it; a failed sample is not reported as ready.
730
+
731
+ Source checks establish structural coverage. Browser verification happens when the
732
+ App actually renders, for the active scenario and theme. It is not a pre-write
733
+ browser test in the mutation server and does not establish every interaction or
734
+ pixel-level visual quality. Hosts without Apps retain static diagnostics and the
735
+ showcase URL; they cannot claim rendered verification from a source check alone.
736
+ For browser automation, wait for the selected App's ready/error state. The
737
+ showcase's live notification stream makes `networkidle` unsuitable. Tall previews
738
+ scroll inside the App document; verify their last sample remains reachable.
739
+
740
+ Existing projects migrate their token fixtures from name lists to explicit sample
741
+ records, pass those records into their public renderer, and add the sample markers.
742
+ Rebuild previews with the updated compiler so the contract is included in resources.
743
+ Tool updates do not overwrite existing project-owned scenarios or renderers.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reqvire-org/ads",
3
- "version": "0.3.0",
3
+ "version": "0.3.2",
4
4
  "type": "module",
5
5
  "description": "A design system your agent can see and shape. Your components. Your look. Your code.",
6
6
  "repository": {
@@ -35,7 +35,7 @@
35
35
  "sync:skill-starter": "node scripts/sync-skill-starter.mjs",
36
36
  "check:skill-starter": "node scripts/sync-skill-starter.mjs --check",
37
37
  "lint": "npm run lint:tool",
38
- "lint:tool": "npm run check:skill-starter && oxlint --deny-warnings src showcase-client mcp-explorer tests/*.test.mjs scripts/build-ads-tool.mjs scripts/pack-ads-tool.mjs",
38
+ "lint:tool": "npm run check:skill-starter && oxlint --deny-warnings src preview-client showcase-client mcp-explorer tests/*.test.mjs scripts/build-ads-tool.mjs scripts/pack-ads-tool.mjs",
39
39
  "lint:project": "npm run lint:ds && npm run lint:style && npm run check:skill-starter",
40
40
  "lint:ds": "node dist/cli.mjs lint-config && node dist/cli.mjs check && oxlint --deny-warnings --config .ds/oxlintrc.json src design-system",
41
41
  "lint:style": "biome lint --config-path=biome.css.json .",
@@ -51,6 +51,7 @@
51
51
  "dependencies": {
52
52
  "@modelcontextprotocol/server": "2.3.1",
53
53
  "postcss": "^8.5.30",
54
+ "postcss-selector-parser": "^7.1.6",
54
55
  "postcss-value-parser": "^4.2.0",
55
56
  "sharp": "^0.35.5",
56
57
  "typescript": "^5.9.3",