@reqvire-org/ads 0.3.7 → 0.3.9

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 (43) hide show
  1. package/dist/atomic-composition.mjs +85 -54
  2. package/dist/atomic-trace.mjs +92 -0
  3. package/dist/bootstrap-ds/scripts/build-mcp-previews.mjs +2 -1
  4. package/dist/bootstrap-ds/vite.config.ts +2 -1
  5. package/dist/check.mjs +14 -3
  6. package/dist/linaria/atomic-css.cjs +2 -0
  7. package/dist/linaria/atomic-styled.cjs +2 -0
  8. package/dist/linaria/preserve-values.cjs +32 -0
  9. package/dist/linaria.d.mts +1 -0
  10. package/dist/linaria.mjs +9 -0
  11. package/dist/mcp/preview-widget.bundle.js +1 -1
  12. package/dist/mcp/resource-loader.js +5 -2
  13. package/dist/mcp/server.mjs +4 -2
  14. package/dist/scenario-trace.mjs +149 -0
  15. package/dist/scenarios-compiler.mjs +2 -1
  16. package/dist/scenarios.mjs +22 -6
  17. package/dist/skills/ads-break-ui/SKILL.md +10 -2
  18. package/dist/skills/ads-break-ui/references/atomic-styles.md +122 -7
  19. package/dist/skills/ads-break-ui/references/scenarios.md +83 -0
  20. package/dist/skills/ads-designer/SKILL.md +13 -3
  21. package/dist/skills/ads-designer/assets/bootstrap-ds/scripts/build-mcp-previews.mjs +2 -1
  22. package/dist/skills/ads-designer/assets/bootstrap-ds/vite.config.ts +2 -1
  23. package/dist/skills/ads-designer/references/atomic-styles.md +122 -7
  24. package/dist/skills/ads-designer/references/scenarios.md +83 -0
  25. package/dist/skills/ads-import/SKILL.md +10 -2
  26. package/dist/skills/ads-import/references/atomic-styles.md +122 -7
  27. package/dist/skills/ads-import/references/scenarios.md +83 -0
  28. package/dist/skills/ads-migrate/SKILL.md +10 -2
  29. package/dist/skills/ads-migrate/references/atomic-styles.md +122 -7
  30. package/dist/skills/ads-migrate/references/scenarios.md +83 -0
  31. package/dist/skills/ads-motion/SKILL.md +10 -2
  32. package/dist/skills/ads-motion/references/atomic-styles.md +122 -7
  33. package/dist/skills/ads-motion/references/scenarios.md +83 -0
  34. package/dist/source-values.mjs +247 -0
  35. package/dist/verification/browser.mjs +21 -12
  36. package/dist/verification/playwright-reporter.cjs +6 -2
  37. package/dist/verification.mjs +8 -3
  38. package/docs/ADS.md +18 -5
  39. package/docs/ARCHITECTURE.md +19 -0
  40. package/docs/BEHAVIORS.md +15 -1
  41. package/docs/CHANGELOG.md +28 -1
  42. package/docs/MCP.md +18 -0
  43. package/package.json +10 -1
@@ -6,15 +6,19 @@ module.exports = class AdsBehaviorReporter {
6
6
  constructor() {
7
7
  this.request = JSON.parse(readFileSync(process.env.ADS_VERIFY_REQUEST, "utf8"));
8
8
  this.cases = [];
9
+ this.errors = [];
9
10
  }
10
11
  onTestEnd(test, result) {
11
12
  const file = path.relative(this.request.projectRoot, test.location.file).replaceAll("\\", "/");
12
13
  for (const tag of test.tags ?? []) {
13
14
  if (!tag.startsWith("@ads:")) continue;
14
- this.cases.push({ id: tag.slice(5), file, status: result.status === "passed" && test.expectedStatus === "passed" ? "passed" : ["skipped", "interrupted"].includes(result.status) ? "skipped" : "failed", message: result.errors?.map(error => error.message).join("\n").slice(0, 4000) });
15
+ this.cases.push({ id: tag.slice(5), file, status: result.status === "passed" && test.expectedStatus === "passed" ? "passed" : ["skipped", "interrupted"].includes(result.status) ? "skipped" : "failed", message: result.errors?.map(error => error.message || error.stack || error.value || "The browser runner reported an error without details.").join("\n").slice(0, 4000) });
15
16
  }
16
17
  }
18
+ onError(error) {
19
+ if (this.errors.length < 5) this.errors.push(String(error.message || error.stack || error.value || "The browser runner reported a suite error without details.").slice(0, 4000));
20
+ }
17
21
  onEnd() {
18
- writeFileSync(process.env.ADS_VERIFY_REPORT, JSON.stringify({ schemaVersion: 1, runId: this.request.runId, cases: this.cases.slice(0, 500) }));
22
+ writeFileSync(process.env.ADS_VERIFY_REPORT, JSON.stringify({ schemaVersion: 1, runId: this.request.runId, cases: this.cases.slice(0, 500), errors: this.errors }));
19
23
  }
20
24
  };
