@reqvire-org/ads 0.3.18 → 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/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 +20 -13
- package/dist/config.mjs +13 -2
- package/dist/init.mjs +17 -14
- package/dist/layers.mjs +288 -0
- package/dist/lint-config.mjs +1 -1
- package/dist/mcp/context.mjs +2 -1
- package/dist/mcp/edit-preparation.mjs +3 -3
- package/dist/mcp/mutations.mjs +1 -1
- package/dist/mcp/prompts.mjs +1 -1
- package/dist/mcp/server.mjs +6 -2
- package/dist/mcp/widget.bundle.js +1 -1
- package/dist/oxlint.mjs +3 -1
- package/dist/preview-resources.mjs +11 -1
- package/dist/public-api.mjs +13 -6
- package/dist/scenario-trace.mjs +6 -6
- package/dist/scenarios-vite.mjs +2 -1
- package/dist/scenarios.mjs +10 -7
- package/dist/showcase/client.bundle.js +1 -1
- package/dist/showcase/sandbox.bundle.js +1 -1
- package/dist/skills/ads-break-ui/SKILL.md +21 -6
- package/dist/skills/ads-break-ui/references/guarded-editing.md +1 -1
- 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 +22 -7
- 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/guarded-editing.md +1 -1
- 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 +21 -6
- package/dist/skills/ads-import/references/guarded-editing.md +1 -1
- 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 +21 -6
- package/dist/skills/ads-migrate/references/guarded-editing.md +1 -1
- 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 +22 -7
- package/dist/skills/ads-motion/references/audit.md +1 -1
- package/dist/skills/ads-motion/references/guarded-editing.md +1 -1
- 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 +32 -5
- package/docs/ARCHITECTURE.md +37 -4
- package/docs/BEHAVIORS.md +7 -0
- package/docs/CHANGELOG.md +23 -0
- package/docs/GUIDE.md +3 -2
- package/docs/MCP.md +27 -6
- package/package.json +1 -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
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
|
}
|
|
@@ -122,7 +122,7 @@ export function createScenarioTrace(program, options) {
|
|
|
122
122
|
const text = ts.isNoSubstitutionTemplateLiteral(template) ? template.text : template.head.text + template.templateSpans.map(span => `__ads_expression__${span.literal.text}`).join("");
|
|
123
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);
|
|
@@ -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);
|
|
@@ -59,6 +59,19 @@ one `ads_investigate({unit,check,runId})` summary before changing APIs or policy
|
|
|
59
59
|
load request/error/artifact details only when relevant.
|
|
60
60
|
|
|
61
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
|
+
|
|
62
75
|
For parent/child `covers`, conditional-control review, reference comparisons,
|
|
63
76
|
public style-helper use, native slot/selector composition, fragment navigation,
|
|
64
77
|
or `scenario-render` / `showcase-control` / `showcase-class` findings, read [scenario contracts](references/scenarios.md). Its verification
|
|
@@ -221,7 +234,7 @@ fixture under the configured token directory. Declare metadata
|
|
|
221
234
|
The renderer name is project-owned; use its real public API. Each literal fixture
|
|
222
235
|
sample declares `{ token, property, probe }`: a real token, the CSS property its
|
|
223
236
|
sample demonstrates, and a valid test value different from that token's current
|
|
224
|
-
value. Pass `samples` into the rendered
|
|
237
|
+
value. Pass `samples` into the rendered development-only review renderer. Names, sample text,
|
|
225
238
|
bindings and probes are fixture data; displayed values come from foundation CSS.
|
|
226
239
|
There is no property catalogue, token-name mapping or probe palette in ADS.
|
|
227
240
|
Every token needs sample coverage; fabricated tokens, missing bindings/probes,
|
|
@@ -242,10 +255,12 @@ source definitions and values; inspecting definitions does not prove UI parity.
|
|
|
242
255
|
The tool keeps the shared naming contract; sample templates and their token
|
|
243
256
|
lists belong to the project.
|
|
244
257
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
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.
|
|
249
264
|
Import colocated image/font fixtures so MCP serves them through asset resources.
|
|
250
265
|
For a broken-image case, use a local failing fixture, rather than an external
|
|
251
266
|
network failure or a domain-specific CSP exception. Production asset APIs remain
|
|
@@ -269,7 +284,7 @@ branch, property and value must fit its role. Token-backed control skins, layout
|
|
|
269
284
|
interactive state rules and owner hooks are rejected even through `:where()`.
|
|
270
285
|
Do not copy an unchecked upstream preflight or turn a component repair into a reset.
|
|
271
286
|
Keep behavior and skin in their runtime owner. Put demo-only sizing and framing
|
|
272
|
-
in a
|
|
287
|
+
in a development-only review helper; add component props for a deliberate runtime
|
|
273
288
|
API need. Preserve required capabilities while following ownership rules.
|
|
274
289
|
Use tokens for absolute/font-relative lengths, including `ch`, `ex`, `cap`, `ic`
|
|
275
290
|
and `lh`; calculations and fallback values follow the same rule. Structural
|
|
@@ -103,7 +103,7 @@ and a definition-only `tokens.css`, imported by its runtime source. Flat owners
|
|
|
103
103
|
shared or --ds-/--ux-/--showcase- variables. Only that owner consumes/defines its
|
|
104
104
|
local tokens; aliases can use shared or own tokens. UI styles stay in Linaria.
|
|
105
105
|
Add a colocated token scenario with `kind:"tokens"`, real fixture `samples` and a
|
|
106
|
-
|
|
106
|
+
development-only review renderer. These inputs belong to the same owner and need real sample
|
|
107
107
|
coverage. Manifests and fixtures stay out of the product bundle.
|
|
108
108
|
|
|
109
109
|
`ads_prepare_token_samples({id, bindings?})` preserves existing project samples,
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
# Components, patterns and application adapters
|
|
2
|
+
|
|
3
|
+
Read this when choosing an owner, moving UI between layers, or resolving
|
|
4
|
+
`component-dependency`, `component-contract`, `component-style`, `no-app-import`
|
|
5
|
+
or `showcase-import`. MCP also serves this guide as `ads://guides/layers`.
|
|
6
|
+
|
|
7
|
+
## Choose by responsibility
|
|
8
|
+
|
|
9
|
+
A **component** provides one cohesive UI capability: semantics, input handling,
|
|
10
|
+
focus, accessibility, states and appearance. It may compose other components and
|
|
11
|
+
controllers. A notification using an icon, action button and spinner can still
|
|
12
|
+
be one component.
|
|
13
|
+
|
|
14
|
+
A **pattern** arranges capabilities and content slots into a reusable structure
|
|
15
|
+
or workflow, such as navigation with search/account controls or a report screen.
|
|
16
|
+
Layout parts may arrange `children` without importing several components.
|
|
17
|
+
Patterns may compose other patterns. A native wrapper or a large implementation
|
|
18
|
+
does not decide the layer.
|
|
19
|
+
|
|
20
|
+
An **application adapter** supplies domain data, store access, route state and
|
|
21
|
+
navigation/service callbacks. Domain-shaped inputs are valid in project-owned
|
|
22
|
+
patterns; importing a particular application store/router is not.
|
|
23
|
+
|
|
24
|
+
A **scenario** supplies demonstration inputs, local state and simulated services.
|
|
25
|
+
A component scenario may compose several public components and development review helpers
|
|
26
|
+
to demonstrate its subject. That does not turn its fixture or variant selectors
|
|
27
|
+
into production API. Keep `covers` aligned with the scenario's subject layer.
|
|
28
|
+
|
|
29
|
+
Do not classify by import count, JSX element count, names or file length. Review
|
|
30
|
+
the capability and its consumer contract. Preserve valid APIs when tracing is
|
|
31
|
+
inconclusive; do not make an export private or add a product prop just to pass.
|
|
32
|
+
|
|
33
|
+
## Demonstrate the production implementation
|
|
34
|
+
|
|
35
|
+
Product patterns are reusable application UI, not showcase replicas. A scenario
|
|
36
|
+
imports the same production owner and supplies fixtures through its public API;
|
|
37
|
+
the application supplies live data and callbacks to that API. Keep mock records,
|
|
38
|
+
demo selections and simulated services in development files. Legitimate defaults,
|
|
39
|
+
control labels and the pattern's own interaction logic remain with their owner.
|
|
40
|
+
|
|
41
|
+
Demo framing must not replace a product pattern's styling or behavior. Browser
|
|
42
|
+
checks should exercise both the demonstrated contract and relevant application
|
|
43
|
+
integration; a successful showcase does not establish production parity. A new
|
|
44
|
+
pattern need not already have an application consumer, and import counts cannot
|
|
45
|
+
detect a duplicated implementation. Existing import guards prevent fixture/test
|
|
46
|
+
dependencies; review and browser checks must assess hardcoded mock data and
|
|
47
|
+
unrepresentative rendering that source ownership alone cannot prove.
|
|
48
|
+
|
|
49
|
+
Review framing belongs in development-only `review/`, not production patterns.
|
|
50
|
+
Use the separate review entry and [review.md](review.md) for the migration and
|
|
51
|
+
build contract.
|
|
52
|
+
|
|
53
|
+
## Dependency contracts (blocking)
|
|
54
|
+
|
|
55
|
+
- Component runtime may depend on components, shared controllers and lower-level
|
|
56
|
+
helpers. It cannot depend on patterns or composition, including indirectly
|
|
57
|
+
through aliases, forwarding exports, helpers or literal dynamic imports.
|
|
58
|
+
Type-only pattern references do not introduce runtime dependencies.
|
|
59
|
+
- Patterns consume component symbols published by the configured public entry,
|
|
60
|
+
including intentional public types. A relative import of a public symbol is
|
|
61
|
+
allowed inside the ADS; requiring a self-import through the barrel can create
|
|
62
|
+
cycles. Private state, class blocks and implementation helpers stay internal.
|
|
63
|
+
A public component may use its own private implementation dependencies.
|
|
64
|
+
- Runtime code cannot import review implementations/types/styles, scenarios, fixtures or checks, including through
|
|
65
|
+
forwarding exports. Scenarios also cannot import application source.
|
|
66
|
+
- ADS implementations receive application data/effects through inputs and
|
|
67
|
+
callbacks. Re-exporting application stores, routers or services from a helper
|
|
68
|
+
does not make them part of the ADS.
|
|
69
|
+
|
|
70
|
+
For example, `components/Control → shared/helper → patterns/Navigation` fails
|
|
71
|
+
`component-dependency`. Extract a lower-level capability/controller, or move the
|
|
72
|
+
arrangement to a pattern. `patterns/Navigation → components/Control` is valid
|
|
73
|
+
when `Control` is public. Replacing that import with its private state helper
|
|
74
|
+
fails `component-contract` even through a forwarding module.
|
|
75
|
+
|
|
76
|
+
## Styling contracts (blocking when ownership is established)
|
|
77
|
+
|
|
78
|
+
Components own their control states and appearance. Patterns own arrangement,
|
|
79
|
+
spacing and their own presentation. Use typed component props/variants, an
|
|
80
|
+
intentional public class helper/hook, or registered non-state customization
|
|
81
|
+
variables. Registration does not authorize overriding interaction policy.
|
|
82
|
+
|
|
83
|
+
```tsx
|
|
84
|
+
// Component-owned implementation; the marker is not a consumer styling API.
|
|
85
|
+
function Control() {
|
|
86
|
+
return <button><span className="control-private-label">Label</span></button>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Pattern: rejected because it styles the component's private rendered part.
|
|
90
|
+
const wrong = css`
|
|
91
|
+
& .control-private-label { color: var(--text-muted); }
|
|
92
|
+
`;
|
|
93
|
+
|
|
94
|
+
// Pattern: its own arrangement remains valid.
|
|
95
|
+
const layout = css`
|
|
96
|
+
display: flex;
|
|
97
|
+
gap: var(--space-4);
|
|
98
|
+
`;
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The source guard matches component-emitted class names and unambiguous literal
|
|
102
|
+
data markers to the selector's styled subject, including `:is()`/`:where()`.
|
|
103
|
+
It also rejects reads of known private component variables. It does not treat
|
|
104
|
+
an ancestor predicate or `:has()` query as styling that ancestor/descendant:
|
|
105
|
+
`[data-layout="expanded"] > & { gap: var(--space-4); }` changes the pattern's
|
|
106
|
+
own arrangement. Public hooks and registered non-state variables remain valid.
|
|
107
|
+
Token definitions, namespaces and writes retain their existing guards.
|
|
108
|
+
|
|
109
|
+
Opaque/computed imports, generated selectors, ambiguous shared markers, forwarded
|
|
110
|
+
consumer classes and arbitrary service abstractions are not a complete static
|
|
111
|
+
proof. Passing source checks does not prove that every visual override is safe.
|
|
112
|
+
Review the real rendered states, computed styles, keyboard interactions and
|
|
113
|
+
responsive layouts; use registered browser checks for promised behavior.
|
|
114
|
+
|
|
115
|
+
## Refactoring an owner
|
|
116
|
+
|
|
117
|
+
Move runtime source, its scenarios/fixtures and guides together; update public
|
|
118
|
+
exports, dependent imports and documented scenario IDs. Keep callers on the
|
|
119
|
+
same intentional public API. Navigation stays in an application callback or a
|
|
120
|
+
normal link supplied by the consumer, rather than a hidden router mutation in
|
|
121
|
+
the pattern. Validate the complete migration with project checks, product and
|
|
122
|
+
preview builds, and behavior tests. An existing guard rejection is not permission
|
|
123
|
+
to write around the guarded editing workflow.
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Development-only review UI
|
|
2
|
+
|
|
3
|
+
Read this when creating or migrating demo frames, preview sizing helpers, token
|
|
4
|
+
renderers or demonstration controls. MCP serves it as `ads://guides/review`.
|
|
5
|
+
|
|
6
|
+
## Responsibilities and boundaries
|
|
7
|
+
|
|
8
|
+
Components provide cohesive production capabilities. Patterns provide reusable
|
|
9
|
+
application structures/workflows. Both receive variable application data and
|
|
10
|
+
callbacks through their APIs. Their scenarios import the same implementation
|
|
11
|
+
used by the application and supply fixtures; do not create a second implementation
|
|
12
|
+
that only looks correct in the showcase.
|
|
13
|
+
|
|
14
|
+
`design-system/review/` owns development-only presentation: example frames,
|
|
15
|
+
arrangement, demonstration controls, token sample renderers and their styles.
|
|
16
|
+
It remains project-owned. The MCP host shell stays generic; review code executes
|
|
17
|
+
inside the preview App with the project's framework, foundation, fonts and styles.
|
|
18
|
+
Nothing depends on particular helper names or an imported component library.
|
|
19
|
+
|
|
20
|
+
The development entry defaults to `review/index.ts` relative to `dsDir`; configure
|
|
21
|
+
`reviewEntry` for another script inside `review/`. Its import alias is the
|
|
22
|
+
configured `publicAlias` plus `/review`, normally `@ds/review`. Discover the actual
|
|
23
|
+
entry/alias through `ads_status.project.review`. Export helpers there, never from
|
|
24
|
+
the production public barrel. Review implementations, types and styles must not
|
|
25
|
+
enter application, component, pattern or controller dependencies. Tests, fixtures,
|
|
26
|
+
scenarios and preview mounts can consume review code.
|
|
27
|
+
|
|
28
|
+
Review helpers can own Linaria presentation, local state and demo controls. Normal
|
|
29
|
+
style-token, variable ownership, measured-style-write and application separation
|
|
30
|
+
gates still apply. They consume deliberate public component/pattern/controller
|
|
31
|
+
contracts and may reuse local foundation helpers. They cannot target private
|
|
32
|
+
component styles or replace real product behavior. Keep one-off fixture data in
|
|
33
|
+
the scenario; a helper need not become a product API to frame an example.
|
|
34
|
+
|
|
35
|
+
## Scenario and token contracts
|
|
36
|
+
|
|
37
|
+
```tsx
|
|
38
|
+
import { Panel } from "@ds";
|
|
39
|
+
import { ReviewFrame } from "@ds/review";
|
|
40
|
+
import { fixture } from "./Panel.fixture";
|
|
41
|
+
|
|
42
|
+
export const scenario = { title: "Panel", covers: ["Panel"] };
|
|
43
|
+
export default function Scenario() {
|
|
44
|
+
return <ReviewFrame><Panel {...fixture} /></ReviewFrame>;
|
|
45
|
+
}
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
The names above are illustrative. `covers` names the production subjects actually
|
|
49
|
+
demonstrated, not every review wrapper. A review export has no product catalogue
|
|
50
|
+
entry and needs no standalone catalogue scenario. Keep review unit tests/specs
|
|
51
|
+
beside their owner; optional behavior contracts can verify review guarantees
|
|
52
|
+
through an existing real scenario. They establish no production coverage.
|
|
53
|
+
|
|
54
|
+
A token scenario may name its real review renderer in `covers`, import it from
|
|
55
|
+
the development entry, declare `samples: fixture.samples`, and pass those samples
|
|
56
|
+
into the renderer. Existing real product patterns may also serve as token UI.
|
|
57
|
+
Empty covers, unused samples, fabricated tokens and plain value lists remain
|
|
58
|
+
invalid. The rendered App must still pass token-binding checks. See
|
|
59
|
+
[scenarios.md](scenarios.md) for the full token/render contract.
|
|
60
|
+
|
|
61
|
+
Review class helpers can supply source-proven Linaria classes to a scenario.
|
|
62
|
+
Arbitrary scenario classes/styles remain forbidden. A review wrapper's own native
|
|
63
|
+
controls do not authorize arbitrary native controls in its consuming scenario;
|
|
64
|
+
slot/selector exceptions still need actual production-owner evidence. Keep
|
|
65
|
+
production and review export names distinct where `covers` would be ambiguous.
|
|
66
|
+
|
|
67
|
+
## Build and guarded edits
|
|
68
|
+
|
|
69
|
+
The normal preview plugin registers the exact review alias before broader product
|
|
70
|
+
aliases. Configure the same alias in project TypeScript and any independent
|
|
71
|
+
preview/dev harness. In an ordered bundler alias map, put `@ds/review` before
|
|
72
|
+
`@ds`; the broad `@ds` mapping must not resolve it as `index.ts/review`.
|
|
73
|
+
Production entrypoints never import the development entry. Inspect the product
|
|
74
|
+
bundle for review modules/CSS; passing static checks does not prove bundle parity.
|
|
75
|
+
|
|
76
|
+
Use `ads_units` for `review/<Owner>` units. Scenarios, local specs and helper files
|
|
77
|
+
follow ordinary flat/directory ownership. A review patch may update only its own
|
|
78
|
+
re-exports in the development entry and its own documented style boundaries.
|
|
79
|
+
Product owners retain the separate production entry companion. Use prepared
|
|
80
|
+
patches/dry runs and coupled transactions as described in
|
|
81
|
+
[guarded-editing.md](guarded-editing.md); do not bypass a rejected mutation.
|
|
82
|
+
|
|
83
|
+
For an existing project, identify helpers by responsibility and actual consumers,
|
|
84
|
+
not names or the number of imported components. Preserve real application patterns.
|
|
85
|
+
Move only demo helpers, their styles and relevant guides to `review/`; update
|
|
86
|
+
scenario/preview imports, style-boundary paths, TypeScript/bundler aliases and
|
|
87
|
+
links. Remove their production exports and obsolete standalone product scenarios.
|
|
88
|
+
Do not copy old example fixtures or export demonstration controls as product UI.
|
|
89
|
+
|
|
90
|
+
Upgrade ADS before migrating a consuming project: older versions do not know the
|
|
91
|
+
separate review entry. For staged guarded edits, first add distinctly named review
|
|
92
|
+
exports, migrate consumers in passing owner patches, then remove the old pattern
|
|
93
|
+
exports/owners once unused. Required coupled changes use a transaction. Check
|
|
94
|
+
source gates, typecheck, preview and production builds, real browser rendering,
|
|
95
|
+
registered behaviors and token bindings. A source move alone establishes none
|
|
96
|
+
of those rendered guarantees.
|
|
@@ -34,10 +34,31 @@ do not add scenario `preventDefault` handlers merely to hide it. Explicit App
|
|
|
34
34
|
bases retain their declared behavior and CSP requirements; external hosts own
|
|
35
35
|
their document-loading policy. Verify link activation as well as rendering.
|
|
36
36
|
|
|
37
|
+
## Forms inside Apps
|
|
38
|
+
|
|
39
|
+
Use real form semantics and the runtime owner's intended submit handler. ADS's
|
|
40
|
+
showcase and diagnostic explorer permit `allow-forms` at every iframe layer,
|
|
41
|
+
including legacy widgets. Browser constraint validation, Submit clicks, Enter and
|
|
42
|
+
`requestSubmit()` can reach the normal submit path. Client-side forms call
|
|
43
|
+
`preventDefault()` in their submit handlers; the host does not synthesize clicks
|
|
44
|
+
or submit events. Native action requests are blocked by CSP `form-action 'none'`.
|
|
45
|
+
Submit effects that use tools or resources still follow the MCP bridge.
|
|
46
|
+
|
|
47
|
+
If a click fires but the `submit` handler never runs, inspect the browser's sandbox
|
|
48
|
+
diagnostic and every ancestor iframe's permissions. Distinguish that host problem
|
|
49
|
+
from native constraint validation legitimately preventing submission. Do not add
|
|
50
|
+
scenario click interception to disguise a missing sandbox permission. External
|
|
51
|
+
MCP hosts control their own sandbox/CSP policy.
|
|
52
|
+
|
|
53
|
+
Verify an invalid submit, a valid submit, implicit Enter submission and the promised
|
|
54
|
+
state effects. A working Next button is not evidence that form submission works.
|
|
55
|
+
Keep native action navigation separate from client-side submission behavior;
|
|
56
|
+
`allow-forms` does not grant host DOM access or top-level navigation.
|
|
57
|
+
|
|
37
58
|
## Owner-local token previews load their own definitions
|
|
38
59
|
|
|
39
60
|
Keep the registered token sheet imported by its runtime owner for product use.
|
|
40
|
-
A colocated `kind: "tokens"` scenario may render only the
|
|
61
|
+
A colocated `kind: "tokens"` scenario may render only the development-only review renderer
|
|
41
62
|
with its fixture samples. Discovery adds the registered owner sheet to the
|
|
42
63
|
generated record's `stylesheets`; `previewEntriesPlugin` imports those paths
|
|
43
64
|
into the token preview independently of the runtime component. Custom builders
|
|
@@ -91,7 +112,7 @@ These examples describe verification mistakes, not component-specific rules:
|
|
|
91
112
|
|
|
92
113
|
| Situation | Do not | Do instead |
|
|
93
114
|
|-----------|--------|------------|
|
|
94
|
-
| A reference example sets `width: 15rem`, while the native popup uses its default width. | Remove the reference width and claim the original example now matches. | Keep the baseline intact. Reproduce demo sizing through the project's
|
|
115
|
+
| A reference example sets `width: 15rem`, while the native popup uses its default width. | Remove the reference width and claim the original example now matches. | Keep the baseline intact. Reproduce demo sizing through the project's development-only review helper or supported style helper. If a different native size is intentional, document it. Label any additional normalized comparison separately. |
|
|
95
116
|
| Static tracing cannot prove that a public clear control renders when a value is selected. | Remove its export, drop its required coverage, or change runtime behavior merely to pass the gate. | Preserve the intended public API. Try a meaningful selected-state fixture or another real scenario. If valid rendering remains untraceable, report the tooling limitation with a small reproduction. |
|
|
96
117
|
| A screenshot shows the clear control in a selected state. | Treat its presence as evidence that clearing works. | Add a browser check: start selected, activate Clear, assert the value and selection are empty, verify the control's resulting visibility and documented focus behavior, then select again. |
|
|
97
118
|
|
|
@@ -207,6 +228,6 @@ An `exercises` entry records behavioral use; it never grants styling permission.
|
|
|
207
228
|
Keep helper signatures compatible with the product API. Fix ownership/provenance
|
|
208
229
|
in the runtime owner when genuinely wrong; retain intentional public APIs when
|
|
209
230
|
reporting an unsupported trace. Demo-only sizing, framing and arrangement still
|
|
210
|
-
belong in
|
|
231
|
+
belong in development-only review helpers. A style helper is not permission to
|
|
211
232
|
author a second design in the scenario. For cascade risks and extraction issues,
|
|
212
233
|
read [atomic-styles.md](atomic-styles.md).
|