@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.
- package/README.md +1 -1
- package/dist/bootstrap-ds/ADS.md +47 -7
- package/dist/bootstrap-ds/README.md +12 -10
- package/dist/bootstrap-ds/design-system/index.ts +1 -1
- package/dist/bootstrap-ds/design-system/patterns/token-preview/Scenario.tsx +4 -0
- package/dist/bootstrap-ds/design-system/patterns/token-preview/TokenPreview.tsx +31 -0
- package/dist/bootstrap-ds/design-system/patterns/token-preview/fixture.ts +8 -0
- package/dist/bootstrap-ds/design-system/tokens/Foundation.fixture.ts +44 -43
- package/dist/bootstrap-ds/design-system/tokens/Foundation.scenario.tsx +3 -13
- package/dist/bootstrap-ds/design-system/tokens/base.css +4 -1
- package/dist/bootstrap-ds/design-system/tokens/colors.css +2 -2
- package/dist/bootstrap-ds/ds.config.json +10 -0
- package/dist/check.mjs +29 -13
- package/dist/contracts/foundation.json +211 -0
- package/dist/foundation.mjs +114 -0
- package/dist/init.mjs +22 -5
- package/dist/mcp/apps.mjs +2 -2
- package/dist/mcp/build-status.mjs +22 -9
- package/dist/mcp/context.mjs +1 -1
- package/dist/mcp/preview-watch.mjs +35 -12
- package/dist/mcp/preview-widget.bundle.js +40 -38
- package/dist/mcp/project-widget.bundle.js +42 -38
- package/dist/mcp/prompts.mjs +1 -1
- package/dist/mcp/server.mjs +5 -5
- package/dist/preview-resources.mjs +1 -1
- package/dist/scenarios-compiler.mjs +86 -43
- package/dist/scenarios.mjs +16 -5
- package/dist/skills/ads-assimilate/SKILL.md +47 -12
- package/dist/skills/ads-assimilate/references/cli.md +3 -2
- package/dist/skills/ads-break-ui/SKILL.md +47 -12
- package/dist/skills/ads-break-ui/references/verification.md +4 -0
- package/dist/skills/ads-designer/SKILL.md +49 -14
- package/dist/skills/ads-designer/assets/bootstrap-ds/ADS.md +47 -7
- package/dist/skills/ads-designer/assets/bootstrap-ds/README.md +12 -10
- package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/index.ts +1 -1
- package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/patterns/token-preview/Scenario.tsx +4 -0
- package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/patterns/token-preview/TokenPreview.tsx +31 -0
- package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/patterns/token-preview/fixture.ts +8 -0
- package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/tokens/Foundation.fixture.ts +44 -43
- package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/tokens/Foundation.scenario.tsx +3 -13
- package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/tokens/base.css +4 -1
- package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/tokens/colors.css +2 -2
- package/dist/skills/ads-designer/assets/bootstrap-ds/ds.config.json +10 -0
- package/dist/skills/ads-designer/references/cli.md +3 -2
- package/dist/skills/ads-import/SKILL.md +47 -12
- package/dist/skills/ads-import/references/cli.md +3 -2
- package/dist/skills/ads-motion/SKILL.md +47 -12
- package/dist/token-policy.mjs +12 -0
- package/dist/token-samples.mjs +8 -0
- package/dist/units.mjs +34 -10
- package/docs/ADS.md +52 -9
- package/docs/ARCHITECTURE.md +44 -3
- package/docs/CHANGELOG.md +32 -1
- package/docs/GUIDE.md +17 -6
- package/docs/MCP.md +62 -9
- package/package.json +3 -2
package/dist/mcp/prompts.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export const reasoning = `ADS is development tooling. The project owns its components, patterns, token values and branding.
|
|
2
2
|
Start with ads_status and use its projectRoot, workflow and project commands; the MCP workspace may be nested inside the client repository. If ADS tools are not exposed, read ads://project/guide then ads://project/status. Find existing public UI with ads_catalog and load one relevant ADS workflow via ads_skills/ads_read_skill. UI implementation/refinement uses ads-designer; library adaptation ads-assimilate; ADS transfer ads-import; motion ads-motion; stress testing ads-break-ui. Domain-model workflows are separate. Inspect the relevant public API, local guide, token definitions and real showcase fixture before proposing changes. Keep source/instruction discovery scoped to the target project, ancestry and selected external sources; broad scans of /mnt or home are not needed to discover this ADS.
|
|
3
|
-
When ads_status.mutations.enabled, discover one owner with ads_units, read relevant ranges with ads_read_file and send only a Git unified diff through ads_apply_patch({unit,patch,dryRun}). A unit includes its own scenarios/fixtures; imported peers remain separate. tokens is the token foundation unit. Only that owner's public re-exports may change in the shared entry. Use dryRun to review validation. Rejected patches leave source unchanged; fix diagnostics rather than bypassing checks with direct writes. Shared globals use foundation; guides use documentation; assets use discovered assets/* units. Intentional configuration, registered project guards/builders and rule exceptions use configuration, which validates the proposed settings and refreshes its compiler. Keep mandatory ADS gates. For large/binary diffs, pass patchFile pointing to .ds/patches/<name>.patch rather than loading bytes into context; Git binary patches require installed Git. When editing is disabled, use local editing tools.
|
|
3
|
+
When ads_status.mutations.enabled, discover one owner with ads_units, read relevant ranges with ads_read_file and send only a Git unified diff through ads_apply_patch({unit,patch,dryRun}). A unit includes its own scenarios/fixtures; imported peers remain separate. tokens is the token foundation unit. Only that owner's public re-exports may change in the shared entry; preserve unrelated statements including export {};. Token manifests list companionFiles: include required foundation import changes only for canonical token files created/deleted in the same patch, preserving other imports/comments and canonical order. Use dryRun to review validation. Rejected patches leave source unchanged; fix diagnostics rather than bypassing checks with direct writes. Shared globals use foundation; guides use documentation; assets use discovered assets/* units. Intentional configuration, registered project guards/builders and rule exceptions use configuration, which validates the proposed settings and refreshes its compiler. Keep mandatory ADS gates. For large/binary diffs, pass patchFile pointing to .ds/patches/<name>.patch rather than loading bytes into context; Git binary patches require installed Git. When editing is disabled, use local editing tools.
|
|
4
4
|
Reason through Data (design-token values, semantic roles and relationships), UX (structure, behavior, accessibility, states and layout), and Skin (token-backed identity and visual states). Business data is a separate application-owned input, supplied through typed props or injected callbacks/services. The project uses Linaria extraction for owned styles; MCP adds no production runtime.
|
|
5
5
|
Choose an existing public component or pattern first. Keep application data/routing/effects in the application and fixture data/state beside its scenario. Change presentation in its owning ADS implementation, then demonstrate the same public API in the showcase. Do not create a widget-only replica or override component internals.
|
|
6
6
|
For token source inspection, call ads_tokens with a focused query. For token samples, components, patterns and composition, use ads_scenarios to select a path-derived id and invoke ads_show_scenario. Showcase and MCP mount that exact entry and its colocated fixture; no page initialization is required. The showcase is a minimal tool-owned shell plus an MCP client and Apps host. Its menus come from tools; it invokes the same entry tools and refreshes the active scenario through notifications. The reusable App loads framework runtime, foundation CSS, fonts and selected modules via MCP resources inside its sandbox. Keep project execution and styles out of the outer host; preserve mount/unmount cleanup and lazy dependencies. Use the existing ads_status showcase URL, or ads showcase when no MCP runtime is connected. Projects supply scenarios/fixtures and the compiler, not browser page files or another shell. Tool metadata selects an MCP App; a resources/read of HTML alone is not proof of rendered UI. If the host has no Apps view, give the configured showcase URL and use text/resources.
|
package/dist/mcp/server.mjs
CHANGED
|
@@ -43,12 +43,12 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
43
43
|
const project = () => { const context = projectContext(options); context.showcase.url = getShowcaseUrl(); return context; };
|
|
44
44
|
const subscribed = new Set();
|
|
45
45
|
const server = new Server({ name: "reqvire-ads", version: pkg.version }, {
|
|
46
|
-
instructions: `${serverInstructions} ${mutations ? "Guarded editing is enabled: discover one owner with ads_units, read relevant ranges with ads_read_file and send only a Git unified diff to ads_apply_patch with that unit ID. Include its own scenarios/fixtures and required re-exports together, never unrelated units. The server validates architecture, registered Oxlint and project checks before writing. Fix rejected patches in their owner. Intentional settings, project guards and builders use the configuration unit; mandatory ADS gates still run." : "Source mutation tools are disabled; source edits use the client's editor/filesystem tools."}`,
|
|
46
|
+
instructions: `${serverInstructions} ${mutations ? "Guarded editing is enabled: discover one owner with ads_units, read relevant ranges with ads_read_file and send only a Git unified diff to ads_apply_patch with that unit ID. Include its own scenarios/fixtures and required re-exports together, never unrelated units. Token manifests list companionFiles for canonical foundation import changes required by token files created/deleted in the same patch. The server validates architecture, registered Oxlint and project checks before writing. Fix rejected patches in their owner. Intentional settings, project guards and builders use the configuration unit; mandatory ADS gates still run." : "Source mutation tools are disabled; source edits use the client's editor/filesystem tools."}`,
|
|
47
47
|
capabilities: { tools: { listChanged: true }, resources: { listChanged: true, subscribe: true }, prompts: {},
|
|
48
48
|
extensions: { "io.modelcontextprotocol/ui": { mimeTypes: [APP_MIME] }, ...(era === "modern" ? { [SKILLS_EXTENSION]: { directoryRead: true } } : {}) } },
|
|
49
49
|
});
|
|
50
50
|
const getRegistry = () => cache.registry ??= inspectScenarios(options);
|
|
51
|
-
const scenarioApps = () => getRegistry().scenarios.map(item => ({ id: item.id, title: item.title, kind: item.kind, file: item.artifact }));
|
|
51
|
+
const scenarioApps = () => getRegistry().scenarios.map(item => ({ id: item.id, title: item.title, kind: item.kind, ...(item.samples ? { samples: item.samples } : {}), file: item.artifact }));
|
|
52
52
|
const tokenScenarios = () => getRegistry().scenarios.filter(item => item.kind === "tokens").map(item => ({ id: item.id, title: item.title, count: item.tokens.length }));
|
|
53
53
|
const allApps = () => [...options.config.mcp.apps, ...scenarioApps()];
|
|
54
54
|
const scenarioWidget = () => options.config.mcp.previewManifest ? PREVIEW_APP_URI : SCENARIO_APP_URI;
|
|
@@ -214,12 +214,12 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
214
214
|
}
|
|
215
215
|
return json({ widget: { resourceUri: TOKENS_APP_URI }, category: null, tokenCount: inspectTokens(root).tokens.length, dataUri: `${PROJECT_URI}/tokens`, showcaseUrl: getShowcaseUrl() });
|
|
216
216
|
} },
|
|
217
|
-
{ name: "ads_units", description: "Discover editable ADS units and their files: components/patterns with colocated previews; composition; tokens; foundation shared files; documentation; assets/brand, assets/fonts and asset groups; configuration with ds.config.json, package/build/lint configuration and registered project guard/compiler sources. List summaries with query, or pass an exact unit ID for its manifest. Imported peers stay separate. New owners follow the existing colocation convention.", schema: query.extend({ unit: z.string().min(1).max(256).optional() }), call: ({ unit, query: search = "", limit }) => {
|
|
217
|
+
{ name: "ads_units", description: "Discover editable ADS units and their files: components/patterns with colocated previews; composition; tokens; foundation shared files; documentation; assets/brand, assets/fonts and asset groups; configuration with ds.config.json, package/build/lint configuration and registered project guard/compiler sources. List summaries with query, or pass an exact unit ID for its manifest. Token manifests include companionFiles with the foundation entry path and allowed import changes. Imported peers stay separate. New owners follow the existing colocation convention.", schema: query.extend({ unit: z.string().min(1).max(256).optional() }), call: ({ unit, query: search = "", limit }) => {
|
|
218
218
|
const matches = edits.units().filter(item => unit ? item.id === unit : item.id.toLowerCase().includes(search.toLowerCase()));
|
|
219
|
-
return json({ total: matches.length, units: matches.slice(0, limit).map(item => ({ id: item.id, kind: item.kind, fileCount: item.files.length, ...(unit ? { files: item.files.slice(0, 200), filesTruncated: item.files.length > 200 } : {}) })) });
|
|
219
|
+
return json({ total: matches.length, units: matches.slice(0, limit).map(item => ({ id: item.id, kind: item.kind, fileCount: item.files.length, ...(unit ? { files: item.files.slice(0, 200), filesTruncated: item.files.length > 200, ...(item.companionFiles ? { companionFiles: item.companionFiles } : {}) } : {}) })) });
|
|
220
220
|
} },
|
|
221
221
|
{ name: "ads_read_file", description: "Read a bounded range of ADS or registered configuration/extension source. Returns unit, full-file digest, startLine and nextOffset; default/max is 12000 UTF-16 characters. Binary assets return byte count/digest and no encoded content. Paths come from ads_units or discovery. Missing new paths return digest null. Read only relevant context before preparing a Git patch.", schema: z.object({ path: z.string().min(1).max(500), offset: z.number().int().min(0).default(0), limit: z.number().int().min(1).max(mutationLimits.readChars).default(mutationLimits.readChars) }).strict(), call: ({ path: file, ...range }) => json(edits.read(file, range)) },
|
|
222
|
-
...(mutations ? [{ name: "ads_apply_patch", description: "Apply one Git diff to one ADS unit from ads_units. Pass exactly one of patch (inline) or patchFile (a regular .patch/.diff under .ds/patches/, for large/binary diffs without model-context bytes). Text uses --- a/path, +++ b/path and exact @@ hunks; /dev/null creates/deletes. Git binary patches require git diff --binary --full-index and installed Git. A unit owns its implementation, previews and fixtures; imported peers are separate. Foundation, documentation, assets and configuration are editable too. Configuration changes may register/edit project guards and builders; the proposed schema, mandatory core/Oxlint and candidate project checks must pass. Only selected-unit public re-exports may change. Set dryRun true to validate without writing. Fix rejected patches and inspect current previews after acceptance.",
|
|
222
|
+
...(mutations ? [{ name: "ads_apply_patch", description: "Apply one Git diff to one ADS unit from ads_units. Pass exactly one of patch (inline) or patchFile (a regular .patch/.diff under .ds/patches/, for large/binary diffs without model-context bytes). Text uses --- a/path, +++ b/path and exact @@ hunks; /dev/null creates/deletes. Git binary patches require git diff --binary --full-index and installed Git. A unit owns its implementation, previews and fixtures; imported peers are separate. Foundation, documentation, assets and configuration are editable too. Configuration changes may register/edit project guards and builders; the proposed schema, mandatory core/Oxlint and candidate project checks must pass. Only selected-unit public re-exports may change; preserve unrelated statements including export {};. A tokens patch may include its companionFiles foundation entry only to add/remove canonical imports required by token files created/deleted in this patch, preserving other imports/comments and canonical order. Set dryRun true to validate without writing. Fix rejected patches and inspect current previews after acceptance.",
|
|
223
223
|
schema: z.object({ unit: z.string().min(1).max(256), dryRun: z.boolean(), patch: z.string().min(1).max(mutationLimits.patchBytes).optional(), patchFile: z.string().min(1).max(500).optional() }).strict().refine(args => (args.patch !== undefined) !== (args.patchFile !== undefined), "Provide exactly one of patch or patchFile."),
|
|
224
224
|
annotations: { readOnlyHint: false, destructiveHint: true, idempotentHint: false, openWorldHint: false }, _meta: { ui: { visibility: ["model"] } },
|
|
225
225
|
call: async (args, signal) => {
|
|
@@ -113,7 +113,7 @@ export function createPreviewResources(output, options, entries) {
|
|
|
113
113
|
const scenarios = entries.map((entry, index) => {
|
|
114
114
|
const chunk = bundle.find(item => item.type === "chunk" && item.facadeModuleId === `\0ads-preview-entry:${index}`);
|
|
115
115
|
if (!chunk) throw new Error(`Missing compiled scenario entry: ${entry.id}`);
|
|
116
|
-
return { id: entry.id, title: entry.title, kind: entry.kind, module: uris.get(chunk.fileName) };
|
|
116
|
+
return { id: entry.id, title: entry.title, kind: entry.kind, ...(entry.samples ? { samples: entry.samples } : {}), module: uris.get(chunk.fileName) };
|
|
117
117
|
});
|
|
118
118
|
const graph = { schemaVersion: 1, framework: options.config.framework, assets: records, scenarios };
|
|
119
119
|
const revision = hash(JSON.stringify(graph));
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { createHash, randomUUID } from "node:crypto";
|
|
2
|
-
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, watch, writeFileSync } from "node:fs";
|
|
2
|
+
import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameSync, rmSync, statSync, watch, writeFileSync } from "node:fs";
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import { loadConfig, isInside } from "./config.mjs";
|
|
5
5
|
import { inspectScenarios } from "./scenarios.mjs";
|
|
@@ -71,7 +71,7 @@ export function createPreviewCache(options, compilerFiles = []) {
|
|
|
71
71
|
}
|
|
72
72
|
return { inputs, reusable, validated: stamp => value.validation === stamp,
|
|
73
73
|
assertCompiler() {
|
|
74
|
-
if (adapterDigest() !== loadedAdapter) throw new Error("Preview compiler code or dependencies changed.
|
|
74
|
+
if (adapterDigest() !== loadedAdapter) throw Object.assign(new Error("Preview compiler code or dependencies changed. Invoke the preview again to restart the builder and load current code."), { code: "ADS_PREVIEW_RESTART" });
|
|
75
75
|
},
|
|
76
76
|
validate(stamp) { value.validation = stamp; save(); },
|
|
77
77
|
record(entry, dependencies) {
|
|
@@ -93,12 +93,17 @@ export async function runScenarioCompiler(directory, { watched = false, validate
|
|
|
93
93
|
function registry() {
|
|
94
94
|
options = loadConfig(root);
|
|
95
95
|
const result = inspectScenarios(options);
|
|
96
|
-
if (result.diagnostics.length) throw new Error(result.diagnostics.map(item => `${item.file}: ${item.message}`).join("\n"));
|
|
96
|
+
if (result.diagnostics.length) throw Object.assign(new Error(result.diagnostics.map(item => `${item.file}: ${item.message}`).join("\n")), { scenarios: result.scenarios });
|
|
97
97
|
mkdirSync(path.join(root, ".ds/scenarios"), { recursive: true });
|
|
98
98
|
writeFileSync(path.join(root, ".ds/scenarios/manifest.json"), JSON.stringify({ schemaVersion: 1, scenarios: result.scenarios }, null, 2));
|
|
99
99
|
return result.scenarios;
|
|
100
100
|
}
|
|
101
|
-
let entries =
|
|
101
|
+
let entries = [], registryFailure;
|
|
102
|
+
try { entries = registry(); }
|
|
103
|
+
catch (error) {
|
|
104
|
+
if (!watched) throw error;
|
|
105
|
+
registryFailure = error; entries = error.scenarios ?? [];
|
|
106
|
+
}
|
|
102
107
|
const reporter = createBuildReporter(root, { apps: entries.map(entry => ({ id: entry.id, file: entry.artifact })) });
|
|
103
108
|
const requested = new Set();
|
|
104
109
|
const pending = new Set();
|
|
@@ -108,6 +113,12 @@ export async function runScenarioCompiler(directory, { watched = false, validate
|
|
|
108
113
|
let generation = 0;
|
|
109
114
|
let publishedStamp;
|
|
110
115
|
let validation;
|
|
116
|
+
function failure(error) {
|
|
117
|
+
try { reporter.failure(error); } catch (reportError) { console.error(reportError); }
|
|
118
|
+
console.error(error);
|
|
119
|
+
if (!watched) process.exitCode = 1;
|
|
120
|
+
}
|
|
121
|
+
if (registryFailure) failure(registryFailure);
|
|
111
122
|
function gate(stamp) {
|
|
112
123
|
if (cache.validated(stamp)) return Promise.resolve();
|
|
113
124
|
return validation ??= Promise.resolve().then(validate).then(() => {
|
|
@@ -119,8 +130,8 @@ export async function runScenarioCompiler(directory, { watched = false, validate
|
|
|
119
130
|
if (buildGraph) return compileGraph();
|
|
120
131
|
const revision = reporter.begin(entry.id);
|
|
121
132
|
const started = generation;
|
|
122
|
-
const stamp = cache.inputs();
|
|
123
133
|
try {
|
|
134
|
+
const stamp = cache.inputs();
|
|
124
135
|
cache.assertCompiler();
|
|
125
136
|
const reuse = cache.reusable(entry);
|
|
126
137
|
const dependencies = new Set();
|
|
@@ -147,7 +158,7 @@ export async function runScenarioCompiler(directory, { watched = false, validate
|
|
|
147
158
|
const failure = outcomes.find(outcome => outcome.status === "rejected");
|
|
148
159
|
if (failure) throw failure.reason;
|
|
149
160
|
cache.assertCompiler();
|
|
150
|
-
if (started !== generation || stamp !== cache.inputs()) return; // Watch queued a newer revision.
|
|
161
|
+
if (stopped || started !== generation || stamp !== cache.inputs()) return; // Watch queued a newer revision.
|
|
151
162
|
if (!reuse) cache.record(entry, dependencies);
|
|
152
163
|
reporter.ready(entry.id, revision);
|
|
153
164
|
} catch (error) {
|
|
@@ -160,24 +171,24 @@ export async function runScenarioCompiler(directory, { watched = false, validate
|
|
|
160
171
|
async function compileGraph() {
|
|
161
172
|
const revisions = new Map(entries.map(entry => [entry.id, reporter.begin(entry.id)]));
|
|
162
173
|
const started = generation;
|
|
163
|
-
const stamp = cache.inputs();
|
|
164
|
-
const dependencies = new Set();
|
|
165
|
-
function assets(directory) {
|
|
166
|
-
for (const item of readdirSync(directory, { withFileTypes: true })) {
|
|
167
|
-
if (item.isSymbolicLink() || ["node_modules", ".ds", ".vite", "dist"].includes(item.name)) continue;
|
|
168
|
-
const file = path.join(directory, item.name);
|
|
169
|
-
if (item.isDirectory()) assets(file);
|
|
170
|
-
else if (/\.(?:css|svg|png|jpe?g|webp|gif|woff2?|ttf|otf)$/.test(file)) dependencies.add(file);
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
assets(options.dsRoot);
|
|
174
|
-
const track = { name: "ads-preview-graph-dependencies", generateBundle() {
|
|
175
|
-
for (const id of this.getModuleIds()) {
|
|
176
|
-
const file = id.split("?")[0];
|
|
177
|
-
if (path.isAbsolute(file) && existsSync(file)) dependencies.add(file);
|
|
178
|
-
}
|
|
179
|
-
} };
|
|
180
174
|
try {
|
|
175
|
+
const stamp = cache.inputs();
|
|
176
|
+
const dependencies = new Set();
|
|
177
|
+
function assets(directory) {
|
|
178
|
+
for (const item of readdirSync(directory, { withFileTypes: true })) {
|
|
179
|
+
if (item.isSymbolicLink() || ["node_modules", ".ds", ".vite", "dist"].includes(item.name)) continue;
|
|
180
|
+
const file = path.join(directory, item.name);
|
|
181
|
+
if (item.isDirectory()) assets(file);
|
|
182
|
+
else if (/\.(?:css|svg|png|jpe?g|webp|gif|woff2?|ttf|otf)$/.test(file)) dependencies.add(file);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
assets(options.dsRoot);
|
|
186
|
+
const track = { name: "ads-preview-graph-dependencies", generateBundle() {
|
|
187
|
+
for (const id of this.getModuleIds()) {
|
|
188
|
+
const file = id.split("?")[0];
|
|
189
|
+
if (path.isAbsolute(file) && existsSync(file)) dependencies.add(file);
|
|
190
|
+
}
|
|
191
|
+
} };
|
|
181
192
|
cache.assertCompiler();
|
|
182
193
|
let reusable = entries.every(entry => cache.reusable(entry));
|
|
183
194
|
if (reusable) { try { reusable = Boolean(readPreviewManifest(options, { verify: true })); } catch { reusable = false; } }
|
|
@@ -185,7 +196,7 @@ export async function runScenarioCompiler(directory, { watched = false, validate
|
|
|
185
196
|
const failure = outcomes.find(outcome => outcome.status === "rejected");
|
|
186
197
|
if (failure) throw failure.reason;
|
|
187
198
|
cache.assertCompiler();
|
|
188
|
-
if (started !== generation || stamp !== cache.inputs()) return;
|
|
199
|
+
if (stopped || started !== generation || stamp !== cache.inputs()) return;
|
|
189
200
|
const publication = outcomes[1].value;
|
|
190
201
|
if (publication) {
|
|
191
202
|
publication.publish(reporter.session);
|
|
@@ -209,15 +220,16 @@ export async function runScenarioCompiler(directory, { watched = false, validate
|
|
|
209
220
|
if (compiling) return;
|
|
210
221
|
compiling = true;
|
|
211
222
|
try {
|
|
212
|
-
while (pending.size && !stopped) {
|
|
223
|
+
while (pending.size && !registryFailure && !stopped) {
|
|
213
224
|
const id = pending.has(active) ? active : pending.values().next().value;
|
|
214
225
|
pending.delete(id);
|
|
215
226
|
const entry = entries.find(entry => entry.id === id);
|
|
216
227
|
if (entry) await compile(entry);
|
|
217
228
|
}
|
|
218
|
-
}
|
|
229
|
+
} catch (error) { failure(error); }
|
|
230
|
+
finally { compiling = false; }
|
|
219
231
|
}
|
|
220
|
-
function request() {
|
|
232
|
+
function request(startBuild = true) {
|
|
221
233
|
const file = process.env.ADS_MCP_REQUEST_FILE;
|
|
222
234
|
if (!file || !existsSync(file)) return;
|
|
223
235
|
try {
|
|
@@ -231,8 +243,8 @@ export async function runScenarioCompiler(directory, { watched = false, validate
|
|
|
231
243
|
if (!buildGraph || publishedStamp !== cache.inputs()) pending.add(entry.id);
|
|
232
244
|
}
|
|
233
245
|
}
|
|
234
|
-
void drain();
|
|
235
|
-
} catch (error) {
|
|
246
|
+
if (startBuild) void drain();
|
|
247
|
+
} catch (error) { failure(error); }
|
|
236
248
|
}
|
|
237
249
|
if (!watched) {
|
|
238
250
|
for (const entry of entries) pending.add(entry.id);
|
|
@@ -241,8 +253,8 @@ export async function runScenarioCompiler(directory, { watched = false, validate
|
|
|
241
253
|
}
|
|
242
254
|
let timer;
|
|
243
255
|
const changed = new Set();
|
|
244
|
-
function sourceChanged(_event, file) {
|
|
245
|
-
if (!file || !/\.(?:[cm]?[jt]sx?|css|html|json|md|svg|png|jpe?g|webp|gif|woff2?|ttf|otf|lock|lockb)$/.test(file)) return;
|
|
256
|
+
function sourceChanged(_event, file, topology = false) {
|
|
257
|
+
if (!file || !topology && !/\.(?:[cm]?[jt]sx?|css|html|json|md|svg|png|jpe?g|webp|gif|woff2?|ttf|otf|lock|lockb)$/.test(file)) return;
|
|
246
258
|
const absolute = path.join(root, file);
|
|
247
259
|
if (file.split(path.sep).some(name => ["node_modules", ".git", ".ds", ".vite", "coverage"].includes(name)) || options.config.assets.generatedPaths.some(base => isInside(path.resolve(root, base), absolute))) return;
|
|
248
260
|
changed.add(absolute);
|
|
@@ -253,7 +265,10 @@ export async function runScenarioCompiler(directory, { watched = false, validate
|
|
|
253
265
|
const changes = [...changed]; changed.clear();
|
|
254
266
|
try {
|
|
255
267
|
entries = registry();
|
|
268
|
+
registryFailure = undefined;
|
|
256
269
|
reporter.setApps(entries.map(entry => ({ id: entry.id, file: entry.artifact })));
|
|
270
|
+
reporter.clearFailure();
|
|
271
|
+
request(false);
|
|
257
272
|
for (const id of requested) if (!entries.some(entry => entry.id === id)) { requested.delete(id); pending.delete(id); }
|
|
258
273
|
// Every source change needs validation; unaffected artifacts need no
|
|
259
274
|
// compilation. Check active first, then rebuild only changed dependencies.
|
|
@@ -263,25 +278,53 @@ export async function runScenarioCompiler(directory, { watched = false, validate
|
|
|
263
278
|
}
|
|
264
279
|
if (changes.length) void drain();
|
|
265
280
|
} catch (error) {
|
|
266
|
-
|
|
267
|
-
console.error(error);
|
|
281
|
+
registryFailure = error; failure(error);
|
|
268
282
|
}
|
|
269
283
|
}, 100);
|
|
270
284
|
}
|
|
271
285
|
// Do not allocate recursive OS watches for node_modules or generated trees.
|
|
272
|
-
const watchers =
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
286
|
+
const watchers = new Map();
|
|
287
|
+
function addWatch(directory, callback, recursive = false) {
|
|
288
|
+
if (stopped) return false;
|
|
289
|
+
try {
|
|
290
|
+
const stat = statSync(directory), identity = `${stat.dev}:${stat.ino}`;
|
|
291
|
+
if (watchers.get(directory)?.identity === identity) return false;
|
|
292
|
+
watchers.get(directory)?.watcher.close();
|
|
293
|
+
const watcher = watch(directory, { recursive }, callback);
|
|
294
|
+
watchers.set(directory, { watcher, identity });
|
|
295
|
+
watcher.on("error", error => {
|
|
296
|
+
watcher.close();
|
|
297
|
+
if (watchers.get(directory)?.watcher === watcher) watchers.delete(directory);
|
|
298
|
+
failure(error);
|
|
299
|
+
});
|
|
300
|
+
return true;
|
|
301
|
+
} catch (error) { failure(error); }
|
|
278
302
|
}
|
|
279
|
-
|
|
280
|
-
if (
|
|
281
|
-
|
|
303
|
+
function reconcileWatches(initial = false) {
|
|
304
|
+
if (stopped) return;
|
|
305
|
+
try {
|
|
306
|
+
addWatch(root, sourceChanged);
|
|
307
|
+
const wanted = new Set([root, path.join(root, ".ds")]);
|
|
308
|
+
for (const entry of readdirSync(root, { withFileTypes: true })) {
|
|
309
|
+
const directory = path.join(root, entry.name);
|
|
310
|
+
if (!entry.isDirectory() || entry.isSymbolicLink() || ["node_modules", ".git", ".ds", ".vite", "coverage"].includes(entry.name)
|
|
311
|
+
|| options.config.assets.generatedPaths.some(base => isInside(path.resolve(root, base), directory))) continue;
|
|
312
|
+
wanted.add(directory);
|
|
313
|
+
if (addWatch(directory, (event, file) => sourceChanged(event, file && path.join(entry.name, file)), true) && !initial) sourceChanged("rename", entry.name, true);
|
|
314
|
+
}
|
|
315
|
+
mkdirSync(path.join(root, ".ds"), { recursive: true });
|
|
316
|
+
addWatch(path.join(root, ".ds"), (_event, file) => { if (file === path.basename(process.env.ADS_MCP_REQUEST_FILE ?? "")) request(); });
|
|
317
|
+
for (const [directory, { watcher }] of watchers) if (!wanted.has(directory) || !existsSync(directory)) {
|
|
318
|
+
watcher.close(); watchers.delete(directory);
|
|
319
|
+
if (!initial && directory !== root && directory !== path.join(root, ".ds")) sourceChanged("rename", path.relative(root, directory), true);
|
|
320
|
+
}
|
|
321
|
+
} catch (error) { failure(error); }
|
|
322
|
+
}
|
|
323
|
+
reconcileWatches(true);
|
|
324
|
+
const reconcileTimer = setInterval(reconcileWatches, 1000);
|
|
282
325
|
request();
|
|
283
326
|
console.log(`Scenario compiler ready: ${entries.length} entries; builds are requested on demand.`);
|
|
284
327
|
for (const signal of ["SIGINT", "SIGTERM"]) process.once(signal, () => {
|
|
285
|
-
stopped = true; clearTimeout(timer); for (const watcher of watchers
|
|
328
|
+
stopped = true; clearTimeout(timer); clearInterval(reconcileTimer); for (const { watcher } of watchers.values()) watcher.close();
|
|
286
329
|
});
|
|
287
330
|
}
|
package/dist/scenarios.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import { isDevelopmentFile, isInside, isScenarioFile, sourceFiles } from "./conf
|
|
|
6
6
|
import { projectCompilerOptions } from "./boundaries.mjs";
|
|
7
7
|
import { inspectTokens } from "./tokens.mjs";
|
|
8
8
|
import { isSharedToken } from "./token-policy.mjs";
|
|
9
|
+
import { validTokenSamples } from "./token-samples.mjs";
|
|
9
10
|
|
|
10
11
|
export const scenarioArtifact = (root, id) => path.join(root, ".ds/scenarios", id, "index.html");
|
|
11
12
|
|
|
@@ -104,12 +105,16 @@ export function inspectScenarios(options, suppliedProgram) {
|
|
|
104
105
|
}
|
|
105
106
|
const fixtureBase = path.basename(file).startsWith("Scenario.") ? path.join(path.dirname(file), "fixture") : file.replace(/\.scenario\.[^.]+$/, ".fixture");
|
|
106
107
|
if (![".ts", ".tsx", ".js", ".jsx", ".mts", ".cts", ".mjs", ".cjs", ".json"].some(extension => existsSync(fixtureBase + extension))) report(file, "Keep this scenario's inputs beside it: fixture.ts (directory entry) or <Owner>.fixture.ts (flat entry). An empty fixture is valid for a component without inputs.");
|
|
107
|
-
if (!metadata || Object.keys(metadata).some((key) => !["title", "covers", "tokens"].includes(key)) || typeof metadata.title !== "string" || !metadata.title.trim() || !Array.isArray(metadata.covers) ||
|
|
108
|
-
|
|
108
|
+
if (!metadata || Object.keys(metadata).some((key) => !["title", "covers", "tokens", "samples"].includes(key)) || typeof metadata.title !== "string" || !metadata.title.trim() || !Array.isArray(metadata.covers) || !metadata.covers.length || metadata.covers.some((name) => typeof name !== "string" || !known.get(name)?.renderable)
|
|
109
|
+
|| Object.hasOwn(metadata, "tokens") && (!Array.isArray(metadata.tokens) || metadata.tokens.some(name => typeof name !== "string" || !name.startsWith("--")))
|
|
110
|
+
|| kind === "tokens" && (Object.hasOwn(metadata, "tokens") || !validTokenSamples(metadata.samples) || !metadata.covers.some(name => known.get(name)?.kind === "patterns"))
|
|
111
|
+
|| Object.hasOwn(metadata, "samples") && !validTokenSamples(metadata.samples)) {
|
|
112
|
+
report(file, "Export static metadata with nonempty covers. Token scenarios must render a project-owned public review pattern and declare samples: fixture.samples, a literal list of { token, property, probe } CSS samples. A plain value list or covers: [] is incomplete."); continue;
|
|
109
113
|
}
|
|
110
114
|
if (!["composition", "tokens"].includes(kind) && metadata.covers.some((name) => known.get(name).kind !== kind)) report(file, "Component scenarios cover primitives; pattern scenarios cover patterns. Use composition/ for larger interfaces.");
|
|
111
|
-
|
|
112
|
-
|
|
115
|
+
const tokens = [...new Set([...(metadata.tokens ?? []), ...(metadata.samples ?? []).map(sample => sample.token)])];
|
|
116
|
+
for (const name of tokens) if (!tokenNames.has(name)) diagnostics.push({ file: path.relative(root, file).replaceAll("\\", "/"), rule: "token-scenario-reference", line: 1, column: 1, message: `Scenario references undeclared token ${name}. Use a real foundation token.` });
|
|
117
|
+
scenarios.push({ id, kind, title: metadata.title, covers: [...new Set(metadata.covers)], tokens, ...(metadata.samples ? { samples: metadata.samples } : {}), file: path.relative(root, file).replaceAll("\\", "/"), artifact: path.relative(root, scenarioArtifact(root, id)).replaceAll("\\", "/") });
|
|
113
118
|
}
|
|
114
119
|
return { scenarios: scenarios.sort((a, b) => a.id.localeCompare(b.id)), diagnostics };
|
|
115
120
|
}
|
|
@@ -161,6 +166,7 @@ export function inspectScenarioCoverage(options, program, report) {
|
|
|
161
166
|
const source = program.getSourceFile(path.join(options.root, scenario.file));
|
|
162
167
|
const rendered = new Set();
|
|
163
168
|
const referencedTokens = new Set();
|
|
169
|
+
let samplesPassed = false;
|
|
164
170
|
const visited = new Set();
|
|
165
171
|
function visit(node, reached = false) {
|
|
166
172
|
if (!node || visited.has(node)) return;
|
|
@@ -175,6 +181,10 @@ export function inspectScenarioCoverage(options, program, report) {
|
|
|
175
181
|
const symbol = canonical(symbolOf(expression));
|
|
176
182
|
const names = identities.get(symbol?.valueDeclaration ?? symbol?.declarations?.[0]);
|
|
177
183
|
for (const name of names ?? []) rendered.add(name);
|
|
184
|
+
if (scenario.kind === "tokens" && [...names ?? []].some(name => scenario.covers.includes(name))) {
|
|
185
|
+
const attributes = ts.isCallExpression(node) ? literal(node.arguments[1], checker) : Object.assign({}, ...node.attributes.properties.map(attribute => ts.isJsxSpreadAttribute(attribute) ? literal(attribute.expression, checker) : ts.isJsxAttribute(attribute) && ts.isIdentifier(attribute.name) && attribute.initializer && ts.isJsxExpression(attribute.initializer) ? { [attribute.name.text]: literal(attribute.initializer.expression, checker) } : {}));
|
|
186
|
+
if (JSON.stringify(attributes?.samples) === JSON.stringify(scenario.samples)) samplesPassed = true;
|
|
187
|
+
}
|
|
178
188
|
}
|
|
179
189
|
// Follow local adapters reachable from the default entry. Unused example
|
|
180
190
|
// functions do not satisfy coverage; production implementations are never
|
|
@@ -192,9 +202,10 @@ export function inspectScenarioCoverage(options, program, report) {
|
|
|
192
202
|
else covered.add(name);
|
|
193
203
|
}
|
|
194
204
|
for (const name of referencedTokens) if (!declaredTokens.has(name)) report(source.fileName, "token-scenario-reference", `Reachable scenario input references undeclared token ${name}.`);
|
|
205
|
+
if (scenario.kind === "tokens" && !samplesPassed) report(source.fileName, "token-scenario-render", "Pass the declared fixture.samples into a rendered public review pattern's samples prop. Printing token names or reading unused fixtures is not sample coverage.");
|
|
195
206
|
for (const name of scenario.tokens) {
|
|
196
207
|
if (!referencedTokens.has(name)) report(source.fileName, "token-scenario-render", `${name} is declared in tokens but has no reachable reference from the default scenario entry. Consume the real token fixture; metadata alone does not count.`);
|
|
197
|
-
else if (declaredTokens.has(name)) coveredTokens.add(name);
|
|
208
|
+
else if (scenario.kind === "tokens" && samplesPassed && declaredTokens.has(name)) coveredTokens.add(name);
|
|
198
209
|
}
|
|
199
210
|
}
|
|
200
211
|
for (const item of ui.filter((item) => item.renderable && !covered.has(item.name))) report(item.file, "scenario-coverage", `Public UI ${item.name} needs a colocated, registered scenario render reference. Related parts may share a scenario.`);
|
|
@@ -22,9 +22,15 @@ relevant ranges with `ads_read_file` and send only a Git unified diff through
|
|
|
22
22
|
`ads_apply_patch({ unit, patch, dryRun })`. A unit includes its own implementation,
|
|
23
23
|
scenarios and fixtures; imported peers remain separate. `tokens` is the token
|
|
24
24
|
foundation unit. Unit discovery and file ownership are distinct from preview IDs.
|
|
25
|
-
Include only that owner's required re-exports in the public entry
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
Include only that owner's required re-exports in the public entry; preserve
|
|
26
|
+
unrelated statements. Its real re-export may accompany removal of a redundant
|
|
27
|
+
`export {};` placeholder; do not add empty exports. A `tokens` manifest lists the foundation
|
|
28
|
+
entry in `companionFiles`: when creating/deleting a canonical token file such as
|
|
29
|
+
`fonts.css`, include only its required import change there in the same patch.
|
|
30
|
+
Preserve other imports/comments and the canonical order. Use `dryRun: true` to
|
|
31
|
+
inspect validation; applying re-runs the gates. Fix rejected patches in their
|
|
32
|
+
owner. Report unsupported coupled changes instead of bypassing a rejection with
|
|
33
|
+
direct source writes. Shared styles/helpers use `foundation`, root guides use
|
|
28
34
|
`documentation`, and branding/fonts use their discovered `assets/*` unit.
|
|
29
35
|
Intentional configuration, rule exceptions, registered guard code and compiler
|
|
30
36
|
changes use `configuration`; validate the proposed settings and keep mandatory
|
|
@@ -209,15 +215,25 @@ entry independently and verify real behavior at narrow/wide sizes in both themes
|
|
|
209
215
|
The older `showcase-coverage` policy separately checks application references.
|
|
210
216
|
|
|
211
217
|
Token previews use that same scenario pipeline. Colocate a token scenario and
|
|
212
|
-
fixture under the configured token directory.
|
|
213
|
-
`{ title:
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
218
|
+
fixture under the configured token directory. Declare metadata
|
|
219
|
+
`{ title: fixture.title, covers: ["TokenSamples"], samples: fixture.samples }`.
|
|
220
|
+
The renderer name is project-owned; use its real public API. Each literal fixture
|
|
221
|
+
sample declares `{ token, property, probe }`: a real token, the CSS property its
|
|
222
|
+
sample demonstrates, and a valid test value different from that token's current
|
|
223
|
+
value. Pass `samples` into the rendered public review pattern. Names, sample text,
|
|
224
|
+
bindings and probes are fixture data; displayed values come from foundation CSS.
|
|
225
|
+
There is no property catalogue, token-name mapping or probe palette in ADS.
|
|
226
|
+
Every token needs sample coverage; fabricated tokens, missing bindings/probes,
|
|
227
|
+
unused fixtures and value-only lists fail the static gates. `covers: []` is invalid.
|
|
228
|
+
During App rendering, the browser checks sample markers `data-ads-token` and
|
|
229
|
+
`data-ads-property`, validates CSS with its own `CSS.supports`, and temporarily
|
|
230
|
+
probes each token to verify that the declared sample property responds. It restores
|
|
231
|
+
values immediately. Missing, hidden, invalid or hardcoded samples produce a preview
|
|
232
|
+
error instead of a successful review. These browser checks apply to the mounted
|
|
233
|
+
scenario/theme, not every application state or pixel-level design quality.
|
|
234
|
+
Keep styles in the project renderer; do not add inline-style exemptions to scenarios.
|
|
235
|
+
Fix `token-scenario-reference`, `token-scenario-render` and `token-scenario-coverage`
|
|
236
|
+
findings. Metadata and unused fixture fields do not count. Discover exact token IDs with `ads_scenarios({kind:"tokens"})`; mount them
|
|
221
237
|
with `ads_show_scenario({id})` or `ads_show_tokens({category:id})`. With the shared
|
|
222
238
|
resource compiler, omitting category opens the first project token scenario.
|
|
223
239
|
Legacy compilers retain the generic definition inspector. Use `ads_tokens` for
|
|
@@ -235,6 +251,25 @@ Use local simulated services in fixtures. Tests may import fixtures; load real
|
|
|
235
251
|
foundation CSS rather than assigning shared token values in JavaScript. Keep
|
|
236
252
|
full-app integration harnesses and workflow coverage in application test support.
|
|
237
253
|
|
|
254
|
+
Foundation CSS is checked, not an escape hatch. Value sheets define shared tokens
|
|
255
|
+
on the document root/themes; `fonts.css` holds font-face metadata. `base.css` has
|
|
256
|
+
three closed roles: root/body typography, canvas and theme defaults; limited native
|
|
257
|
+
normalization; and global focus indicators/reduced-motion rules. Every selector
|
|
258
|
+
branch, property and value must fit its role. Token-backed control skins, layout,
|
|
259
|
+
interactive state rules and owner hooks are rejected even through `:where()`.
|
|
260
|
+
Do not copy an unchecked upstream preflight or turn a component repair into a reset.
|
|
261
|
+
Put skin/state/animation in the Linaria owner, and scenario layout variations in
|
|
262
|
+
the public review pattern's typed API. Browser review still verifies the effects.
|
|
263
|
+
|
|
264
|
+
Invalid metadata and source failures keep the managed compiler alive for repairs.
|
|
265
|
+
Read `ads_status.build` or `ads://project/build`, fix the owner, then wait for current
|
|
266
|
+
successful output. If the builder exited, fix its command and invoke the preview
|
|
267
|
+
again; ADS starts one fresh child without an MCP reconnect. Compiler-code changes
|
|
268
|
+
also reload on the next preview invocation. Reconnect the client when upgrading
|
|
269
|
+
the ADS server package itself. Persistent SSE/notification streams make
|
|
270
|
+
`networkidle` unsuitable: wait for the App's current ready/error state instead.
|
|
271
|
+
|
|
272
|
+
|
|
238
273
|
|
|
239
274
|
|
|
240
275
|
Register component/project Oxlint plugins and rules in `ds.config.json`. Use
|
|
@@ -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.
|
|
58
|
+
ads_tool_package="/absolute/path/to/reqvire-org-ads-0.3.2.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,8 @@ 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
|
|
96
|
+
Use ADS 0.3.2 or later for the closed foundation contract and recoverable
|
|
97
|
+
shared-resource preview compiler. `mcp` starts stdio
|
|
97
98
|
plus its local website; `showcase` starts the browser-only MCP runtime. Open the existing runtime URL from `ads_status` when
|
|
98
99
|
connected; otherwise use `npm run dev:showcase` or the installed `ads showcase`.
|
|
99
100
|
The tool client discovers entries and renders MCP Apps. Init adds the launch
|
|
@@ -25,9 +25,15 @@ relevant ranges with `ads_read_file` and send only a Git unified diff through
|
|
|
25
25
|
`ads_apply_patch({ unit, patch, dryRun })`. A unit includes its own implementation,
|
|
26
26
|
scenarios and fixtures; imported peers remain separate. `tokens` is the token
|
|
27
27
|
foundation unit. Unit discovery and file ownership are distinct from preview IDs.
|
|
28
|
-
Include only that owner's required re-exports in the public entry
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
Include only that owner's required re-exports in the public entry; preserve
|
|
29
|
+
unrelated statements. Its real re-export may accompany removal of a redundant
|
|
30
|
+
`export {};` placeholder; do not add empty exports. A `tokens` manifest lists the foundation
|
|
31
|
+
entry in `companionFiles`: when creating/deleting a canonical token file such as
|
|
32
|
+
`fonts.css`, include only its required import change there in the same patch.
|
|
33
|
+
Preserve other imports/comments and the canonical order. Use `dryRun: true` to
|
|
34
|
+
inspect validation; applying re-runs the gates. Fix rejected patches in their
|
|
35
|
+
owner. Report unsupported coupled changes instead of bypassing a rejection with
|
|
36
|
+
direct source writes. Shared styles/helpers use `foundation`, root guides use
|
|
31
37
|
`documentation`, and branding/fonts use their discovered `assets/*` unit.
|
|
32
38
|
Intentional configuration, rule exceptions, registered guard code and compiler
|
|
33
39
|
changes use `configuration`; validate the proposed settings and keep mandatory
|
|
@@ -161,15 +167,25 @@ entry independently and verify real behavior at narrow/wide sizes in both themes
|
|
|
161
167
|
The older `showcase-coverage` policy separately checks application references.
|
|
162
168
|
|
|
163
169
|
Token previews use that same scenario pipeline. Colocate a token scenario and
|
|
164
|
-
fixture under the configured token directory.
|
|
165
|
-
`{ title:
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
170
|
+
fixture under the configured token directory. Declare metadata
|
|
171
|
+
`{ title: fixture.title, covers: ["TokenSamples"], samples: fixture.samples }`.
|
|
172
|
+
The renderer name is project-owned; use its real public API. Each literal fixture
|
|
173
|
+
sample declares `{ token, property, probe }`: a real token, the CSS property its
|
|
174
|
+
sample demonstrates, and a valid test value different from that token's current
|
|
175
|
+
value. Pass `samples` into the rendered public review pattern. Names, sample text,
|
|
176
|
+
bindings and probes are fixture data; displayed values come from foundation CSS.
|
|
177
|
+
There is no property catalogue, token-name mapping or probe palette in ADS.
|
|
178
|
+
Every token needs sample coverage; fabricated tokens, missing bindings/probes,
|
|
179
|
+
unused fixtures and value-only lists fail the static gates. `covers: []` is invalid.
|
|
180
|
+
During App rendering, the browser checks sample markers `data-ads-token` and
|
|
181
|
+
`data-ads-property`, validates CSS with its own `CSS.supports`, and temporarily
|
|
182
|
+
probes each token to verify that the declared sample property responds. It restores
|
|
183
|
+
values immediately. Missing, hidden, invalid or hardcoded samples produce a preview
|
|
184
|
+
error instead of a successful review. These browser checks apply to the mounted
|
|
185
|
+
scenario/theme, not every application state or pixel-level design quality.
|
|
186
|
+
Keep styles in the project renderer; do not add inline-style exemptions to scenarios.
|
|
187
|
+
Fix `token-scenario-reference`, `token-scenario-render` and `token-scenario-coverage`
|
|
188
|
+
findings. Metadata and unused fixture fields do not count. Discover exact token IDs with `ads_scenarios({kind:"tokens"})`; mount them
|
|
173
189
|
with `ads_show_scenario({id})` or `ads_show_tokens({category:id})`. With the shared
|
|
174
190
|
resource compiler, omitting category opens the first project token scenario.
|
|
175
191
|
Legacy compilers retain the generic definition inspector. Use `ads_tokens` for
|
|
@@ -187,6 +203,25 @@ Use local simulated services in fixtures. Tests may import fixtures; load real
|
|
|
187
203
|
foundation CSS rather than assigning shared token values in JavaScript. Keep
|
|
188
204
|
full-app integration harnesses and workflow coverage in application test support.
|
|
189
205
|
|
|
206
|
+
Foundation CSS is checked, not an escape hatch. Value sheets define shared tokens
|
|
207
|
+
on the document root/themes; `fonts.css` holds font-face metadata. `base.css` has
|
|
208
|
+
three closed roles: root/body typography, canvas and theme defaults; limited native
|
|
209
|
+
normalization; and global focus indicators/reduced-motion rules. Every selector
|
|
210
|
+
branch, property and value must fit its role. Token-backed control skins, layout,
|
|
211
|
+
interactive state rules and owner hooks are rejected even through `:where()`.
|
|
212
|
+
Do not copy an unchecked upstream preflight or turn a component repair into a reset.
|
|
213
|
+
Put skin/state/animation in the Linaria owner, and scenario layout variations in
|
|
214
|
+
the public review pattern's typed API. Browser review still verifies the effects.
|
|
215
|
+
|
|
216
|
+
Invalid metadata and source failures keep the managed compiler alive for repairs.
|
|
217
|
+
Read `ads_status.build` or `ads://project/build`, fix the owner, then wait for current
|
|
218
|
+
successful output. If the builder exited, fix its command and invoke the preview
|
|
219
|
+
again; ADS starts one fresh child without an MCP reconnect. Compiler-code changes
|
|
220
|
+
also reload on the next preview invocation. Reconnect the client when upgrading
|
|
221
|
+
the ADS server package itself. Persistent SSE/notification streams make
|
|
222
|
+
`networkidle` unsuitable: wait for the App's current ready/error state instead.
|
|
223
|
+
|
|
224
|
+
|
|
190
225
|
|
|
191
226
|
|
|
192
227
|
Register component/project Oxlint plugins and rules in `ds.config.json`. Use
|
|
@@ -22,6 +22,10 @@ Select checks that match the item:
|
|
|
22
22
|
touch/scroll interaction, and reduced motion for animated controls.
|
|
23
23
|
|
|
24
24
|
Use an isolated browser context. Preserve the user's open pages and settings.
|
|
25
|
+
The showcase keeps a notification stream open. Wait for document loading and the
|
|
26
|
+
selected App's ready/error state; `networkidle` is not a readiness condition.
|
|
27
|
+
For tall previews, scroll the App document to its last example and verify it is
|
|
28
|
+
reachable without the host shell scrolling or clipping the preview.
|
|
25
29
|
Capture the fixture, viewport/container size, theme, zoom, and actions alongside
|
|
26
30
|
each failure. CSS viewport emulation is useful; it is not evidence of real
|
|
27
31
|
mobile keyboard, browser chrome, or assistive-technology behavior.
|