@@ -182,19 +182,24 @@ export function createVerificationController(root) {
182
182
  if (!existsSync(report) || realpathSync(report) !== report || lstatSync(report).size > 1024 * 1024) throw new Error("Runner produced no valid bounded verification report. A successful exit alone is not passing evidence.");
183
183
  reportValue = JSON.parse(readFileSync(report, "utf8"));
184
184
  if (reportValue.schemaVersion !== 1 || reportValue.runId !== runId || !Array.isArray(reportValue.cases) || reportValue.cases.length > 500) throw new Error("Runner report has the wrong run ID/schema or too many cases.");
185
- } catch (error) { runnerError = error; diagnostics.push({ rule: "behavior-runner", message: error.message, output: error.output?.slice(-4000), profile: profileName }); }
185
+ } catch (error) { runnerError = error; reportValue = undefined; diagnostics.push({ rule: "behavior-runner", message: error.message, output: error.output?.slice(-4000), profile: profileName }); }
186
+ const suiteErrors = Array.isArray(reportValue?.errors) ? reportValue.errors.slice(0, 5).map(error => String(error).slice(0, 1000)) : [];
187
+ const runnerFailure = run && (run.code !== 0 || suiteErrors.length) ? `Registered runner ${profileName} ${run.code !== 0 ? `exited with code ${run.code ?? "unknown"}` : "reported a suite error"}. ${suiteErrors.join("; ") || reportValue?.cases.filter(item => item?.status === "failed").slice(0, 3).map(item => `${item.id}: ${item.message || "failed without a case diagnostic"}`).join("; ") || "Inspect the runner output and verification report."}`.slice(0, 3000) : undefined;
188
+ if (runnerFailure) diagnostics.push({ rule: "behavior-runner", message: runnerFailure, output: run.output?.slice(-4000), profile: profileName });
186
189
  const afterOptions = loadConfig(root), afterContext = inputContext(afterOptions);
187
190
  for (const check of group) {
188
191
  const file = slash(path.relative(root, path.resolve(root, path.dirname(check.contract.file), check.file)));
189
192
  const matches = reportValue?.cases.filter(item => item && item.id === check.case && item.file === file) ?? [];
190
- let status = !run || run.code !== 0 ? "failed" : matches.length && matches.every(item => item.status === "passed") ? "passed" : matches.length && matches.every(item => item.status === "skipped") ? "skipped" : "failed";
193
+ const reportedStatus = matches.length && matches.every(item => item.status === "passed") ? "passed" : matches.length && matches.every(item => item.status === "skipped") ? "skipped" : "failed";
194
+ let status = !run || runnerFailure ? "failed" : reportedStatus;
191
195
  if (runnerError?.code === "ENOENT") status = "unavailable";
192
196
  const current = behaviorInputs(afterOptions, check.contract, check, afterContext).revision === check.inputRevision;
193
197
  if (!current) status = "stale";
194
198
  if (signal.aborted) status = "cancelled";
195
199
  results.push({ unit: check.unit, check: check.id, file: check.file, case: check.case, profile: profileName, status, current, revision: check.inputRevision, runId, completedAt: new Date().toISOString(),
196
200
  previewRevision: profile.mode === "browser" ? host?.previewRevision() ?? readPreviewManifest(options)?.revision ?? null : null,
197
- message: runnerError?.message ?? (!matches.length ? "The selected case did not execute/report. Missing and skipped cases do not pass." : matches.filter(item => item.status !== "passed").map(item => String(item.message ?? item.status).slice(0, 2000)).join("\n")), report: slash(path.relative(root, report)) });
201
+ ...(runnerFailure ? { reportedStatus, runnerExitCode: run.code } : {}),
202
+ message: runnerError?.message ?? (matches.filter(item => item.status !== "passed").map(item => String(item.message || `Case reported ${item.status} without a diagnostic.`).slice(0, 2000)).join("\n") || (!matches.length ? "The selected case did not execute/report. Missing and skipped cases do not pass." : runnerFailure ? `This case reported ${reportedStatus}, but the complete run did not pass. ${runnerFailure}` : "")), report: slash(path.relative(root, report)) });
198
203
  }
199
204
  if (signal.aborted) break;
200
205
  }
