@reqvire-org/ads 0.3.17 → 0.3.20
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/dist/atomic-composition.mjs +1 -1
- package/dist/behaviors.mjs +5 -5
- package/dist/bootstrap-ds/ADS.md +2 -2
- package/dist/bootstrap-ds/README.md +1 -1
- package/dist/bootstrap-ds/design-system/index.ts +0 -1
- package/dist/bootstrap-ds/design-system/review/index.ts +1 -0
- package/dist/bootstrap-ds/design-system/tokens/Foundation.scenario.tsx +1 -1
- package/dist/bootstrap-ds/ds.config.json +1 -1
- package/dist/bootstrap-ds/tsconfig.json +18 -3
- package/dist/bootstrap-ds/vite.config.ts +1 -1
- package/dist/boundaries.mjs +4 -2
- package/dist/check.mjs +49 -15
- package/dist/config.mjs +13 -2
- package/dist/init.mjs +17 -14
- package/dist/investigations.mjs +34 -3
- package/dist/layers.mjs +288 -0
- package/dist/linaria/preserve-values.cjs +21 -2
- package/dist/lint-config.mjs +1 -1
- package/dist/mcp/behavior-pages.mjs +36 -0
- package/dist/mcp/context.mjs +5 -2
- package/dist/mcp/edit-preparation.mjs +3 -3
- package/dist/mcp/execution-results.mjs +146 -0
- package/dist/mcp/mutations.mjs +4 -4
- package/dist/mcp/prompts.mjs +1 -1
- package/dist/mcp/result-pages.mjs +87 -0
- package/dist/mcp/server.mjs +49 -30
- package/dist/mcp/widget.bundle.js +1 -1
- package/dist/oxlint.mjs +3 -1
- package/dist/preview-resources.mjs +11 -1
- package/dist/property-registrations.mjs +39 -0
- package/dist/public-api.mjs +13 -6
- package/dist/scenario-trace.mjs +7 -7
- package/dist/scenarios-vite.mjs +2 -1
- package/dist/scenarios.mjs +10 -7
- package/dist/scoped-tokens.mjs +6 -0
- package/dist/showcase/client.bundle.js +1 -1
- package/dist/showcase/sandbox.bundle.js +1 -1
- package/dist/skills/ads-break-ui/SKILL.md +27 -10
- package/dist/skills/ads-break-ui/references/atomic-styles.md +48 -0
- package/dist/skills/ads-break-ui/references/captures.md +6 -1
- package/dist/skills/ads-break-ui/references/evidence.md +52 -5
- package/dist/skills/ads-break-ui/references/guarded-editing.md +9 -6
- package/dist/skills/ads-break-ui/references/layers.md +123 -0
- package/dist/skills/ads-break-ui/references/review.md +96 -0
- package/dist/skills/ads-break-ui/references/scenarios.md +24 -3
- package/dist/skills/ads-designer/SKILL.md +28 -11
- package/dist/skills/ads-designer/assets/bootstrap-ds/ADS.md +2 -2
- package/dist/skills/ads-designer/assets/bootstrap-ds/README.md +1 -1
- package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/index.ts +0 -1
- package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/review/index.ts +1 -0
- package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/tokens/Foundation.scenario.tsx +1 -1
- package/dist/skills/ads-designer/assets/bootstrap-ds/ds.config.json +1 -1
- package/dist/skills/ads-designer/assets/bootstrap-ds/tsconfig.json +18 -3
- package/dist/skills/ads-designer/assets/bootstrap-ds/vite.config.ts +1 -1
- package/dist/skills/ads-designer/references/atomic-styles.md +48 -0
- package/dist/skills/ads-designer/references/captures.md +6 -1
- package/dist/skills/ads-designer/references/evidence.md +52 -5
- package/dist/skills/ads-designer/references/guarded-editing.md +9 -6
- package/dist/skills/ads-designer/references/layers.md +123 -0
- package/dist/skills/ads-designer/references/review.md +96 -0
- package/dist/skills/ads-designer/references/scenarios.md +24 -3
- package/dist/skills/ads-import/SKILL.md +27 -10
- package/dist/skills/ads-import/references/atomic-styles.md +48 -0
- package/dist/skills/ads-import/references/captures.md +6 -1
- package/dist/skills/ads-import/references/evidence.md +52 -5
- package/dist/skills/ads-import/references/guarded-editing.md +9 -6
- package/dist/skills/ads-import/references/layers.md +123 -0
- package/dist/skills/ads-import/references/review.md +96 -0
- package/dist/skills/ads-import/references/scenarios.md +24 -3
- package/dist/skills/ads-migrate/SKILL.md +27 -10
- package/dist/skills/ads-migrate/references/atomic-styles.md +48 -0
- package/dist/skills/ads-migrate/references/captures.md +6 -1
- package/dist/skills/ads-migrate/references/evidence.md +52 -5
- package/dist/skills/ads-migrate/references/guarded-editing.md +9 -6
- package/dist/skills/ads-migrate/references/layers.md +123 -0
- package/dist/skills/ads-migrate/references/review.md +96 -0
- package/dist/skills/ads-migrate/references/scenarios.md +24 -3
- package/dist/skills/ads-motion/SKILL.md +28 -11
- package/dist/skills/ads-motion/references/atomic-styles.md +48 -0
- package/dist/skills/ads-motion/references/audit.md +1 -1
- package/dist/skills/ads-motion/references/captures.md +6 -1
- package/dist/skills/ads-motion/references/evidence.md +52 -5
- package/dist/skills/ads-motion/references/guarded-editing.md +9 -6
- package/dist/skills/ads-motion/references/layers.md +123 -0
- package/dist/skills/ads-motion/references/motion-review.md +35 -0
- package/dist/skills/ads-motion/references/review.md +96 -35
- package/dist/skills/ads-motion/references/scenarios.md +24 -3
- package/dist/units.mjs +17 -14
- package/docs/ADS.md +42 -5
- package/docs/ARCHITECTURE.md +64 -4
- package/docs/BEHAVIORS.md +21 -2
- package/docs/CHANGELOG.md +40 -0
- package/docs/GUIDE.md +3 -2
- package/docs/MCP.md +87 -20
- package/package.json +2 -1
- package/dist/bootstrap-ds/design-system/patterns/token-preview/Scenario.tsx +0 -4
- package/dist/bootstrap-ds/design-system/patterns/token-preview/fixture.ts +0 -8
- package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/patterns/token-preview/Scenario.tsx +0 -4
- package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/patterns/token-preview/fixture.ts +0 -8
- /package/dist/bootstrap-ds/design-system/{patterns → review}/token-preview/TokenPreview.tsx +0 -0
- /package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/{patterns → review}/token-preview/TokenPreview.tsx +0 -0
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { ident, lexer, parse, string, walk, generate } from "css-tree";
|
|
2
|
+
|
|
3
|
+
// CSS Properties and Values API Level 1 syntax types, not project token roles.
|
|
4
|
+
const types=new Set(["angle","color","custom-ident","image","integer","length","length-percentage","number","percentage","resolution","string","time","transform-function","transform-list","url"]);
|
|
5
|
+
const relative=/^(?:r?em|r?ex|r?cap|r?ch|r?ic|r?lh|[sld]?v(?:w|h|i|b|min|max)|cq(?:w|h|i|b|min|max))$/i;
|
|
6
|
+
const descriptors=new Set(["syntax","inherits","initial-value"]);
|
|
7
|
+
|
|
8
|
+
/** Registration metadata has its own CSS grammar; it is not an element style. */
|
|
9
|
+
export function propertyRegistration(rule) {
|
|
10
|
+
const name=ident.decode(rule.params.trim());
|
|
11
|
+
if(!name.startsWith("--")||name==="--"||lexer.matchType("custom-property-name",rule.params).error)throw new Error("@property needs one literal custom-property name.");
|
|
12
|
+
for(let parent=rule.parent;parent&&parent.type!=="root";parent=parent.parent)if(parent.type!=="atrule"||["property","font-face"].includes(parent.name.toLowerCase())||/keyframes$/i.test(parent.name))throw new Error("@property is document-global. Place it at template/sheet level or inside conditional at-rules, not element selectors or another descriptor block.");
|
|
13
|
+
const values={};
|
|
14
|
+
for(const node of rule.nodes??[]){
|
|
15
|
+
if(node.type==="comment")continue;
|
|
16
|
+
if(node.type!=="decl"||!descriptors.has(node.prop.toLowerCase())||node.important||Object.hasOwn(values,node.prop.toLowerCase()))throw new Error("@property accepts only one syntax, inherits and initial-value descriptor; no styles, nested rules or !important.");
|
|
17
|
+
values[node.prop.toLowerCase()]=node.value;
|
|
18
|
+
}
|
|
19
|
+
const syntaxAst=parse(values.syntax??"",{context:"value"}),syntaxNode=syntaxAst.children.first;
|
|
20
|
+
if(syntaxAst.children.size!==1||syntaxNode?.type!=="String")throw new Error("@property syntax must be one CSS string.");
|
|
21
|
+
const syntax=string.decode(generate(syntaxNode)).trim();
|
|
22
|
+
if(syntax!=="*"&&(!syntax||!syntax.split("|").every(part=>{
|
|
23
|
+
part=part.trim();const multiplier=/[+#]$/.test(part)?part.at(-1):"",base=multiplier?part.slice(0,-1).trim():part,match=/^<([a-z-]+)>$/.exec(base);
|
|
24
|
+
return match?types.has(match[1])&&!(match[1]==="transform-list"&&multiplier):!!base&&!lexer.matchType("custom-ident",base).error;
|
|
25
|
+
})))throw new Error("Invalid @property syntax. Use the CSS Properties and Values API grammar, not a project-specific type.");
|
|
26
|
+
if(!/^(?:true|false)$/i.test(values.inherits??""))throw new Error("@property requires inherits: true or false.");
|
|
27
|
+
if(values["initial-value"]===undefined&&syntax!=="*")throw new Error("Typed @property registrations require initial-value.");
|
|
28
|
+
if(values["initial-value"]!==undefined){
|
|
29
|
+
const initial=parse(values["initial-value"],{context:"value"});
|
|
30
|
+
if(syntax!=="*"){
|
|
31
|
+
walk(initial,node=>{
|
|
32
|
+
if(node.type==="Function"&&/^(?:var|env|attr)$/i.test(node.name)||node.type==="Dimension"&&relative.test(node.unit)||node.type==="Identifier"&&/^(?:currentcolor|inherit|initial|unset|revert(?:-layer)?)$/i.test(node.name))throw new Error("Typed @property initial-value must be computationally independent: no var(), relative units or currentColor. Keep design assignments token-backed outside this descriptor.");
|
|
33
|
+
});
|
|
34
|
+
const match=lexer.match(syntax,initial);
|
|
35
|
+
if(match.error)throw new Error("@property initial-value must match its declared syntax.");
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
return {name,signature:JSON.stringify([syntax,values.inherits.toLowerCase(),values["initial-value"]===undefined?null:generate(parse(values["initial-value"],{context:"value"}))])};
|
|
39
|
+
}
|
package/dist/public-api.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import ts from "typescript";
|
|
3
|
-
import { isDevelopmentFile, isInside } from "./config.mjs";
|
|
3
|
+
import { isDevelopmentFile, isInside, isReviewFile } from "./config.mjs";
|
|
4
4
|
import { uiOperation } from "./ui-framework.mjs";
|
|
5
5
|
import { projectCompilerOptions } from "./boundaries.mjs";
|
|
6
6
|
|
|
@@ -35,9 +35,16 @@ function resolvedCallable(program, options, name) {
|
|
|
35
35
|
return resolvedCallables.get(program).get(name) ?? false;
|
|
36
36
|
}
|
|
37
37
|
|
|
38
|
-
export function publicApi(program, options) {
|
|
38
|
+
export function publicApi(program, options) { return entryApi(program, options, options.config.publicEntry, false); }
|
|
39
|
+
|
|
40
|
+
/** A separate development API: never part of the product catalogue or exports. */
|
|
41
|
+
export function reviewApi(program, options) { return entryApi(program, options, options.config.reviewEntry, true); }
|
|
42
|
+
|
|
43
|
+
export function scenarioApi(program, options) { return [...publicApi(program, options), ...reviewApi(program, options)]; }
|
|
44
|
+
|
|
45
|
+
function entryApi(program, options, entryFile, review) {
|
|
39
46
|
const checker = program.getTypeChecker();
|
|
40
|
-
const entry = program.getSourceFile(path.join(options.dsRoot,
|
|
47
|
+
const entry = program.getSourceFile(path.join(options.dsRoot, entryFile));
|
|
41
48
|
const module = entry && checker.getSymbolAtLocation(entry);
|
|
42
49
|
const items = [];
|
|
43
50
|
for (const exported of module ? checker.getExportsOfModule(module) : []) {
|
|
@@ -46,8 +53,8 @@ export function publicApi(program, options) {
|
|
|
46
53
|
const declaration = symbol.valueDeclaration ?? symbol.declarations?.[0];
|
|
47
54
|
if (!declaration || !(symbol.flags & ts.SymbolFlags.Value)) continue;
|
|
48
55
|
const file = declaration.getSourceFile().fileName;
|
|
49
|
-
if (isDevelopmentFile(file, options)) continue;
|
|
50
|
-
const kind = isInside(path.join(options.dsRoot, "components"), file) ? "components" : isInside(path.join(options.dsRoot, "patterns"), file) ? "patterns" : isInside(path.join(options.dsRoot, "controllers"), file) ? "controllers" : null;
|
|
56
|
+
if (review ? !isReviewFile(file, options) : isDevelopmentFile(file, options)) continue;
|
|
57
|
+
const kind = review ? "review" : isInside(path.join(options.dsRoot, "components"), file) ? "components" : isInside(path.join(options.dsRoot, "patterns"), file) ? "patterns" : isInside(path.join(options.dsRoot, "controllers"), file) ? "controllers" : null;
|
|
51
58
|
if (!kind) continue;
|
|
52
59
|
let renderable = false;
|
|
53
60
|
function visit(node) {
|
|
@@ -68,7 +75,7 @@ export function publicApi(program, options) {
|
|
|
68
75
|
const behavioral = !!(type.getCallSignatures().length || type.getConstructSignatures().length
|
|
69
76
|
|| !renderable && !!(type.flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown))
|
|
70
77
|
&& (program.getCompilerOptions().noLib || program.getCompilerOptions().noResolve)
|
|
71
|
-
&& resolvedCallable(program, options, exported.name));
|
|
78
|
+
&& !review && resolvedCallable(program, options, exported.name));
|
|
72
79
|
items.push({ name: exported.name, kind, file, declaration, renderable: kind !== "controllers" && renderable, behavioral });
|
|
73
80
|
}
|
|
74
81
|
return items;
|
package/dist/scenario-trace.mjs
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import ts from "typescript";
|
|
3
3
|
import postcss from "postcss";
|
|
4
|
-
import { isDevelopmentFile, isInside } from "./config.mjs";
|
|
5
|
-
import {
|
|
4
|
+
import { isDevelopmentFile, isInside, isReviewFile } from "./config.mjs";
|
|
5
|
+
import { scenarioApi } from "./public-api.mjs";
|
|
6
6
|
import { uiOperation } from "./ui-framework.mjs";
|
|
7
7
|
import { inspectUnits } from "./units.mjs";
|
|
8
8
|
import { createSourceValues, reference, union, isFunction } from "./source-values.mjs";
|
|
@@ -29,10 +29,10 @@ export function createScenarioTrace(program, options) {
|
|
|
29
29
|
const publicNodes = new Map(), helpers = new Map(), inventory = inspectUnits(options);
|
|
30
30
|
const locationKey = value => JSON.stringify([value.file, value.line, value.column]);
|
|
31
31
|
function runtimeOwner(file) {
|
|
32
|
-
return ["components", "patterns"].some(folder => isInside(path.join(options.dsRoot, folder), file)) && !isDevelopmentFile(file, options)
|
|
32
|
+
return (isReviewFile(file, options) || ["components", "patterns"].some(folder => isInside(path.join(options.dsRoot, folder), file)) && !isDevelopmentFile(file, options))
|
|
33
33
|
? inventory.owner(inventory.relative(path.relative(options.root, file))) : null;
|
|
34
34
|
}
|
|
35
|
-
for (const item of
|
|
35
|
+
for (const item of scenarioApi(program, options)) {
|
|
36
36
|
const value = resolve(reference(item.declaration.name ?? item.declaration), state());
|
|
37
37
|
if (value.kind !== "node") continue;
|
|
38
38
|
if (item.renderable) {
|
|
@@ -85,7 +85,7 @@ export function createScenarioTrace(program, options) {
|
|
|
85
85
|
if (target.kind === "union") return target.values.length <= maxBranches ? common(target.values.map(component)) : new Set();
|
|
86
86
|
if (target.kind !== "node") return new Set();
|
|
87
87
|
const names = new Set(publicNodes.get(target.node) ?? []);
|
|
88
|
-
if (isFunction(target.node) && !isDevelopmentFile(target.node.getSourceFile().fileName, options)) {
|
|
88
|
+
if (isFunction(target.node) && (!isDevelopmentFile(target.node.getSourceFile().fileName, options) || isReviewFile(target.node.getSourceFile().fileName, options))) {
|
|
89
89
|
const outputs = returns(target, [view.attributes], next(context), "component");
|
|
90
90
|
for (const name of render(union(outputs), next(context))) names.add(name);
|
|
91
91
|
}
|
|
@@ -120,9 +120,9 @@ export function createScenarioTrace(program, options) {
|
|
|
120
120
|
let declarations = 0;
|
|
121
121
|
const template = node.template;
|
|
122
122
|
const text = ts.isNoSubstitutionTemplateLiteral(template) ? template.text : template.head.text + template.templateSpans.map(span => `__ads_expression__${span.literal.text}`).join("");
|
|
123
|
-
try { postcss.parse(text).walkDecls(()
|
|
123
|
+
try { postcss.parse(text).walkDecls(declaration => { for(let parent=declaration.parent;parent;parent=parent.parent)if(parent.type==="atrule"&&/^(?:property|font-face|(?:-\w+-)?keyframes)$/i.test(parent.name))return; ++declarations; }); } catch { /* Source CSS guards own malformed templates. */ }
|
|
124
124
|
if (owned(source, via) && declarations) return result(true, true);
|
|
125
|
-
reasons.add("The returned style must originate in
|
|
125
|
+
reasons.add("The returned style must originate in component/pattern or review Linaria through its registered callable API.");
|
|
126
126
|
return result(false);
|
|
127
127
|
}
|
|
128
128
|
if (ts.isCallExpression(node) && isCx(node.expression)) {
|
package/dist/scenarios-vite.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
import { mkdirSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { loadConfig, isInside } from "./config.mjs";
|
|
4
4
|
import { inspectScenarios } from "./scenarios.mjs";
|
|
5
|
+
import { configureReviewAlias } from "./preview-resources.mjs";
|
|
5
6
|
export { createPreviewResources, previewEntriesPlugin } from "./preview-resources.mjs";
|
|
6
7
|
|
|
7
8
|
/** Project-owned rendering; this plugin supplies only discoverable module entries. */
|
|
@@ -16,7 +17,7 @@ export function scenarioRegistryPlugin(directory = process.cwd()) {
|
|
|
16
17
|
writeFileSync(path.join(options.root, ".ds/scenarios/manifest.json"), JSON.stringify({ schemaVersion: 1, scenarios: value.scenarios }, null, 2) + "\n");
|
|
17
18
|
return `export const scenarios = [${value.scenarios.map((item) => `{...${JSON.stringify(item)}, load: () => import(${JSON.stringify(path.join(options.root, item.file).replaceAll("\\", "/"))})}`).join(",\n")}];`;
|
|
18
19
|
}
|
|
19
|
-
return { name: "ads-scenario-registry", resolveId(id) { if (id === virtual) return resolved; },
|
|
20
|
+
return { name: "ads-scenario-registry", config(config) { configureReviewAlias(config, options); }, resolveId(id) { if (id === virtual) return resolved; },
|
|
20
21
|
load(id) { if (id === resolved) return registry(); },
|
|
21
22
|
handleHotUpdate({ file, server }) {
|
|
22
23
|
if (!isInside(options.dsRoot, file)) return;
|
package/dist/scenarios.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
2
|
import { existsSync } from "node:fs";
|
|
3
3
|
import ts from "typescript";
|
|
4
|
-
import { publicApi } from "./public-api.mjs";
|
|
4
|
+
import { publicApi, scenarioApi } from "./public-api.mjs";
|
|
5
5
|
import { uiOperation } from "./ui-framework.mjs";
|
|
6
6
|
import { isDevelopmentFile, isInside, isScenarioFile, sourceFiles } from "./config.mjs";
|
|
7
7
|
import { projectCompilerOptions } from "./boundaries.mjs";
|
|
@@ -37,8 +37,10 @@ export function publicUi(program, options) {
|
|
|
37
37
|
export function inspectScenarios(options, suppliedProgram) {
|
|
38
38
|
const { root, dsRoot } = options;
|
|
39
39
|
const files = sourceFiles(dsRoot).filter(isScenarioFile);
|
|
40
|
-
const program = suppliedProgram ?? ts.createProgram([path.join(dsRoot, options.config.publicEntry), ...files], { ...projectCompilerOptions(options), allowJs: true });
|
|
41
|
-
const
|
|
40
|
+
const program = suppliedProgram ?? ts.createProgram([path.join(dsRoot, options.config.publicEntry), path.join(dsRoot, options.config.reviewEntry), ...files], { ...projectCompilerOptions(options), allowJs: true });
|
|
41
|
+
const apis = scenarioApi(program, options);
|
|
42
|
+
const duplicate = apis.find((item, index) => apis.some((other, otherIndex) => index !== otherIndex && item.name === other.name));
|
|
43
|
+
const known = new Map(apis.filter(item => item.kind !== "controllers").map((item) => [item.name, item]));
|
|
42
44
|
const checker = program.getTypeChecker();
|
|
43
45
|
const behaviorApis = new Map(publicApi(program, options).filter(item => !item.renderable && item.behavioral).map(item => [item.name, item]));
|
|
44
46
|
const tokenInventory = inspectTokens(root);
|
|
@@ -48,6 +50,7 @@ export function inspectScenarios(options, suppliedProgram) {
|
|
|
48
50
|
const diagnostics = [];
|
|
49
51
|
const ids = new Set();
|
|
50
52
|
function report(file, message) { diagnostics.push({ file: path.relative(root, file).replaceAll("\\", "/"), rule: "scenario-contract", line: 1, column: 1, message }); }
|
|
53
|
+
if (duplicate) report(path.join(dsRoot, options.config.reviewEntry), `Review export ${duplicate.name} collides with the production API. Use a distinct development export so scenario coverage is unambiguous.`);
|
|
51
54
|
for (const file of files) {
|
|
52
55
|
const relative = path.relative(dsRoot, file).replaceAll("\\", "/");
|
|
53
56
|
let kind = isInside(path.join(dsRoot, options.config.tokenDir), file) ? "tokens" : relative.split("/")[0];
|
|
@@ -89,9 +92,9 @@ export function inspectScenarios(options, suppliedProgram) {
|
|
|
89
92
|
if (!fixture) 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.");
|
|
90
93
|
if (!metadata || Object.keys(metadata).some((key) => !["title", "kind", "covers", "exercises", "tokens", "samples", "captureTargets"].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)
|
|
91
94
|
|| Object.hasOwn(metadata, "tokens") && (!Array.isArray(metadata.tokens) || metadata.tokens.some(name => typeof name !== "string" || !name.startsWith("--")))
|
|
92
|
-
|| kind === "tokens" && (Object.hasOwn(metadata, "tokens") || !validTokenSamples(metadata.samples) || !metadata.covers.some(name => known.get(name)?.kind
|
|
95
|
+
|| kind === "tokens" && (Object.hasOwn(metadata, "tokens") || !validTokenSamples(metadata.samples) || !metadata.covers.some(name => ["patterns", "review"].includes(known.get(name)?.kind)))
|
|
93
96
|
|| Object.hasOwn(metadata, "samples") && !validTokenSamples(metadata.samples)) {
|
|
94
|
-
report(file, "Export static metadata with nonempty covers naming rendered public UI. Optional exercises names public behavioral APIs, not rendered UI. Token scenarios must render a project-owned
|
|
97
|
+
report(file, "Export static metadata with nonempty covers naming rendered public UI. Optional exercises names public behavioral APIs, not rendered UI. Token scenarios must render a project-owned review renderer (from the separate review entry, or a real product pattern) and declare samples: fixture.samples, a literal list of { token, property, probe } CSS samples. A plain value list or covers: [] is incomplete."); continue;
|
|
95
98
|
}
|
|
96
99
|
// A bad behavioral entry must fail validation without erasing independent
|
|
97
100
|
// render evidence and manufacturing missing-coverage errors for real UI.
|
|
@@ -156,7 +159,7 @@ export function inspectScenarioCoverage(options, program, report, advise = () =>
|
|
|
156
159
|
return symbol.declarations?.length ? symbol : null;
|
|
157
160
|
}
|
|
158
161
|
const identities = new Map(), behaviorIdentities = new Set();
|
|
159
|
-
for (const item of
|
|
162
|
+
for (const item of scenarioApi(program, options)) {
|
|
160
163
|
if (!identities.has(item.declaration)) identities.set(item.declaration, new Set());
|
|
161
164
|
identities.get(item.declaration).add(item.name);
|
|
162
165
|
if (item.behavioral && !item.renderable) behaviorIdentities.add(item.declaration);
|
|
@@ -235,7 +238,7 @@ export function inspectScenarioCoverage(options, program, report, advise = () =>
|
|
|
235
238
|
else if (!exercised.has(name)) advise(source.fileName, "scenario-exercise-review", `${name} is referenced dynamically. Verify its invocation in the registered browser/behavior checks; a reference alone does not prove execution.`);
|
|
236
239
|
}
|
|
237
240
|
for (const name of referencedTokens) if (!declaredTokens.has(name)) report(source.fileName, "token-scenario-reference", `Reachable scenario input references undeclared token ${name}.`);
|
|
238
|
-
if (scenario.kind === "tokens" && !samplesPassed) report(source.fileName, "token-scenario-render", "Pass the declared fixture.samples into a rendered
|
|
241
|
+
if (scenario.kind === "tokens" && !samplesPassed) report(source.fileName, "token-scenario-render", "Pass the declared fixture.samples into a rendered review renderer's samples prop. Printing token names or reading unused fixtures is not sample coverage.");
|
|
239
242
|
for (const name of scenario.tokens) {
|
|
240
243
|
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.`);
|
|
241
244
|
else if (scenario.kind === "tokens" && samplesPassed && declaredTokens.has(name)) coveredTokens.add(name);
|
package/dist/scoped-tokens.mjs
CHANGED
|
@@ -6,6 +6,7 @@ import { inspectUnits } from "./units.mjs";
|
|
|
6
6
|
import { isDevelopmentFile } from "./config.mjs";
|
|
7
7
|
import { foundationSelector } from "./foundation.mjs";
|
|
8
8
|
import { isSharedToken, tokenContract } from "./token-policy.mjs";
|
|
9
|
+
import { propertyRegistration } from "./property-registrations.mjs";
|
|
9
10
|
|
|
10
11
|
/** An explicit owner contract, never a heuristic based on a token's name. */
|
|
11
12
|
export function scopedTokens(options) {
|
|
@@ -30,8 +31,13 @@ export function scopedTokens(options) {
|
|
|
30
31
|
if (realpathSync(file) !== file) throw new Error("Scoped token stylesheets cannot resolve through symlinks.");
|
|
31
32
|
const css = postcss.parse(readFileSync(file, "utf8"), {from:file});
|
|
32
33
|
let count = 0;
|
|
34
|
+
css.walkAtRules(/^property$/i,rule=>{
|
|
35
|
+
try {const {name}=propertyRegistration(rule);let defined=false;css.walkDecls(name,()=>{defined=true;});if(!name.startsWith(data.namespace)||!defined)throw new Error("Register only a real token defined in this owner's namespace and sheet.");}
|
|
36
|
+
catch(error){report(stylesheet,error.message,rule.source?.start.line);}
|
|
37
|
+
});
|
|
33
38
|
css.walk(node => {
|
|
34
39
|
if (node.type === "comment" || node.type === "root") return;
|
|
40
|
+
if(node.type==="atrule"&&node.name.toLowerCase()==="property"||node.type==="decl"&&node.parent.type==="atrule"&&node.parent.name.toLowerCase()==="property")return; // Separately validated registration metadata.
|
|
35
41
|
if (node.type === "atrule" && ["media", "supports", "layer"].includes(node.name) && node.nodes) return;
|
|
36
42
|
if (node.type === "rule" && node.parent.type !== "rule" && !foundationSelector(node.selector, {definitions:true})) return;
|
|
37
43
|
if (node.type === "decl" && node.parent.type === "rule" && node.prop.startsWith(data.namespace) && node.prop.length > data.namespace.length && !node.important) { count++; return; }
|
|
@@ -113,7 +113,7 @@ Boolean requesting whether a visible border and background is provided by the ho
|
|
|
113
113
|
- "model": Tool visible to and callable by the agent
|
|
114
114
|
- "app": Tool callable by the app from this server only`),csp:p.never().optional(),permissions:p.never().optional()}),$k=p.object({mimeTypes:p.array(p.string()).optional().describe('Array of supported MIME types for UI resources.\nMust include `"text/html;profile=mcp-app"` for MCP Apps support.')}),FP=p.object({method:p.literal("ui/download-file"),params:p.object({contents:p.array(p.union([or,rr])).describe("Resource contents to download \u2014 embedded (inline data) or linked (host fetches). Uses standard MCP resource types.")})}),HP=p.object({method:p.literal("ui/message"),params:p.object({role:p.literal("user").describe('Message role, currently only "user" is supported.'),content:p.array(Nt).describe("Message content blocks (text, image, etc.).")})}),Ck=p.object({method:p.literal("ui/notifications/sandbox-resource-ready"),params:p.object({html:p.string().describe("HTML content to load into the inner iframe."),sandbox:p.string().optional().describe("Optional override for the inner iframe's sandbox attribute."),csp:Tg.optional().describe("CSP configuration from resource metadata."),permissions:Eg.optional().describe("Sandbox permissions from resource metadata.")})}),Ik=p.object({method:p.literal("ui/notifications/tool-result"),params:Po.describe("Standard MCP tool execution result.")}),Ly=p.object({toolInfo:p.object({id:pt.optional().describe("JSON-RPC id of the tools/call request."),tool:xo.describe("Tool definition including name, inputSchema, etc.")}).optional().describe("Metadata of the tool call that instantiated this App."),theme:PP.optional().describe("Current color theme preference."),styles:MP.optional().describe("Style configuration for theming the app."),displayMode:Fa.optional().describe("How the UI is currently displayed."),availableDisplayModes:p.array(Fa).optional().describe("Display modes the host supports."),containerDimensions:p.union([p.object({height:p.number().describe("Fixed container height in pixels.")}),p.object({maxHeight:p.union([p.number(),p.undefined()]).optional().describe("Maximum container height in pixels.")})]).and(p.union([p.object({width:p.number().describe("Fixed container width in pixels.")}),p.object({maxWidth:p.union([p.number(),p.undefined()]).optional().describe("Maximum container width in pixels.")})])).optional().describe(`Container dimensions. Represents the dimensions of the iframe or other
|
|
115
115
|
container holding the app. Specify either width or maxWidth, and either height or maxHeight.`),locale:p.string().optional().describe("User's language and region preference in BCP 47 format."),timeZone:p.string().optional().describe("User's timezone in IANA format."),userAgent:p.string().optional().describe("Host application identifier."),platform:p.union([p.literal("web"),p.literal("desktop"),p.literal("mobile")]).optional().describe("Platform type for responsive design decisions."),deviceCapabilities:p.object({touch:p.boolean().optional().describe("Whether the device supports touch input."),hover:p.boolean().optional().describe("Whether the device supports hover interactions.")}).optional().describe("Device input capabilities."),safeAreaInsets:p.object({top:p.number().describe("Top safe area inset in pixels."),right:p.number().describe("Right safe area inset in pixels."),bottom:p.number().describe("Bottom safe area inset in pixels."),left:p.number().describe("Left safe area inset in pixels.")}).optional().describe("Mobile safe area boundaries in pixels.")}).passthrough(),Ok=p.object({method:p.literal("ui/notifications/host-context-changed"),params:Ly.describe("Partial context update containing only changed fields.")}),VP=p.object({method:p.literal("ui/update-model-context"),params:p.object({content:p.array(Nt).optional().describe("Context content blocks (text, image, etc.)."),structuredContent:p.record(p.string(),p.unknown().describe("Structured content for machine-readable context data.")).optional().describe("Structured content for machine-readable context data.")})}),JP=p.object({method:p.literal("ui/initialize"),params:p.object({appInfo:It.describe("App identification (name and version)."),appCapabilities:UP.describe("Features and capabilities this app provides."),protocolVersion:p.string().describe("Protocol version this app supports.")})}),BP=p.object({protocolVersion:p.string().describe('Negotiated protocol version string (e.g., "2025-11-21").'),hostInfo:It.describe("Host application identification and version."),hostCapabilities:LP.describe("Features and capabilities provided by the host."),hostContext:Ly.describe("Rich context about the host environment.")}).passthrough();var Ou=class{eventTarget;eventSource;messageListener;constructor(t=window.parent,o){this.eventTarget=t,this.eventSource=o,this.messageListener=r=>{if(o&&r.source!==this.eventSource){console.debug("Ignoring message from unknown source",r);return}let n=ht.safeParse(r.data);n.success?(console.debug("Parsed message",n.data),this.onmessage?.(n.data)):r.data?.jsonrpc!=="2.0"?console.debug("Ignoring non-JSON-RPC message",n.error.message,r):(console.error("Failed to parse message",n.error.message,r),this.onerror?.(Error("Invalid JSON-RPC message received: "+n.error.message)))}}async start(){window.addEventListener("message",this.messageListener)}async send(t,o){t.method!==xP&&console.debug("Sending message",t),this.eventTarget.postMessage(t,"*")}async close(){window.removeEventListener("message",this.messageListener),this.onclose?.()}onclose;onerror;onmessage;sessionId;setProtocolVersion};var WP=[jy],Nu=class extends Tu{_client;_hostInfo;_hostCapabilities;_appCapabilities;_hostContext={};_appInfo;_initializedReceived=!1;_registeredEvents=new Set;_events=new xg;_methods=new Pg;setRequestHandler=(...t)=>{let[o,...r]=Ny("request",t)??t;this._methods.claim(o,"setRequestHandler"),super.setRequestHandler.call(this,o,...r)};setNotificationHandler=(...t)=>{let[o,...r]=Ny("notification",t)??t;this._methods.claim(o,"setNotificationHandler"),super.setNotificationHandler.call(this,o,...r)};removeRequestHandler=t=>{this._methods.release(t),super.removeRequestHandler(t)};removeNotificationHandler=t=>{this._methods.release(t),super.removeNotificationHandler(t)};replaceRequestHandler=(t,...o)=>{this._methods.replace(t),super.setRequestHandler.call(this,t,...o)};_wrapHandler(t,o){let r=super._wrapHandler(t,o);return t==="ui/initialize"||t==="ping"?r:async(n,s)=>(this._initializedReceived||console.warn(`[ext-apps] AppBridge received '${t}' before ui/notifications/initialized. The View is calling host methods before completing the handshake; it should await app.connect() first.`),r(n,s))}eventSchemas={sizechange:NP,sandboxready:OP,initialized:ZP,requestteardown:jP,loggingmessage:nr};setEventHandler(t,o){this._ensureEventSlot(t),this._events.setHandler(t,o)}getEventHandler(t){return this._events.getHandler(t)}addEventListener(t,o){this._ensureEventSlot(t),this._events.addEventListener(t,o)}removeEventListener(t,o){this._events.removeEventListener(t,o)}_ensureEventSlot(t){if(this._registeredEvents.has(t))return;let o=this.eventSchemas[t];if(!o)throw Error(`Unknown event: ${String(t)}`);let r=n=>{t==="initialized"&&(this._initializedReceived=!0),this._events.dispatch(t,n)};t==="loggingmessage"?this.setNotificationHandler("notifications/message",n=>{r(n.params)}):this.setNotificationHandler(o.shape.method.value,{params:o.shape.params},n=>r(n)),this._registeredEvents.add(t)}warnIfRequestHandlerReplaced(t,o,r){o&&r&&console.warn(`[MCP Apps] ${t} handler replaced. Previous handler will no longer be called.`)}constructor(t,o,r,n){super(n),this._client=t,this._hostInfo=o,this._hostCapabilities=r,this._methods.replace("notifications/cancelled"),this._methods.replace("notifications/progress"),this._ensureEventSlot("initialized"),this._hostContext=n?.hostContext||{},this.replaceRequestHandler("ui/initialize",{params:JP.shape.params,result:BP},s=>this._onAppsInitialize(s)),this.replaceRequestHandler("ping",(s,i)=>(this.onping?.(s.params,i),{})),this.replaceRequestHandler("ui/request-display-mode",{params:My.shape.params,result:Ay},()=>({mode:this._hostContext.displayMode??"inline"}))}buildContext(t){return t}assertCapabilityForMethod(t){}assertRequestHandlerCapability(t){}assertNotificationCapability(t){}getAppCapabilities(){return this._appCapabilities}getAppVersion(){return this._appInfo}onping;get onsizechange(){return this.getEventHandler("sizechange")}set onsizechange(t){this.setEventHandler("sizechange",t)}get onsandboxready(){return this.getEventHandler("sandboxready")}set onsandboxready(t){this.setEventHandler("sandboxready",t)}get oninitialized(){return this.getEventHandler("initialized")}set oninitialized(t){this.setEventHandler("initialized",t)}_onmessage;get onmessage(){return this._onmessage}set onmessage(t){this.warnIfRequestHandlerReplaced("onmessage",this._onmessage,t),this._onmessage=t,this.replaceRequestHandler("ui/message",{params:HP.shape.params,result:IP},async(o,r)=>{if(!this._onmessage)throw Error("No onmessage handler set");return this._onmessage(o,r)})}_onopenlink;get onopenlink(){return this._onopenlink}set onopenlink(t){this.warnIfRequestHandlerReplaced("onopenlink",this._onopenlink,t),this._onopenlink=t,this.replaceRequestHandler("ui/open-link",{params:kP.shape.params,result:$P},async(o,r)=>{if(!this._onopenlink)throw Error("No onopenlink handler set");return this._onopenlink(o,r)})}_ondownloadfile;get ondownloadfile(){return this._ondownloadfile}set ondownloadfile(t){this.warnIfRequestHandlerReplaced("ondownloadfile",this._ondownloadfile,t),this._ondownloadfile=t,this.replaceRequestHandler("ui/download-file",{params:FP.shape.params,result:CP},async(o,r)=>{if(!this._ondownloadfile)throw Error("No ondownloadfile handler set");return this._ondownloadfile(o,r)})}get onrequestteardown(){return this.getEventHandler("requestteardown")}set onrequestteardown(t){this.setEventHandler("requestteardown",t)}_onrequestdisplaymode;get onrequestdisplaymode(){return this._onrequestdisplaymode}set onrequestdisplaymode(t){this.warnIfRequestHandlerReplaced("onrequestdisplaymode",this._onrequestdisplaymode,t),this._onrequestdisplaymode=t,this.replaceRequestHandler("ui/request-display-mode",{params:My.shape.params,result:Ay},async(o,r)=>{if(!this._onrequestdisplaymode)throw Error("No onrequestdisplaymode handler set");return this._onrequestdisplaymode(o,r)})}get onloggingmessage(){return this.getEventHandler("loggingmessage")}set onloggingmessage(t){this.setEventHandler("loggingmessage",t)}_onupdatemodelcontext;get onupdatemodelcontext(){return this._onupdatemodelcontext}set onupdatemodelcontext(t){this.warnIfRequestHandlerReplaced("onupdatemodelcontext",this._onupdatemodelcontext,t),this._onupdatemodelcontext=t,this.replaceRequestHandler("ui/update-model-context",{params:VP.shape.params,result:Ro},async(o,r)=>{if(!this._onupdatemodelcontext)throw Error("No onupdatemodelcontext handler set");return this._onupdatemodelcontext(o,r)})}_oncalltool;get oncalltool(){return this._oncalltool}set oncalltool(t){this.warnIfRequestHandlerReplaced("oncalltool",this._oncalltool,t),this._oncalltool=t,this.replaceRequestHandler("tools/call",async(o,r)=>{if(!this._oncalltool)throw Error("No oncalltool handler set");return this._oncalltool(o.params,r)})}_oncreatesamplingmessage;get oncreatesamplingmessage(){return this._oncreatesamplingmessage}set oncreatesamplingmessage(t){this.warnIfRequestHandlerReplaced("oncreatesamplingmessage",this._oncreatesamplingmessage,t),this._oncreatesamplingmessage=t,this.replaceRequestHandler("sampling/createMessage",async(o,r)=>{if(!this._oncreatesamplingmessage)throw Error("No oncreatesamplingmessage handler set");return this._oncreatesamplingmessage(o.params,r)})}sendToolListChanged(t={}){return this.notification({method:"notifications/tools/list_changed",params:t})}_onlistresources;get onlistresources(){return this._onlistresources}set onlistresources(t){this.warnIfRequestHandlerReplaced("onlistresources",this._onlistresources,t),this._onlistresources=t,this.replaceRequestHandler("resources/list",async(o,r)=>{if(!this._onlistresources)throw Error("No onlistresources handler set");return this._onlistresources(o.params,r)})}_onlistresourcetemplates;get onlistresourcetemplates(){return this._onlistresourcetemplates}set onlistresourcetemplates(t){this.warnIfRequestHandlerReplaced("onlistresourcetemplates",this._onlistresourcetemplates,t),this._onlistresourcetemplates=t,this.replaceRequestHandler("resources/templates/list",async(o,r)=>{if(!this._onlistresourcetemplates)throw Error("No onlistresourcetemplates handler set");return this._onlistresourcetemplates(o.params,r)})}_onreadresource;get onreadresource(){return this._onreadresource}set onreadresource(t){this.warnIfRequestHandlerReplaced("onreadresource",this._onreadresource,t),this._onreadresource=t,this.replaceRequestHandler("resources/read",async(o,r)=>{if(!this._onreadresource)throw Error("No onreadresource handler set");return this._onreadresource(o.params,r)})}sendResourceListChanged(t={}){return this.notification({method:"notifications/resources/list_changed",params:t})}_onlistprompts;get onlistprompts(){return this._onlistprompts}set onlistprompts(t){this.warnIfRequestHandlerReplaced("onlistprompts",this._onlistprompts,t),this._onlistprompts=t,this.replaceRequestHandler("prompts/list",async(o,r)=>{if(!this._onlistprompts)throw Error("No onlistprompts handler set");return this._onlistprompts(o.params,r)})}sendPromptListChanged(t={}){return this.notification({method:"notifications/prompts/list_changed",params:t})}getCapabilities(){return this._hostCapabilities}async _onAppsInitialize(t){let o=t.protocolVersion;return this._appInfo!==void 0&&console.warn("[ext-apps] AppBridge received a second ui/initialize. The View may be double-mounting (e.g. React StrictMode in dev) without closing the previous App instance. Responding normally; the latest appInfo/appCapabilities replace the previous values."),this._appCapabilities=t.appCapabilities,this._appInfo=t.appInfo,{protocolVersion:WP.includes(o)?o:jy,hostCapabilities:this.getCapabilities(),hostInfo:this._hostInfo,hostContext:this._hostContext}}setHostContext(t){let o={},r=!1;for(let n of Object.keys(t)){let s=this._hostContext[n],i=t[n];KP(s,i)||(o[n]=i,r=!0)}r&&(this._hostContext=t,this.sendHostContextChange(o))}sendHostContextChange(t){return this.notification({method:"ui/notifications/host-context-changed",params:t})}sendToolInput(t){return this.notification({method:"ui/notifications/tool-input",params:t})}sendToolInputPartial(t){return this.notification({method:"ui/notifications/tool-input-partial",params:t})}sendToolResult(t){return this.notification({method:"ui/notifications/tool-result",params:t})}sendToolCancelled(t){return this.notification({method:"ui/notifications/tool-cancelled",params:t})}sendSandboxResourceReady(t){return this.notification({method:"ui/notifications/sandbox-resource-ready",params:t})}teardownResource(t,o){return this.request({method:"ui/resource-teardown",params:t},AP,o)}sendResourceTeardown=this.teardownResource;callTool(t,o){return this.request({method:"tools/call",params:t},o)}listTools(t,o){return this.request({method:"tools/list",params:t},o)}async connect(t){if(this.transport)throw Error("AppBridge is already connected. Call close() before connecting again.");if(this._initializedReceived=!1,this._client){let o=this._client.getServerCapabilities();if(!o)throw Error("Client server capabilities not available");o.tools&&(this.oncalltool=async(r,n)=>this._client.request({method:"tools/call",params:r},{signal:n.mcpReq.signal}),o.tools.listChanged&&this._client.setNotificationHandler("notifications/tools/list_changed",r=>this.sendToolListChanged(r.params))),o.resources&&(this.onlistresources=async(r,n)=>this._client.request({method:"resources/list",params:r},{signal:n.mcpReq.signal}),this.onlistresourcetemplates=async(r,n)=>this._client.request({method:"resources/templates/list",params:r},{signal:n.mcpReq.signal}),this.onreadresource=async(r,n)=>this._client.request({method:"resources/read",params:r},{signal:n.mcpReq.signal}),o.resources.listChanged&&this._client.setNotificationHandler("notifications/resources/list_changed",r=>this.sendResourceListChanged(r.params))),o.prompts&&(this.onlistprompts=async(r,n)=>this._client.request({method:"prompts/list",params:r},{signal:n.mcpReq.signal}),o.prompts.listChanged&&this._client.setNotificationHandler("notifications/prompts/list_changed",r=>this.sendPromptListChanged(r.params)))}return super.connect(t)}};function KP(e,t){return JSON.stringify(e)===JSON.stringify(t)}var Uy=[200,800],GP=e=>e?.name==="TypeError"&&/^(Failed to fetch|fetch failed|Load failed|NetworkError when attempting to fetch resource\.)$/.test(e.message);function YP(e,t){return new Promise((o,r)=>{t?.throwIfAborted();let s=setTimeout(()=>{t?.removeEventListener("abort",i),o()},e),i=()=>{clearTimeout(s),t.removeEventListener("abort",i),r(t.reason)};t?.addEventListener("abort",i,{once:!0})})}function Zy(e,{signal:t,onDiagnostic:o,wait:r=YP}={}){return async n=>{for(let s=1;;s++){t?.throwIfAborted();try{let i=await e(n);return t?.throwIfAborted(),s>1&&o?.({state:"recovered",uri:n.uri,attempt:s}),i}catch(i){if(t?.throwIfAborted(),!GP(i))throw i;if(s>Uy.length)throw o?.({state:"exhausted",uri:n.uri,attempt:s,message:i.message}),Object.assign(new Error(`MCP resource transport failed for ${n.uri} after ${s} attempts: ${i.message}. Inspect the host connection and browser network diagnostics; this resource-loading error does not establish a component assertion failure.`,{cause:i}),{code:"ADS_RESOURCE_TRANSPORT"});let a=Uy[s-1];o?.({state:"retry",uri:n.uri,attempt:s,nextAttempt:s+1,delayMs:a,message:i.message}),await r(a,t)}}}}var re=e=>document.getElementById(e),Ha=new URLSearchParams(location.search),XP=Ha.get("scenario")??Ha.get("category"),QP=["tokens","components","patterns","composition"],we="tokens",oo={},kg=!0,Ng=[],qg=[],jg=[],$g,gt,ft,Zn,Dn=0,fr=Ha.get("capture")==="1"&&Ha.get("theme")==="dark"?"dark":"light";Ha.get("capture")==="1"&&(document.documentElement.dataset.capture="true");document.documentElement.dataset.theme=fr;var Cg,Ig=!1,Au,Mg,Lu=!1,Va=!1,Fn,mr,Og=0,qu,ju=new AbortController,St=new Cy({name:"ads-showcase",version:"1.0.0"},{versionNegotiation:{mode:"auto"},capabilities:{extensions:{"io.modelcontextprotocol/ui":{mimeTypes:["text/html;profile=mcp-app"]}}}}),Mu=(e,t={})=>St.callTool({name:e,arguments:t}),Ag=Zy(e=>St.readResource(e,{signal:ju.signal}),{signal:ju.signal,onDiagnostic:e=>{e.state==="retry"&&(document.documentElement.dataset.adsResourceRetries=String(Number(document.documentElement.dataset.adsResourceRetries??0)+1)),console.warn("ADS resource transport",e)}}),eT=async e=>JSON.parse((await Ag({uri:e})).contents.find(t=>typeof t.text=="string").text);function ro(e,t=!1){re("loader").hidden=!0,re("preview").removeAttribute("aria-busy"),re("status").hidden=!e,re("status").textContent=e,re("status").dataset.error=String(t),re("status").setAttribute("role",t?"alert":"status")}function Fy(){re("status").hidden=!0,re("loader").hidden=!1,re("preview").setAttribute("aria-busy","true")}function Dy(e){let t=new URL(location.href);return t.searchParams.delete("scenario"),t.searchParams.delete("category"),e&&t.searchParams.set("scenario",e),t.pathname+t.search+t.hash}function tT(){return we==="tokens"?Ng:qg.filter(e=>e.kind===we)}function Lg(){return{theme:fr,displayMode:"inline",containerDimensions:{width:re("frame-slot").clientWidth,height:re("frame-slot").clientHeight}}}function Ug(){let e=tT();e.some(o=>o.id===oo[we])||(oo[we]=e[0]?.id??null),history.replaceState(null,"",Dy(oo[we]));let t=re("sidebar").scrollTop;re("entries").setAttribute("aria-label",we[0].toUpperCase()+we.slice(1)),re("entries").replaceChildren(...e.map(o=>{let r=document.createElement("a");return r.textContent=o.title,r.href=Dy(o.id),o.id===oo[we]&&r.setAttribute("aria-current","page"),r.addEventListener("click",n=>{n.button||n.metaKey||n.ctrlKey||n.shiftKey||n.altKey||(n.preventDefault(),oo[we]=o.id,Ug(),Vn().catch(gr))}),r})),re("sidebar").scrollTop=t;for(let o of re("sections").querySelectorAll("button"))o.dataset.section===we?o.setAttribute("aria-current","page"):o.removeAttribute("aria-current")}function gr(e){!Lu&&!Va&&(re("status").dataset.errorCategory=e.category??(e.code==="ADS_RESOURCE_TRANSPORT"?"transport":"runner"),re("status").dataset.errorCode=e.code??"ADS_HOST_STARTUP",ro(`${e.message}${gt?`
|
|
116
|
-
The retained preview is not verified against the current source.`:""}`,!0))}async function Hn(e){if(e){try{await e.teardownResource({},{timeout:500})}catch{}await e.close()}}function Hy(){if(Lu||Va)return;Va=!0,ju.abort(),++Dn,clearTimeout(Au),ro("MCP disconnected. The retained preview is stale. Refresh the page after restarting ADS.",!0);let e=ft;ft=void 0,Hn(e)}async function Vy(){$g=(await Mu("ads_status")).structuredContent,re("title").textContent=$g.project.showcase.title,re("title").title=$g.projectRoot;let e=[],t;do{let i=await St.listTools(t?{cursor:t}:{});e.push(...i.tools),t=i.nextCursor}while(t);jg=e,Ng=(await Mu("ads_tokens",{limit:1})).structuredContent.categories;let o=[],r=0;do{let i=(await Mu("ads_scenarios",{limit:50,offset:r})).structuredContent;o.push(...i.scenarios),r=i.nextOffset}while(r!==void 0);qg=o;let n=kg?XP:oo[we],s=qg.find(i=>i.id===n);s&&QP.includes(s.kind)?(we=s.kind,oo[we]=s.id):kg&&Ng.some(i=>i.id===n)&&(oo.tokens=n),kg=!1,Ug()}function Vn({force:e=!1}={}){if(Va)return Promise.resolve();let t=oo[we],o=`${we}:${t}`;if(Zn?.key===o)return Zn.promise;if(!e&&!Zn&>?.key===o)return Promise.resolve();let r={key:o};return Zn=r,r.promise=oT(t,o,we==="tokens").finally(()=>{Zn===r&&(Zn=void 0)}),r.promise}async function oT(e,t,o){let r=++Dn;try{if(!e&&!o){if(await Hn(ft),r!==Dn)return;ft=void 0,gt=void 0,Fn=void 0,mr=void 0,re("frame-slot").replaceChildren(),ro("No scenarios discovered. Colocate an independently mountable scenario and fixture with the public implementation.");return}let n=jg.find(l=>l.name===(o?"ads_show_tokens":"ads_show_scenario"));if(!n?._meta?.ui?.resourceUri)throw new Error("The server did not expose a preview App tool.");let s=o?e?{category:e}:{}:{id:e};Fy();let i=await Mu(n.name,s);if(r!==Dn)return;if(i.isError)throw Object.assign(new Error(i.content?.find(l=>l.type==="text")?.text??"Preview build failed."),i.structuredContent?.failure);gt={key:t,id:e,token:o&&!i.structuredContent?.scenario,build:i.structuredContent?.build,manifestRevision:i.structuredContent?.manifestRevision};let a=await rT(n._meta.ui.resourceUri);if(r!==Dn)return;if(Fn==="ui://ads/preview"&&!gt.manifestRevision)throw new Error("The resource preview has no manifest revision.");await a.sendToolInput({arguments:s}),await a.sendToolResult(i),i.structuredContent?.manifestUri||ro("")}catch(n){r===Dn&&gr(n)}}async function rT(e){if(Fn===e&&mr)return mr;let t=++Og,o=ft;ft=void 0,Fn=e,mr=(async()=>{await Hn(o);let r=await Ag({uri:e});if(t!==Og||Lu||Va)throw new Error("Preview opening was superseded.");let n=r.contents.find(u=>u.mimeType==="text/html;profile=mcp-app"&&typeof u.text=="string");if(!n)throw new Error("The preview resource contains no MCP App.");let s=document.createElement("iframe");s.title="Design-system preview",s.setAttribute("sandbox","allow-scripts allow-same-origin"),s.src=qu,re("frame-slot").replaceChildren(s);let i=new Nu(null,{name:"ADS showcase",version:"1.0.0"},{serverResources:{},serverTools:{},logging:{}},{hostContext:Lg()});ft=i,i.onreadresource=u=>Ag(u),i.oncalltool=u=>{let d=jg.find(h=>h.name===u.name);if(!d||!(d._meta?.ui?.visibility??["model","app"]).includes("app"))throw new Error("This tool is not callable from an App.");return St.callTool(u)},i.onloggingmessage=u=>{u.logger!=="ads-preview"||u.data?.id!==gt?.id||(u.data.state==="ready"&&u.data.revision===gt.manifestRevision?ro(""):u.data.code==="ADS_PREVIEW_REOPEN"?(ft=void 0,Fn=void 0,mr=void 0,Fy(),Hn(i).then(()=>Vn({force:!0})).catch(gr)):u.data.state==="error"&&ro(u.data.message,!0))},i.onerror=gr;let a,l=new Promise((u,d)=>{a=setTimeout(()=>d(new Error("MCP App initialization timed out.")),15e3),i.oninitialized=()=>{clearTimeout(a),u()}});i.onsandboxready=()=>i.sendSandboxResourceReady({html:n.text,csp:n._meta?.ui?.csp,sandbox:"allow-scripts"});try{await Promise.all([i.connect(new Ou(s.contentWindow,s.contentWindow)),l])}catch(u){throw await Hn(i),u}finally{clearTimeout(a)}return i})();try{return await mr}catch(r){throw t===Og&&(Fn=void 0,mr=void 0),r}}async function nT(){let e=gt;if(!e||e.token)return;let t=await eT("ads://project/build");if(gt!==e)return;let o=t.previews.find(r=>r.id===e.id);if(!o){ro("The selected scenario is no longer registered.",!0);return}if(!(["idle","building"].includes(o.state)&&e.build?.current&&o.session===e.build.session&&o.revision<=e.build.revision)){if(o.state==="error"){ro(`Preview build failed. Showing an earlier view.
|
|
116
|
+
The retained preview is not verified against the current source.`:""}`,!0))}async function Hn(e){if(e){try{await e.teardownResource({},{timeout:500})}catch{}await e.close()}}function Hy(){if(Lu||Va)return;Va=!0,ju.abort(),++Dn,clearTimeout(Au),ro("MCP disconnected. The retained preview is stale. Refresh the page after restarting ADS.",!0);let e=ft;ft=void 0,Hn(e)}async function Vy(){$g=(await Mu("ads_status")).structuredContent,re("title").textContent=$g.project.showcase.title,re("title").title=$g.projectRoot;let e=[],t;do{let i=await St.listTools(t?{cursor:t}:{});e.push(...i.tools),t=i.nextCursor}while(t);jg=e,Ng=(await Mu("ads_tokens",{limit:1})).structuredContent.categories;let o=[],r=0;do{let i=(await Mu("ads_scenarios",{limit:50,offset:r})).structuredContent;o.push(...i.scenarios),r=i.nextOffset}while(r!==void 0);qg=o;let n=kg?XP:oo[we],s=qg.find(i=>i.id===n);s&&QP.includes(s.kind)?(we=s.kind,oo[we]=s.id):kg&&Ng.some(i=>i.id===n)&&(oo.tokens=n),kg=!1,Ug()}function Vn({force:e=!1}={}){if(Va)return Promise.resolve();let t=oo[we],o=`${we}:${t}`;if(Zn?.key===o)return Zn.promise;if(!e&&!Zn&>?.key===o)return Promise.resolve();let r={key:o};return Zn=r,r.promise=oT(t,o,we==="tokens").finally(()=>{Zn===r&&(Zn=void 0)}),r.promise}async function oT(e,t,o){let r=++Dn;try{if(!e&&!o){if(await Hn(ft),r!==Dn)return;ft=void 0,gt=void 0,Fn=void 0,mr=void 0,re("frame-slot").replaceChildren(),ro("No scenarios discovered. Colocate an independently mountable scenario and fixture with the public implementation.");return}let n=jg.find(l=>l.name===(o?"ads_show_tokens":"ads_show_scenario"));if(!n?._meta?.ui?.resourceUri)throw new Error("The server did not expose a preview App tool.");let s=o?e?{category:e}:{}:{id:e};Fy();let i=await Mu(n.name,s);if(r!==Dn)return;if(i.isError)throw Object.assign(new Error(i.content?.find(l=>l.type==="text")?.text??"Preview build failed."),i.structuredContent?.failure);gt={key:t,id:e,token:o&&!i.structuredContent?.scenario,build:i.structuredContent?.build,manifestRevision:i.structuredContent?.manifestRevision};let a=await rT(n._meta.ui.resourceUri);if(r!==Dn)return;if(Fn==="ui://ads/preview"&&!gt.manifestRevision)throw new Error("The resource preview has no manifest revision.");await a.sendToolInput({arguments:s}),await a.sendToolResult(i),i.structuredContent?.manifestUri||ro("")}catch(n){r===Dn&&gr(n)}}async function rT(e){if(Fn===e&&mr)return mr;let t=++Og,o=ft;ft=void 0,Fn=e,mr=(async()=>{await Hn(o);let r=await Ag({uri:e});if(t!==Og||Lu||Va)throw new Error("Preview opening was superseded.");let n=r.contents.find(u=>u.mimeType==="text/html;profile=mcp-app"&&typeof u.text=="string");if(!n)throw new Error("The preview resource contains no MCP App.");let s=document.createElement("iframe");s.title="Design-system preview",s.setAttribute("sandbox","allow-scripts allow-same-origin allow-forms"),s.src=qu,re("frame-slot").replaceChildren(s);let i=new Nu(null,{name:"ADS showcase",version:"1.0.0"},{serverResources:{},serverTools:{},logging:{}},{hostContext:Lg()});ft=i,i.onreadresource=u=>Ag(u),i.oncalltool=u=>{let d=jg.find(h=>h.name===u.name);if(!d||!(d._meta?.ui?.visibility??["model","app"]).includes("app"))throw new Error("This tool is not callable from an App.");return St.callTool(u)},i.onloggingmessage=u=>{u.logger!=="ads-preview"||u.data?.id!==gt?.id||(u.data.state==="ready"&&u.data.revision===gt.manifestRevision?ro(""):u.data.code==="ADS_PREVIEW_REOPEN"?(ft=void 0,Fn=void 0,mr=void 0,Fy(),Hn(i).then(()=>Vn({force:!0})).catch(gr)):u.data.state==="error"&&ro(u.data.message,!0))},i.onerror=gr;let a,l=new Promise((u,d)=>{a=setTimeout(()=>d(new Error("MCP App initialization timed out.")),15e3),i.oninitialized=()=>{clearTimeout(a),u()}});i.onsandboxready=()=>i.sendSandboxResourceReady({html:n.text,csp:n._meta?.ui?.csp,sandbox:"allow-scripts allow-forms"});try{await Promise.all([i.connect(new Ou(s.contentWindow,s.contentWindow)),l])}catch(u){throw await Hn(i),u}finally{clearTimeout(a)}return i})();try{return await mr}catch(r){throw t===Og&&(Fn=void 0,mr=void 0),r}}async function nT(){let e=gt;if(!e||e.token)return;let t=await eT("ads://project/build");if(gt!==e)return;let o=t.previews.find(r=>r.id===e.id);if(!o){ro("The selected scenario is no longer registered.",!0);return}if(!(["idle","building"].includes(o.state)&&e.build?.current&&o.session===e.build.session&&o.revision<=e.build.revision)){if(o.state==="error"){ro(`Preview build failed. Showing an earlier view.
|
|
117
117
|
${o.diagnostics.map(r=>`${r.file??""}${r.line?`:${r.line}`:""}: ${r.message}`).join(`
|
|
118
118
|
`)}`,!0);return}if(["idle","building"].includes(o.state)){ro("Rebuilding\u2026 The current view is the previous preview.");return}o.current&&(o.revision!==e.build?.revision||o.session!==e.build?.session)&&await Vn({force:!0})}}function Uu(e=!1,t=!1){Ig||=e,t&>?.token&&(gt.key="invalidated"),clearTimeout(Au),Au=setTimeout(()=>{if(Cg){Uu(e,t);return}Cg=(async()=>{Ig&&(Ig=!1,await Vy()),await Vn(),await nT()})().catch(gr).finally(()=>{Cg=void 0})},150)}for(let e of re("sections").querySelectorAll("button"))e.addEventListener("click",()=>{we=e.dataset.section,Ug(),Vn().catch(gr)});re("pane-toggle").addEventListener("click",()=>{let e=document.body.classList.toggle("collapsed");re("pane-toggle").setAttribute("aria-expanded",String(!e)),re("pane-toggle").setAttribute("aria-label",e?"Show navigation pane":"Hide navigation pane"),re("pane-toggle").title=e?"Show navigation pane":"Hide navigation pane",re("pane-toggle").querySelector("path").setAttribute("d",e?"M6 4l4 4-4 4":"M10 4l-4 4 4 4")});re("theme").addEventListener("click",()=>{fr=fr==="light"?"dark":"light",document.documentElement.dataset.theme=fr,re("theme").setAttribute("aria-label",fr==="dark"?"Use light theme":"Use dark theme"),re("theme").title=re("theme").getAttribute("aria-label"),re("theme").querySelector("svg").innerHTML=fr==="dark"?'<circle cx="8" cy="8" r="3"/><path d="M8 1v1M8 14v1M1 8h1M14 8h1M3 3l1 1M12 12l1 1M3 13l1-1M12 4l1-1"/>':'<path d="M13.5 9a5.5 5.5 0 0 1-6.5-6.5A5.5 5.5 0 1 0 13.5 9Z"/>',ft?.setHostContext(Lg())});re("minimize").addEventListener("click",()=>{re("header").hidden=!0,document.body.classList.add("collapsed")});var Jy=new ResizeObserver(()=>ft?.setHostContext(Lg()));Jy.observe(re("frame-slot"));window.addEventListener("pagehide",()=>{Lu=!0,ju.abort(),clearTimeout(Au),Jy.disconnect(),Hn(ft),Mg?.close(),St.close()},{once:!0});St.onclose=Hy;St.setNotificationHandler("notifications/resources/list_changed",()=>Uu(!0));St.setNotificationHandler("notifications/tools/list_changed",()=>Uu(!0));St.setNotificationHandler("notifications/resources/updated",({params:e})=>Uu(!1,e.uri==="ads://project/tokens"));try{if(qu=(await fetch("/host.json").then(e=>e.json())).sandboxUrl,!qu||new URL(qu).origin===location.origin)throw new Error("The separate-origin MCP App sandbox is unavailable.");await St.connect(new Iy(new URL("/mcp",location.href))),Mg=await St.listen({resourcesListChanged:!0,toolsListChanged:!0,resourceSubscriptions:["ads://project/build","ads://project/tokens","ads://project/scenarios","ads://project/status"]}),Mg.closed.then(e=>{e!=="local"&&Hy()}),await Vy(),await Vn()}catch(e){gr(e)}
|
|
119
119
|
/*! Bundled license information:
|
|
@@ -1 +1 @@
|
|
|
1
|
-
function c(e,t){let
|
|
1
|
+
function c(e,t){let o=new DOMParser().parseFromString(e,"text/html"),r=o.createElement("meta");if(r.httpEquiv="Content-Security-Policy",r.content=t,o.head.prepend(r),!o.querySelector("base[href]")){let i=o.querySelector("base")??o.createElement("base");i.setAttribute("href","about:srcdoc"),r.after(i)}return"<!doctype html>"+o.documentElement.outerHTML}var a=new URL(location.href).searchParams.get("parent"),n;function s(e=[]){return e.filter(t=>/^(?:https?:\/\/[a-z0-9.*:[\]-]+(?::\d+)?|data:|blob:|about:)$/i.test(t)).join(" ")}function d(e={}){let t=s(e.resourceDomains);return`default-src 'none';script-src 'unsafe-inline' ${t};style-src 'unsafe-inline' ${t};img-src data: ${t};font-src ${t||"'none'"};media-src ${t||"'none'"};worker-src ${t||"'none'"};connect-src ${s(e.connectDomains)||"'none'"};frame-src ${s(e.frameDomains)||"'none'"};base-uri about: ${s(e.baseUriDomains)};object-src 'none';form-action 'none'`}window.addEventListener("message",e=>{if(e.source===parent&&e.origin===a){let t=e.data;if(t?.method==="ui/notifications/sandbox-resource-ready"){let{html:o,csp:r}=t.params??{};if(typeof o!="string")return;n?.remove(),n=window.document.createElement("iframe"),n.title="Design-system App",n.setAttribute("sandbox","allow-scripts allow-forms"),window.document.body.append(n),n.srcdoc=c(o,d(r))}else t?.jsonrpc==="2.0"&&!t.method?.startsWith("ui/notifications/sandbox-")&&n?.contentWindow.postMessage(t,"*")}else e.source===n?.contentWindow&&e.data?.jsonrpc==="2.0"&&!e.data.method?.startsWith("ui/notifications/sandbox-")&&parent.postMessage(e.data,a)});parent.postMessage({jsonrpc:"2.0",method:"ui/notifications/sandbox-proxy-ready",params:{}},a);
|
|
@@ -54,18 +54,33 @@ For on-demand current/candidate screenshots and visual dry-run review, read
|
|
|
54
54
|
[captures.md](references/captures.md).
|
|
55
55
|
For a rejected patch, failed check or loading error, read
|
|
56
56
|
[evidence and diagnosis](references/evidence.md); inspect the failing boundary
|
|
57
|
-
|
|
57
|
+
then page needed `ads_results` sections using the returned result ID. Inspect
|
|
58
|
+
one `ads_investigate({unit,check,runId})` summary before changing APIs or policy;
|
|
59
|
+
load request/error/artifact details only when relevant.
|
|
58
60
|
|
|
59
61
|
|
|
62
|
+
Review/demo layout belongs in development-only `review/`, exported through the
|
|
63
|
+
separate `${publicAlias}/review` entry (normally `@ds/review`), never the production
|
|
64
|
+
barrel. Product patterns are application UI. For ownership, migration, token
|
|
65
|
+
renderers, build aliases or `review-import` / `review-contract` diagnostics, read
|
|
66
|
+
[development review UI](references/review.md), also served as `ads://guides/review`.
|
|
67
|
+
|
|
68
|
+
A component provides one cohesive UI capability; a pattern arranges capabilities
|
|
69
|
+
into reusable structures or workflows. Components may compose components/controllers;
|
|
70
|
+
layout patterns may arrange slots. Do not classify by component count. For owner
|
|
71
|
+
selection or `component-dependency`, `component-contract` or `component-style`, read
|
|
72
|
+
[layer responsibilities and contracts](references/layers.md), also served as
|
|
73
|
+
`ads://guides/layers`. Preserve public APIs and documented non-state styling hooks.
|
|
74
|
+
|
|
60
75
|
For parent/child `covers`, conditional-control review, reference comparisons,
|
|
61
|
-
public style-helper use, fragment
|
|
62
|
-
`showcase-class` findings, read [scenario contracts](references/scenarios.md). Its verification
|
|
76
|
+
public style-helper use, native slot/selector composition, fragment navigation,
|
|
77
|
+
or `scenario-render` / `showcase-control` / `showcase-class` findings, read [scenario contracts](references/scenarios.md). Its verification
|
|
63
78
|
examples explain API visibility, reference baselines and state-transition checks.
|
|
64
79
|
Traced parents can cover declared public children; verified public Linaria
|
|
65
80
|
helpers do not grant custom scenario CSS.
|
|
66
81
|
|
|
67
82
|
For atomic base/variant styling, helper/wrapper class composition, extracted
|
|
68
|
-
custom-property arithmetic, or an
|
|
83
|
+
custom-property arithmetic, typed `@property` registrations, or an
|
|
69
84
|
`atomic-composition-review` / `atomic-composition-limit` advisory, read
|
|
70
85
|
[atomic style composition](references/atomic-styles.md). The same guide is served
|
|
71
86
|
as `ads://guides/atomic-styles`. It explains shorthand/logical conflicts and
|
|
@@ -219,7 +234,7 @@ fixture under the configured token directory. Declare metadata
|
|
|
219
234
|
The renderer name is project-owned; use its real public API. Each literal fixture
|
|
220
235
|
sample declares `{ token, property, probe }`: a real token, the CSS property its
|
|
221
236
|
sample demonstrates, and a valid test value different from that token's current
|
|
222
|
-
value. Pass `samples` into the rendered
|
|
237
|
+
value. Pass `samples` into the rendered development-only review renderer. Names, sample text,
|
|
223
238
|
bindings and probes are fixture data; displayed values come from foundation CSS.
|
|
224
239
|
There is no property catalogue, token-name mapping or probe palette in ADS.
|
|
225
240
|
Every token needs sample coverage; fabricated tokens, missing bindings/probes,
|
|
@@ -240,10 +255,12 @@ source definitions and values; inspecting definitions does not prove UI parity.
|
|
|
240
255
|
The tool keeps the shared naming contract; sample templates and their token
|
|
241
256
|
lists belong to the project.
|
|
242
257
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
258
|
+
Development review helpers need no product catalogue scenario. A token scenario
|
|
259
|
+
may name its review renderer in `covers`; ordinary component/pattern scenarios
|
|
260
|
+
cover their production subjects. Keep review exports in the separate review entry.
|
|
261
|
+
Remove redundant helper catalogue scenarios during migration. Product UI retains
|
|
262
|
+
its real render-coverage requirement. Keep directory ownership anchored by its
|
|
263
|
+
implementation entry (`index` or a file matching the directory name) or colocated fixture.
|
|
247
264
|
Import colocated image/font fixtures so MCP serves them through asset resources.
|
|
248
265
|
For a broken-image case, use a local failing fixture, rather than an external
|
|
249
266
|
network failure or a domain-specific CSP exception. Production asset APIs remain
|
|
@@ -267,7 +284,7 @@ branch, property and value must fit its role. Token-backed control skins, layout
|
|
|
267
284
|
interactive state rules and owner hooks are rejected even through `:where()`.
|
|
268
285
|
Do not copy an unchecked upstream preflight or turn a component repair into a reset.
|
|
269
286
|
Keep behavior and skin in their runtime owner. Put demo-only sizing and framing
|
|
270
|
-
in a
|
|
287
|
+
in a development-only review helper; add component props for a deliberate runtime
|
|
271
288
|
API need. Preserve required capabilities while following ownership rules.
|
|
272
289
|
Use tokens for absolute/font-relative lengths, including `ch`, `ex`, `cap`, `ic`
|
|
273
290
|
and `lh`; calculations and fallback values follow the same rule. Structural
|
|
@@ -81,6 +81,54 @@ const treatment = css`
|
|
|
81
81
|
This is a distinct state, not two competing base atoms. Whether changing padding
|
|
82
82
|
on hover is desirable remains a product decision.
|
|
83
83
|
|
|
84
|
+
## Register typed custom properties without atomizing their descriptors
|
|
85
|
+
|
|
86
|
+
Use a literal `@property` in its component/pattern's Linaria template when the
|
|
87
|
+
browser needs a typed, interpolated runtime variable:
|
|
88
|
+
|
|
89
|
+
```tsx
|
|
90
|
+
const treatment = css`
|
|
91
|
+
@property --ds-example-progress {
|
|
92
|
+
syntax: "<number>";
|
|
93
|
+
inherits: false;
|
|
94
|
+
initial-value: 0;
|
|
95
|
+
}
|
|
96
|
+
opacity: var(--ds-example-progress);
|
|
97
|
+
`;
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The identifier is illustrative; use a unique name owned by the real unit.
|
|
101
|
+
Registrations are document-global, even when authored beside one component.
|
|
102
|
+
Put them at template level or inside conditional at-rules, never element selectors.
|
|
103
|
+
Use the same ADS tag resolver described below in both product and preview builds;
|
|
104
|
+
it preserves each complete registration and its conditional context instead of
|
|
105
|
+
splitting syntax/inherits/initial-value into invalid atomic rules. This works for
|
|
106
|
+
atomic `css` and `styled`; the browser receives CSS without a registration runtime.
|
|
107
|
+
MCP delivers it inside the App document, not the outer showcase host.
|
|
108
|
+
|
|
109
|
+
`property-registration` validates descriptor shape, CSS syntax/initial-value and
|
|
110
|
+
ownership, and rejects conflicting schemas or registrations shared by unrelated
|
|
111
|
+
owners. Identical registrations from the same owner are permitted. Runtime
|
|
112
|
+
`--ds-*` / `--ux-*` variables stay with their runtime owner. A shared token's
|
|
113
|
+
registration belongs beside its actual definition in a foundation value sheet;
|
|
114
|
+
a namespaced owner token's registration belongs in its registered `tokens.css`.
|
|
115
|
+
Neither `base.css` nor `fonts.css` becomes a registration escape hatch. Registration
|
|
116
|
+
alone does not define a token or satisfy its sample/fixture coverage.
|
|
117
|
+
|
|
118
|
+
Typed initial values must match their syntax and be computationally independent:
|
|
119
|
+
`0px` is valid metadata; `var(--space-4)`, `1rem` and `currentColor` are not.
|
|
120
|
+
The literal exemption applies only to registration descriptors. Keep design
|
|
121
|
+
assignments outside them token-backed; a technical fallback is not a place to
|
|
122
|
+
hide a component skin. Use literal static descriptors so guards can inspect them.
|
|
123
|
+
See the [W3C registration contract](https://www.w3.org/TR/css-properties-values-api-1/#at-property-rule).
|
|
124
|
+
|
|
125
|
+
Verify extracted CSS and actual interpolation, including after minification and
|
|
126
|
+
inside the MCP App. A declaration can parse while a browser ignores an invalid
|
|
127
|
+
registration. Keep required browser fallbacks and reduced-motion behavior; do not
|
|
128
|
+
replace a valid CSS implementation with script merely to evade extraction or a
|
|
129
|
+
guard limitation. These checks cover declarative registrations; arbitrary dynamic
|
|
130
|
+
`CSS.registerProperty()` calls are not an ownership proof.
|
|
131
|
+
|
|
84
132
|
## Preserve custom-property arithmetic during extraction
|
|
85
133
|
|
|
86
134
|
Custom properties can hold an unevaluated token stream that becomes arithmetic
|
|
@@ -101,7 +101,12 @@ failed/cancelled capture does not replace existing image slots or apply source.
|
|
|
101
101
|
If a scenario/target is newly introduced, `current` is null with an explicit
|
|
102
102
|
`currentAbsent` explanation; only the real candidate image exists.
|
|
103
103
|
|
|
104
|
-
|
|
104
|
+
The edit reply is a summary. Read `ads_results({resultId,section:"captures"})` for
|
|
105
|
+
the requested image resource URIs and `section:"diagnostics"` for relevant
|
|
106
|
+
messages; follow `nextCursor` when needed. Read image resources explicitly.
|
|
107
|
+
Standalone `ads_capture` continues returning its selected capture directly.
|
|
108
|
+
|
|
109
|
+
Review those images and diagnostics, fix the draft if necessary, then apply
|
|
105
110
|
with `dryRun:false`. Apply reruns all gates. An image is promoted only when the
|
|
106
111
|
accepted source revision matches the captured candidate; other proposals remain
|
|
107
112
|
stale. Images do not approve a patch or certify interaction/accessibility/parity.
|
|
@@ -20,21 +20,67 @@ A complete failed run can contain independent passing cases. Suite interruption,
|
|
|
20
20
|
missing/skipped cases, stale inputs and an unexplained process exit cannot certify
|
|
21
21
|
a pass. Keep the reported case outcome separate from whole-run acceptance.
|
|
22
22
|
|
|
23
|
+
## Retrieve only the evidence needed
|
|
24
|
+
|
|
25
|
+
`ads_verify`, `ads_apply_patch` and `ads_apply_transaction` reply with counts,
|
|
26
|
+
bounded acceptance, at most three failure previews and `resultId`, within 4096
|
|
27
|
+
UTF-8 JSON bytes. Full validation still decides acceptance. Do not repeat a suite
|
|
28
|
+
merely to recover its output, or restate the complete report in conversation.
|
|
29
|
+
|
|
30
|
+
Read a relevant saved section:
|
|
31
|
+
|
|
32
|
+
```js
|
|
33
|
+
ads_results({resultId: "<returned ID>", section: "outcomes", status: "not_passed"})
|
|
34
|
+
// Continue only when needed, with the same section and status:
|
|
35
|
+
ads_results({resultId: "<returned ID>", section: "outcomes", status: "not_passed", cursor: "<nextCursor>"})
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Pages default to 10 rows, allow at most 20 and stay within 8192 JSON bytes. The
|
|
39
|
+
byte cap can return fewer rows; `nextCursor` is the continuation. Other sections
|
|
40
|
+
are `checks` (pipeline steps), `diagnostics`, `advisories`, `warnings`, `changes`,
|
|
41
|
+
`captures`, `acceptance`, `investigation` (mutation boundary) and `selection`.
|
|
42
|
+
A row marked `detailTruncated` is a preview. To read that exact record, pass its
|
|
43
|
+
`recordIndex`, the same result/section, and `offset`/`chars` (maximum 2000 UTF-16
|
|
44
|
+
characters); follow `nextOffset`. Do not combine ranges with a cursor/status filter.
|
|
45
|
+
Read only the relevant log range. Reading pages or ranges never executes checks.
|
|
46
|
+
|
|
47
|
+
`ads://project/results` lists the latest IDs. `ads://results/<resultId>` reads a
|
|
48
|
+
summary; matching query parameters read pages/ranges. Only the latest verification
|
|
49
|
+
and latest mutation snapshots are retained, independently, up to 8 MiB each.
|
|
50
|
+
A superseded ID fails explicitly. An oversized report retains only its summary
|
|
51
|
+
with a warning; disk failure retains details in server memory until shutdown.
|
|
52
|
+
Neither changes actual acceptance or applied state. These are logical JSON byte
|
|
53
|
+
budgets, not token estimates; compatibility text can duplicate structured JSON.
|
|
54
|
+
Saved outcomes describe completion-time evidence, not current source freshness;
|
|
55
|
+
compare their revisions with `ads_behaviors` before claiming a current pass.
|
|
56
|
+
|
|
57
|
+
`ads_behaviors` pages owner summaries, then an exact unit's `checks`, `guarantees`,
|
|
58
|
+
`subjects`, `demonstrations` or `profiles`. Use `nextCursor` and identical filters;
|
|
59
|
+
changes invalidate a discovery cursor. Read a truncated contract via its
|
|
60
|
+
`sourceFile` and bounded `ads_read_file`, rather than requesting everything.
|
|
61
|
+
|
|
23
62
|
## Get a compact investigation
|
|
24
63
|
|
|
25
|
-
|
|
64
|
+
Take an outcome's exact owner/check/run IDs from `ads_results`, then:
|
|
26
65
|
|
|
27
66
|
```json
|
|
28
|
-
{ "unit": "components/Example", "check": "keyboard-navigation" }
|
|
67
|
+
{ "unit": "components/Example", "check": "keyboard-navigation", "runId": "<outcome runId>" }
|
|
29
68
|
```
|
|
30
69
|
|
|
31
|
-
Send this to `ads_investigate`. It reads
|
|
70
|
+
Send this to `ads_investigate`. It reads a summary within 4096 JSON bytes, without
|
|
71
|
+
running code. The run ID rejects evidence superseded by another execution.
|
|
72
|
+
`section: "details"`, `"requests"`, `"browserErrors"` or `"artifacts"` reads separate pages
|
|
73
|
+
(default 10/max 20 rows, at most 8192 JSON bytes); artifacts themselves require
|
|
74
|
+
explicit resource reads. Truncated investigation records also support
|
|
75
|
+
`recordIndex`/`offset`/`chars`; keep the exact `runId` to avoid mixing evidence.
|
|
76
|
+
Without a check, the tool pages available investigations.
|
|
32
77
|
`ads://project/investigations` lists available entries; the returned `resourceUri`
|
|
33
78
|
reads the same bundle. It records:
|
|
34
79
|
|
|
35
80
|
- Project versus isolated candidate, configured framework/profile and scenario.
|
|
36
81
|
- Exact input revision and the preview revision actually observed by the helper.
|
|
37
|
-
-
|
|
82
|
+
- `details.failingStep`, `details.source`, startup viewport and failure boundary.
|
|
83
|
+
- Counts for separately paged relevant requests, browser errors and artifacts.
|
|
38
84
|
- Available registered trace, screenshot or context artifact resource URIs.
|
|
39
85
|
- Explicit `acceptance.established` and `acceptance.unverified` scope.
|
|
40
86
|
|
|
@@ -58,7 +104,8 @@ Candidate evidence never becomes passing live-source evidence. Compare the
|
|
|
58
104
|
recorded input revision with current `ads_behaviors` evidence; a bundle describes
|
|
59
105
|
its run, and does not claim current source merely because it remains available.
|
|
60
106
|
For a build/architecture rejection before behavior runs, inspect the mutation
|
|
61
|
-
|
|
107
|
+
saved `investigation` section via `ads_results`. Status retains the compact
|
|
108
|
+
execution summary and result ID, rather than embedding complete reports.
|
|
62
109
|
|
|
63
110
|
## Preserve the design while repairing the failure
|
|
64
111
|
|
|
@@ -15,8 +15,10 @@ ads_apply_patch({"unit":"components/Example","patchFile":"<returned patchFile>",
|
|
|
15
15
|
|
|
16
16
|
The dry run validates the full candidate, including affected registered behavior
|
|
17
17
|
checks in an isolated project copy. Configured browser checks use that candidate's
|
|
18
|
-
managed preview, not the live showcase.
|
|
19
|
-
|
|
18
|
+
managed preview, not the live showcase. Read the summary, then page failing
|
|
19
|
+
`outcomes`, `diagnostics` or pipeline `checks` through `ads_results` with its
|
|
20
|
+
`resultId`. Inspect one exact outcome with `ads_investigate({unit,check,runId})`;
|
|
21
|
+
fix the draft and diff again. See [evidence.md](evidence.md) for page/range limits.
|
|
20
22
|
Keep behavior.json/specs with their owner and use registered checks; a scratchpad
|
|
21
23
|
run against live source cannot verify unapplied changes. After validation, apply
|
|
22
24
|
with `dryRun:false`; ADS revalidates before writing. Dry-run evidence is not saved
|
|
@@ -101,7 +103,7 @@ and a definition-only `tokens.css`, imported by its runtime source. Flat owners
|
|
|
101
103
|
shared or --ds-/--ux-/--showcase- variables. Only that owner consumes/defines its
|
|
102
104
|
local tokens; aliases can use shared or own tokens. UI styles stay in Linaria.
|
|
103
105
|
Add a colocated token scenario with `kind:"tokens"`, real fixture `samples` and a
|
|
104
|
-
|
|
106
|
+
development-only review renderer. These inputs belong to the same owner and need real sample
|
|
105
107
|
coverage. Manifests and fixtures stay out of the product bundle.
|
|
106
108
|
|
|
107
109
|
`ads_prepare_token_samples({id, bindings?})` preserves existing project samples,
|
|
@@ -120,9 +122,10 @@ them through their actual public composition/customization path and list their r
|
|
|
120
122
|
render references in covers. Do not count internal production renders as public API
|
|
121
123
|
coverage. Behavioral APIs use exercises and registered guarantees instead.
|
|
122
124
|
Scenario controls still use public UI. A native link/button can supply semantics
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
125
|
+
through a public runtime slot that demonstrably clones its child with owned
|
|
126
|
+
props, or through matching runtime-owned selectors attached to the actual rendered
|
|
127
|
+
DOM. Source ownership must be proven; an `asChild` flag or a lookalike marker is
|
|
128
|
+
insufficient. Standalone controls and custom scenario styling remain rejected. See
|
|
126
129
|
[scenarios.md](scenarios.md) for evidence and limits. Preserve the intended
|
|
127
130
|
composition instead of substituting an unrelated styled control or adding a
|
|
128
131
|
demo-only component prop. Review framing belongs in public layout patterns.
|