package/docs/ADS.md CHANGED
@@ -141,7 +141,11 @@ TSX or JSON equivalents are supported. An empty fixture is valid without inputs.
141
141
  `scenario-contract`, `scenario-render` and `scenario-coverage` checks require valid
142
142
  entries and reachable public render references for every public renderable
143
143
  component/pattern, including unused exports and Linaria styled exports. Types, constants and utility functions
144
- are excluded. Coverage is a static source policy; it does not prove runtime
144
+ are excluded. Explicit `covers` entries can receive transitive evidence through a
145
+ rendered parent's returned JSX, fixture props/defaults and known framework
146
+ wrappers/providers. Unknown branches contribute only their common public parts;
147
+ unused JSX and callbacks contribute none. Bounded tracing does not automatically
148
+ add dependency APIs to `covers`. Coverage is a static source policy; it does not prove runtime
145
149
  mounting, correct props, every branch/state or visual parity. The older
146
150
  `showcase-coverage` policy also checks application render references.
147
151
 
@@ -156,6 +160,11 @@ or applies the right CSS property. Browser review still verifies the samples.
156
160
  Scenarios and fixture helpers are development-only wherever colocated. Runtime
157
161
  source and the public barrel cannot import or re-export them. Showcase/scenario
158
162
  code defines no CSS, custom classes or native controls; use public ADS APIs.
163
+ Scenarios may consume public class helpers whose returned styles trace to owned
164
+ Linaria. Stable hooks in that owner may accompany real styles; strings forwarded
165
+ from scenarios, hook-only returns and untraceable outputs remain invalid.
166
+ `exercises` does not authorize classes. See the skill-local
167
+ [scenario contract guide](../skills/ads-designer/references/scenarios.md).
159
168
  Neither ADS implementations nor scenarios import application code. Live business
160
169
  data belongs to application adapters; ADS receives inputs or injected callbacks.
161
170
  Direct global `fetch`, XHR, WebSocket and EventSource use is rejected. Renderer
@@ -373,6 +382,9 @@ guarded mutations: valid CSS can intentionally overlap. Read
373
382
  [atomic style composition](../skills/ads-designer/references/atomic-styles.md) for causes, matching-property fixes
374
383
  and the source review's limits. Registered rendered checks can enforce a
375
384
  project's intended computed geometry; token validity alone cannot establish it.
385
+ Bounded tracing follows local helpers and explicit `className` forwarding;
386
+ diagnostics distinguish a reached DOM target from informational analysis limits.
387
+ Neither a trace nor an absent warning establishes the intended rendered result.
376
388
 
377
389
  Document a new ownership exception with its guard/configuration change and
378
390
  tests. Passing static checks establishes only the policies and source formats
@@ -397,10 +409,11 @@ Validate accepted and rejected cases, configuration, and false-positive limits.
397
409
 
398
410
  ## Bootstrap and updates
399
411
 
400
- The atomic composition guide lives in each skill's `references/atomic-styles.md`.
401
- The maintained copy is in `skills/ads-designer/references/atomic-styles.md`;
402
- MCP serves that packaged skill reference. `npm run sync:skill-references` copies
403
- it to the other skills for standalone use; `npm run check:skill-references`
412
+ The atomic composition and scenario contract guides live in each skill's
413
+ `references/atomic-styles.md` and `references/scenarios.md`. The maintained copies
414
+ are in `skills/ads-designer/references/`; MCP serves those packaged references.
415
+ `npm run sync:skill-references` copies them to the other skills for standalone use;
416
+ `npm run check:skill-references`
404
417
  rejects drift during the tool build. Published packages include these references
405
418
  under `dist/skills/`.
406
419
 
@@ -127,6 +127,9 @@ every shorthand/logical-property overlap. ADS reports detectable composition
127
127
  risks as nonblocking warnings with guidance, while registered rendered checks
128
128
  can enforce actual geometry. See the skill-local
129
129
  [atomic style composition guide](../skills/ads-designer/references/atomic-styles.md).
130
+ Bounded source tracing follows local class helpers and explicit component
131
+ `className` forwarding to DOM targets. Separate elements remain separate;
132
+ unsupported paths are informational limits, not evidence of safe composition.
130
133
 
131
134
  Airbnb's 2022 [migration report](https://medium.com/airbnb-engineering/airbnbs-trip-to-linaria-dc169230bd12)
132
135
  provides practical evidence for this choice: performance was its leading
@@ -143,6 +146,14 @@ of every unused variant. Atomic composition still requires care with the cascade
143
146
  Apply extraction to application and project preview builds, preserve intentional
144
147
  foundation CSS, and inspect compiled output when performance matters. The
145
148
  [implementation guide](../design-system/README.md#bundling-notes) covers these details.
149
+ Both extraction pipelines use the build-only `@reqvire-org/ads/linaria` tag
150
+ resolver. It delegates atomization and nesting to the project's installed Linaria
151
+ processor while preserving custom-property token streams around that pass.
152
+ Arithmetic becomes meaningful after `var()` substitution, so stripping whitespace
153
+ can invalidate otherwise valid source. Verify emitted CSS and computed browser
154
+ values; source guards alone cannot establish extraction correctness. The
155
+ skill-local [atomic styling guide](../skills/ads-designer/references/atomic-styles.md#preserve-custom-property-arithmetic-during-extraction)
156
+ documents integration and source-workaround semantics.
146
157
 
147
158
  ## Public APIs preserve the boundary
148
159
 
@@ -283,6 +294,14 @@ their required parent. Every declared token and every public renderable componen
283
294
  and pattern needs addressable coverage. Token references must exist in the real
284
295
  foundation; claimed token/UI coverage must be reachable from the scenario entry.
285
296
  Types, constants and utilities do not require UI scenarios.
297
+ Explicitly declared public children can share a parent's scenario when bounded
298
+ source tracing reaches them through its returned JSX and selected props/defaults.
299
+ Opaque conditional branches do not grant speculative coverage. Scenarios may
300
+ consume deliberate public class helpers when their returned styles retain
301
+ runtime-owned Linaria provenance; stable owner-authored hooks may accompany real
302
+ styles. This preserves ownership rather than granting scenario styling freedom.
303
+ The skill-local [scenario guide](../skills/ads-designer/references/scenarios.md)
304
+ explains evidence, limits and diagnostics.
286
305
 
287
306
  After source changes, validation and compilation must both succeed before a new
288
307
  graph becomes current. MCP announces build/resource updates. The built-in client
package/docs/BEHAVIORS.md CHANGED
@@ -103,6 +103,10 @@ values lazily and shares that type discovery within the check. Untyped `any`
103
103
  values and objects containing methods do not establish a callable API.
104
104
  An invalid `exercises` entry names the offending value and fails validation while
105
105
  retaining independent render evidence for `covers`.
106
+ Calls to a callable's attached methods also exercise that public receiver,
107
+ including statically named member calls inside reachable event-handler closures.
108
+ Aliases, namespace access and re-exports retain the receiver identity. Reading a
109
+ method or defining an unused callback does not establish an invocation.
106
110
 
107
111
  Public APIs appear in `ads_catalog` with `apiKind: "ui"` or `"behavior"` and their
108
112
  scenario links. `ads_behaviors` joins contracts to demonstrations and evidence.
@@ -136,7 +140,17 @@ and any required test setup in that project. The ADS tooling host gains no
136
140
  framework or browser dependency from a profile.
137
141
 
138
142
  The Playwright adapter appends selected file paths, its reporter and a generated
139
- artifact directory. Use native test tags for stable case identity:
143
+ artifact directory.
144
+
145
+ Runner failures remain blocking even when some selected cases reported passed.
146
+ Those results retain `reportedStatus` and `runnerExitCode`, and explain the suite
147
+ failure rather than returning an empty message. Suite-level browser errors and
148
+ bounded runner output appear in diagnostics. `openBehaviorScenario` includes the
149
+ scenario ID and failed browser requests in startup failures; App resource errors
150
+ identify the resource URI. Failed assertions and startup errors are not retried
151
+ automatically. Fix or investigate the failure before explicitly verifying again.
152
+
153
+ Use native test tags for stable case identity:
140
154
 
141
155
  ```ts
142
156
  import { test, expect } from "@playwright/test";
package/docs/CHANGELOG.md CHANGED
@@ -1,6 +1,33 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## 0.3.9
4
+
5
+ - Credit explicitly declared public child renders reached through parents,
6
+ fixture props/defaults and known framework wrappers/providers. Share bounded
7
+ source evaluation with atomic review; unresolved paths, unused JSX and
8
+ callbacks do not manufacture child coverage.
9
+ - Allow scenario class helpers with proven runtime Linaria provenance and stable
10
+ owner-authored hooks alongside real styles. Reject consumer strings, private
11
+ styles, empty templates and untraceable returns; document the contract in all
12
+ five portable skills and `ads://guides/scenarios`.
13
+ - Preserve custom-property token streams through Linaria atomic extraction with
14
+ the build-only `@reqvire-org/ads/linaria` resolver. Integrate the resolver in
15
+ product and MCP preview starter builds; verify extracted arithmetic and real
16
+ browser transforms using generic fixtures with and without CSS minification.
17
+ - Explain suite-wide verification failures on otherwise passing cases, retain
18
+ their reported status, and surface runner-level errors. Include scenario IDs,
19
+ failed requests and App resource URIs in startup diagnostics without retrying
20
+ failed checks automatically.
21
+
22
+ ## 0.3.8
23
+
24
+ - Trace local atomic class helpers and explicit component class forwarding,
25
+ preserving alternatives and spread order. Report composition paths, DOM
26
+ targets, writing-mode risks and bounded informational analysis limits;
27
+ suppress safely established literal-equivalent overlaps. Keep review warnings
28
+ nonblocking and use registered rendered checks for intended geometry.
29
+ - Count calls to attached methods of public callable behavioral APIs through
30
+ aliases, namespace imports and re-exports, including mounted event handlers.
4
31
 
5
32
  ## 0.3.7
6
33
 
package/docs/MCP.md CHANGED
@@ -197,6 +197,12 @@ component-level fixes. The full guide lives in every ADS skill as
197
197
  Guarded mutation results preserve these warnings without rejecting valid source.
198
198
  An overlap warning is a review signal; failed registered rendered checks remain
199
199
  blocking.
200
+ Warnings can carry a `composition` source path, a statically reached DOM `target`
201
+ and `writingModeDependent`. Unresolved paths appear as bounded
202
+ `atomic-composition-limit` advisories with `severity: "info"`; neither an
203
+ informational limit nor a traced target proves a rendered failure or success.
204
+ Bounded check results put warnings before informational limits; the checks
205
+ resource retains complete results.
200
206
 
201
207
  ### Behavioral verification
202
208
 
@@ -435,6 +441,12 @@ project's normal lint/build gates.
435
441
 
436
442
  `ads_scenarios` discovers path-derived IDs for token, component, pattern and
437
443
  composition scenarios. `ads_catalog` links public exports to their coverage.
444
+ `covers` can include public children reached through the rendered parent's
445
+ returned JSX with known props/defaults. Public class helpers require verified
446
+ runtime Linaria provenance; `exercises` alone grants no class permission. The
447
+ `ads://guides/scenarios` resource and each skill's
448
+ [local scenario reference](../skills/ads-designer/references/scenarios.md) describe
449
+ the bounded checks and diagnostics.
438
450
  `ads_show_scenario({id})` selects an independently mountable entry with its own
439
451
  fixtures. `ads_show_tokens({category})` selects a discovered token scenario;
440
452
  omitting category selects the first token entry. The tool contains no project
@@ -551,6 +563,12 @@ framework compilation and Linaria extraction. The starter's
551
563
  - Keep `preserveEntrySignatures: "strict"`, `cssCodeSplit: true`,
552
564
  `assetsInlineLimit: 0`, `modulePreload: false`, and `write: false`. Include the
553
565
  supplied dependency-tracking plugin and the project's Linaria/framework plugins.
566
+ - Use `linariaTagResolver` from `@reqvire-org/ads/linaria` in WyW's `tagResolver`
567
+ option in both the product build and preview builder. It preserves opaque
568
+ custom-property values through atomic extraction, including whitespace needed
569
+ by arithmetic later substituted into `calc()`. Existing custom builders need
570
+ this integration explicitly; disabling minification does not fix extraction.
571
+ See the skill-local [atomic styling guide](../skills/ads-designer/references/atomic-styles.md#preserve-custom-property-arithmetic-during-extraction).
554
572
  - `showcase/mcp/main.tsx` exports `mountScenario(Scenario, container)` and returns
555
573
  an unmount callback. It imports the same project foundation used in production.
556
574
  It creates no App bridge; ADS supplies that bridge once.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@reqvire-org/ads",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
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": {
@@ -87,6 +87,14 @@
87
87
  "vite": "^6.0.7"
88
88
  },
89
89
  "license": "MIT",
90
+ "peerDependencies": {
91
+ "@linaria/atomic": "^8.0.0"
92
+ },
93
+ "peerDependenciesMeta": {
94
+ "@linaria/atomic": {
95
+ "optional": true
96
+ }
97
+ },
90
98
  "engines": {
91
99
  "node": ">=22.12.0"
92
100
  },
@@ -96,6 +104,7 @@
96
104
  "exports": {
97
105
  ".": "./dist/index.mjs",
98
106
  "./oxlint": "./dist/oxlint.mjs",
107
+ "./linaria": "./dist/linaria.mjs",
99
108
  "./tokens": "./dist/contracts/tokens.json",
100
109
  "./mcp": "./dist/mcp/server.mjs",
101
110
  "./mcp/build": "./dist/mcp/build-status.mjs",