@reqvire-org/ads 0.3.7 → 0.3.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/dist/atomic-composition.mjs +85 -54
  2. package/dist/atomic-trace.mjs +92 -0
  3. package/dist/bootstrap-ds/scripts/build-mcp-previews.mjs +2 -1
  4. package/dist/bootstrap-ds/vite.config.ts +2 -1
  5. package/dist/check.mjs +14 -3
  6. package/dist/linaria/atomic-css.cjs +2 -0
  7. package/dist/linaria/atomic-styled.cjs +2 -0
  8. package/dist/linaria/preserve-values.cjs +32 -0
  9. package/dist/linaria.d.mts +1 -0
  10. package/dist/linaria.mjs +9 -0
  11. package/dist/mcp/preview-widget.bundle.js +1 -1
  12. package/dist/mcp/resource-loader.js +5 -2
  13. package/dist/mcp/server.mjs +4 -2
  14. package/dist/scenario-trace.mjs +149 -0
  15. package/dist/scenarios-compiler.mjs +2 -1
  16. package/dist/scenarios.mjs +22 -6
  17. package/dist/skills/ads-break-ui/SKILL.md +10 -2
  18. package/dist/skills/ads-break-ui/references/atomic-styles.md +122 -7
  19. package/dist/skills/ads-break-ui/references/scenarios.md +83 -0
  20. package/dist/skills/ads-designer/SKILL.md +13 -3
  21. package/dist/skills/ads-designer/assets/bootstrap-ds/scripts/build-mcp-previews.mjs +2 -1
  22. package/dist/skills/ads-designer/assets/bootstrap-ds/vite.config.ts +2 -1
  23. package/dist/skills/ads-designer/references/atomic-styles.md +122 -7
  24. package/dist/skills/ads-designer/references/scenarios.md +83 -0
  25. package/dist/skills/ads-import/SKILL.md +10 -2
  26. package/dist/skills/ads-import/references/atomic-styles.md +122 -7
  27. package/dist/skills/ads-import/references/scenarios.md +83 -0
  28. package/dist/skills/ads-migrate/SKILL.md +10 -2
  29. package/dist/skills/ads-migrate/references/atomic-styles.md +122 -7
  30. package/dist/skills/ads-migrate/references/scenarios.md +83 -0
  31. package/dist/skills/ads-motion/SKILL.md +10 -2
  32. package/dist/skills/ads-motion/references/atomic-styles.md +122 -7
  33. package/dist/skills/ads-motion/references/scenarios.md +83 -0
  34. package/dist/source-values.mjs +247 -0
  35. package/dist/verification/browser.mjs +21 -12
  36. package/dist/verification/playwright-reporter.cjs +6 -2
  37. package/dist/verification.mjs +8 -3
  38. package/docs/ADS.md +18 -5
  39. package/docs/ARCHITECTURE.md +19 -0
  40. package/docs/BEHAVIORS.md +15 -1
  41. package/docs/CHANGELOG.md +28 -1
  42. package/docs/MCP.md +18 -0
  43. package/package.json +10 -1
@@ -1,7 +1,9 @@
1
1
  import path from "node:path";
2
2
  import ts from "typescript";
3
3
  import postcss from "postcss";
4
+ import valueParser from "postcss-value-parser";
4
5
  import { isInside } from "./config.mjs";
6
+ import { createAtomicTrace } from "./atomic-trace.mjs";
5
7
 
6
8
  export const atomicStyleGuide = "ads://guides/atomic-styles";
7
9
 
@@ -62,6 +64,67 @@ function overlaps(a, b) {
62
64
  return intersects(longhands.get(a) ?? [a], longhands.get(b) ?? [b]);
63
65
  }
64
66
 
67
+ const modes = ["horizontal-tb", "vertical-rl", "vertical-lr"].flatMap(mode => ["ltr", "rtl"].map(direction => ({ mode, direction })));
68
+ function physicalSlots(property, { mode, direction }) {
69
+ const value = footprint(property);
70
+ if (!value) return null;
71
+ if (value.whole) return sides;
72
+ const horizontal = mode === "horizontal-tb";
73
+ const axes = {
74
+ "block-start": horizontal ? "top" : mode === "vertical-rl" ? "right" : "left",
75
+ "block-end": horizontal ? "bottom" : mode === "vertical-rl" ? "left" : "right",
76
+ "inline-start": horizontal ? direction === "ltr" ? "left" : "right" : direction === "ltr" ? "top" : "bottom",
77
+ "inline-end": horizontal ? direction === "ltr" ? "right" : "left" : direction === "ltr" ? "bottom" : "top",
78
+ "inline-size": horizontal ? "width" : "height", "block-size": horizontal ? "height" : "width",
79
+ inline: horizontal ? "x" : "y", block: horizontal ? "y" : "x",
80
+ };
81
+ return value.slots.map(slot => value.family === "radius" && slot ? (/^(start|end)-/.test(slot) ? [axes[`block-${slot.split("-")[0]}`], axes[`inline-${slot.split("-")[1]}`]] : slot.split("-")).sort().join("-") : axes[slot] ?? slot);
82
+ }
83
+ function writingModeDependent(a, b) {
84
+ const first = footprint(a), second = footprint(b);
85
+ if (!first || !second || first.family !== second.family || first.whole || second.whole || first.logical === second.logical) return false;
86
+ const matches = modes.map(mode => intersects(physicalSlots(a, mode), physicalSlots(b, mode)));
87
+ return matches.some(Boolean) && !matches.every(Boolean);
88
+ }
89
+ function words(value) {
90
+ const nodes = valueParser(value).nodes;
91
+ return nodes.every(node => ["word", "space", "comment"].includes(node.type)) ? nodes.filter(node => node.type === "word").map(node => node.value.toLowerCase()) : null;
92
+ }
93
+ function boxValues(entry, mode) {
94
+ const value = footprint(entry.property), parts = words(entry.value);
95
+ if (!value || !["padding", "margin", "scroll-padding", "scroll-margin", "inset"].includes(value.family) || !parts?.length || parts.length > 4 || parts.some(part => ["inherit", "initial", "unset", "revert", "revert-layer"].includes(part))) return null;
96
+ const slots = physicalSlots(entry.property, mode);
97
+ if (!value.whole && parts.length > slots.length) return null;
98
+ const expanded = value.whole ? [parts[0], parts[1] ?? parts[0], parts[2] ?? parts[0], parts[3] ?? parts[1] ?? parts[0]] : slots.map((_slot, index) => parts[index] ?? parts[0]);
99
+ return new Map(slots.map((slot, index) => [slot, expanded[index]]));
100
+ }
101
+ function equivalent(a, b) {
102
+ // Only suppress values whose overlap can be established from literal source.
103
+ // var(), calc(), inheritance and token aliases still need rendered review.
104
+ const first = footprint(a.property), second = footprint(b.property);
105
+ if (first && second && first.family === second.family && ["padding", "margin", "scroll-padding", "scroll-margin", "inset"].includes(first.family)) {
106
+ return modes.every(mode => {
107
+ const left = boxValues(a, mode), right = boxValues(b, mode);
108
+ return left && right && [...left].every(([slot, value]) => !right.has(slot) || right.get(slot) === value);
109
+ });
110
+ }
111
+ const flex = a.property === "flex" ? a : b.property === "flex" ? b : null;
112
+ const field = flex === a ? b : a;
113
+ if (flex && ["flex-grow", "flex-shrink", "flex-basis"].includes(field.property)) {
114
+ const parts = words(flex.value);
115
+ return parts?.length === 3 && /^\d+(?:\.\d+)?$/.test(parts[0]) && /^\d+(?:\.\d+)?$/.test(parts[1]) && /^(?:auto|0|\d+(?:\.\d+)?(?:px|rem|em|%))$/.test(parts[2]) && parts[["flex-grow", "flex-shrink", "flex-basis"].indexOf(field.property)] === field.value.trim().toLowerCase();
116
+ }
117
+ return false;
118
+ }
119
+
120
+ function valueModeDependent(a, b) {
121
+ const equal = modes.map(mode => {
122
+ const left = boxValues(a, mode), right = boxValues(b, mode);
123
+ return left && right ? [...left].every(([slot, value]) => !right.has(slot) || right.get(slot) === value) : null;
124
+ });
125
+ return !equal.includes(null) && equal.some(Boolean) && !equal.every(Boolean);
126
+ }
127
+
65
128
  function unwrap(node) {
66
129
  while (node && (ts.isParenthesizedExpression(node) || ts.isAsExpression(node) || ts.isSatisfiesExpression(node) || ts.isNonNullExpression(node))) node = node.expression;
67
130
  return node;
@@ -70,11 +133,6 @@ function unwrap(node) {
70
133
  /** Review only: valid CSS can intentionally overlap. Never rewrite project CSS. */
71
134
  export function createAtomicCompositionReview(program, options) {
72
135
  const checker = program.getTypeChecker(), templates = new Map();
73
- function declaration(node) {
74
- let symbol = checker.getSymbolAtLocation(ts.isPropertyAccessExpression(node) ? node.name : node);
75
- if (symbol?.flags & ts.SymbolFlags.Alias) symbol = checker.getAliasedSymbol(symbol);
76
- return symbol?.valueDeclaration;
77
- }
78
136
  function imported(node, names, modules, seen = new Set()) {
79
137
  node = unwrap(node);
80
138
  if (!node || seen.has(node)) return false;
@@ -117,67 +175,40 @@ export function createAtomicCompositionReview(program, options) {
117
175
  } catch { /* The core CSS syntax diagnostic owns malformed templates. */ }
118
176
  return result;
119
177
  }
120
- function object(node, seen = new Set()) {
121
- node = unwrap(node);
122
- if (!node || seen.has(node)) return null;
123
- if (ts.isObjectLiteralExpression(node) || ts.isArrayLiteralExpression(node)) return node;
124
- const owner = declaration(node);
125
- return owner?.initializer ? object(owner.initializer, new Set([...seen, node])) : null;
126
- }
127
- function styles(node, seen = new Set()) {
128
- node = unwrap(node);
129
- if (!node || seen.has(node)) return [];
130
- seen = new Set([...seen, node]);
131
- if (ts.isTaggedTemplateExpression(node) && isAtomic(node.tag)) return entries(node);
132
- if (ts.isConditionalExpression(node)) return [...styles(node.whenTrue, seen), ...styles(node.whenFalse, seen)];
133
- if (ts.isBinaryExpression(node) && [ts.SyntaxKind.AmpersandAmpersandToken, ts.SyntaxKind.BarBarToken, ts.SyntaxKind.QuestionQuestionToken].includes(node.operatorToken.kind)) return [...(node.operatorToken.kind === ts.SyntaxKind.AmpersandAmpersandToken ? [] : styles(node.left, seen)), ...styles(node.right, seen)];
134
- if (ts.isCallExpression(node) && isCx(node.expression)) return node.arguments.flatMap(argument => styles(argument, seen));
135
- if (ts.isSpreadElement(node)) {
136
- const value = object(node.expression);
137
- return value && ts.isArrayLiteralExpression(value) ? value.elements.flatMap(element => styles(element, seen)) : [];
138
- }
139
- const owner = declaration(node);
140
- if (owner?.initializer) return styles(owner.initializer, seen);
141
- if (ts.isElementAccessExpression(node) || ts.isPropertyAccessExpression(node)) {
142
- const value = object(node.expression);
143
- if (!value || !ts.isObjectLiteralExpression(value)) return [];
144
- const key = ts.isPropertyAccessExpression(node) ? node.name.text : node.argumentExpression && ts.isStringLiteralLike(node.argumentExpression) ? node.argumentExpression.text : null;
145
- return value.properties.flatMap(property => {
146
- const name = property.name && (ts.isIdentifier(property.name) || ts.isStringLiteralLike(property.name)) ? property.name.text : null;
147
- return key !== null && name !== key ? [] : ts.isPropertyAssignment(property) ? styles(property.initializer, seen) : ts.isShorthandPropertyAssignment(property) ? styles(property.name, seen) : [];
148
- });
149
- }
150
- return [];
151
- }
178
+ const trace = createAtomicTrace(program, options, { entries, isAtomic, isCx });
152
179
  return source => {
153
- const warnings = [], seen = new Set();
154
- function compare(first, second, location) {
180
+ const warnings = new Map(), limits = new Map();
181
+ const relative = entry => ({ ...entry, file: path.relative(options.root, entry.file).replaceAll("\\", "/") });
182
+ function compare(first, second, location, context = {}) {
155
183
  for (const a of first) for (const b of second) {
156
- if (a === b || a.selectors !== b.selectors || a.conditions !== b.conditions || a.important !== b.important || a.value === "0" && b.value === "0" || !overlaps(a.property, b.property)) continue;
184
+ if (a === b || a.selectors !== b.selectors || a.conditions !== b.conditions || a.important !== b.important || !overlaps(a.property, b.property) || equivalent(a, b)) continue;
157
185
  const key = JSON.stringify([a, b].map(entry => [entry.file, entry.line, entry.column]).sort());
158
- if (seen.has(key)) continue;
159
- seen.add(key);
186
+ const composition = [...new Map([...(context.path ?? []), ...(a.via ?? []), ...(b.via ?? [])].map(entry => [JSON.stringify(entry), relative(entry)])).values()].slice(0, 16);
187
+ const previous = warnings.get(key);
188
+ if (previous && (previous.target || !context.target) && previous.composition.length >= composition.length) continue;
160
189
  const position = source.getLineAndCharacterOfPosition(location.getStart(source));
161
- warnings.push({ rule: "atomic-composition-review", severity: "warning", line: position.line + 1, column: position.character + 1,
162
- message: `Atomic declarations ${a.property}: ${previewValue(a.value)} and ${b.property}: ${previewValue(b.value)} may overlap without matching cx merge keys. Their winner can depend on extraction order, specificity or writing mode. Use consistent property names/granularity across the base and variants, compose with Linaria cx, and verify computed styles in the built preview. Review all affected variants; valid tokens do not establish cascade correctness. See ${atomicStyleGuide}.`,
163
- guidanceUri: atomicStyleGuide, related: [a, b].map(({ selectors: _selectors, conditions: _conditions, important: _important, ...entry }) => ({...entry,value:previewValue(entry.value)})) });
190
+ const dependent = writingModeDependent(a.property, b.property) || valueModeDependent(a, b);
191
+ warnings.set(key, { rule: "atomic-composition-review", severity: "warning", line: position.line + 1, column: position.character + 1,
192
+ message: `Atomic declarations ${a.property}: ${previewValue(a.value)} and ${b.property}: ${previewValue(b.value)} may overlap without matching cx merge keys.${context.target ? ` Source tracing reaches the same <${context.target.name}> className.` : " This is a source composition risk, not a confirmed rendered conflict."}${dependent ? " The overlap depends on writing mode or direction." : ""} Their winner can depend on extraction order, specificity or writing mode. Use consistent property names/granularity across the base and variants, compose with Linaria cx, and verify computed styles in the built preview. Review all affected variants; valid tokens do not establish cascade correctness. See ${atomicStyleGuide}.`,
193
+ guidanceUri: atomicStyleGuide, writingModeDependent: dependent, composition, ...(context.target ? { target: relative(context.target) } : {}), ...(context.limits?.length ? { analysisLimits: [...new Set(context.limits)].slice(0, 5) } : {}),
194
+ related: [a, b].map(({ selectors: _selectors, conditions: _conditions, important: _important, via: _via, ...entry }) => ({...entry,value:previewValue(entry.value)})) });
164
195
  }
165
196
  }
197
+ function limit(node, reasons, composition) {
198
+ const key = reasons.join("\n");
199
+ if (limits.has(key) || limits.size >= 5) return;
200
+ const position = source.getLineAndCharacterOfPosition(node.getStart(source));
201
+ limits.set(key, { rule: "atomic-composition-limit", severity: "info", line: position.line + 1, column: position.character + 1,
202
+ message: `Atomic source review is incomplete: ${[...new Set(reasons)].slice(0, 3).join(" ")} Review the actual DOM classes and computed styles in a built scenario. An unresolved path is not evidence that composition is safe. See ${atomicStyleGuide}.`, guidanceUri: atomicStyleGuide, composition: composition.slice(0, 16).map(relative) });
203
+ }
166
204
  function visit(node) {
167
205
  if (ts.isTaggedTemplateExpression(node) && isAtomic(node.tag)) {
168
206
  const declarations = entries(node);
169
207
  for (let index = 0; index < declarations.length; index++) compare([declarations[index]], declarations.slice(index + 1), node);
170
208
  }
171
- if (ts.isCallExpression(node) && isCx(node.expression)) {
172
- const groups = node.arguments.flatMap(argument => {
173
- const value = ts.isSpreadElement(argument) ? object(argument.expression) : null;
174
- return value && ts.isArrayLiteralExpression(value) ? value.elements.map(element => styles(element)) : [styles(argument)];
175
- });
176
- for (let index = 0; index < groups.length; index++) for (let next = index + 1; next < groups.length; next++) compare(groups[index], groups[next], node);
177
- }
178
209
  ts.forEachChild(node, visit);
179
210
  }
180
- if (isInside(options.dsRoot, source.fileName)) visit(source);
181
- return warnings;
211
+ if (isInside(options.dsRoot, source.fileName)) { visit(source); trace(source, { compare, limit }); }
212
+ return [...warnings.values(), ...limits.values()];
182
213
  };
183
214
  }
@@ -0,0 +1,92 @@
1
+ import ts from "typescript";
2
+ import { createSourceValues, reference, literal, unknown, isFunction } from "./source-values.mjs";
3
+
4
+ /** Bounded symbolic composition review, sharing source evaluation with scenarios. */
5
+ export function createAtomicTrace(program, options, { entries, isAtomic, isCx }) {
6
+ const { state, next, at, resolve, get, arrays, returns } = createSourceValues(program, options, { preserveCall: isCx });
7
+ return function review(source, { compare, limit }) {
8
+ const maxBranches = 32;
9
+ const unique = branches => [...new Map(branches.map(branch => [JSON.stringify(branch.map(entry => [entry.file, entry.line, entry.column])), branch])).values()];
10
+ function styles(input, context) {
11
+ const value = resolve(input, next(context));
12
+ context = value.context ?? input.context ?? context;
13
+ if (value.kind === "unknown") return { branches: [[]], limits: value.reason ? [value.reason] : [] };
14
+ if (value.kind === "union") {
15
+ const results = value.values.slice(0, maxBranches).map(item => styles(item, next(context)));
16
+ const branches = unique(results.flatMap(item => item.branches));
17
+ return { branches: branches.slice(0, maxBranches), limits: [...results.flatMap(item => item.limits), ...(value.values.length > maxBranches || branches.length > maxBranches ? ["Atomic source tracing reached its alternative limit."] : [])] };
18
+ }
19
+ if (value.kind !== "node") return { branches: [[]], limits: [] };
20
+ const { node, env, via } = value;
21
+ if (ts.isTaggedTemplateExpression(node) && isAtomic(node.tag)) return { branches: [entries(node).map(entry => ({ ...entry, via }))], limits: [] };
22
+ if (ts.isCallExpression(node) && isCx(node.expression)) {
23
+ const inputs = node.arguments.flatMap(argument => ts.isSpreadElement(argument) ? arrays(reference(argument.expression, env, via), context) ?? [unknown("A dynamic class argument spread could not be traced.")] : [reference(argument, env, via)]);
24
+ let branches = [[]]; const limits = [];
25
+ for (const argument of inputs) {
26
+ const result = styles(argument, next(context));
27
+ limits.push(...result.limits);
28
+ const combined = new Map(); let truncated = false;
29
+ for (const first of branches) for (const second of result.branches) {
30
+ // cx replaces exact property/selector/condition keys; shorthand
31
+ // expansion remains the CSS overlap review's responsibility.
32
+ const merged = new Map([...first, ...second.map(entry => ({ ...entry, via: [...via, ...(entry.via ?? [])] }))].map(entry => [JSON.stringify([entry.property, entry.selectors, entry.conditions]), entry]));
33
+ const branch = [...merged.values()], key = JSON.stringify(branch.map(entry => [entry.file, entry.line, entry.column]));
34
+ if (!combined.has(key) && combined.size >= maxBranches) { truncated = true; continue; }
35
+ combined.set(key, branch);
36
+ }
37
+ if (truncated) limits.push("Atomic source tracing reached its alternative limit.");
38
+ branches = [...combined.values()];
39
+ }
40
+ return { branches, limits };
41
+ }
42
+ return { branches: [[]], limits: [] };
43
+ }
44
+ function inspect(value, node, context, target, path = []) {
45
+ const result = styles(value, context);
46
+ for (const branch of result.branches) for (let index = 0; index < branch.length; index++) compare([branch[index]], branch.slice(index + 1), node, { target, path, limits: result.limits });
47
+ if (result.limits.length && (result.branches.some(branch => branch.length) || ts.isCallExpression(node) && node.arguments.some(argument => styles(reference(argument), next(context)).branches.some(branch => branch.length)))) limit(node, result.limits, path);
48
+ return result;
49
+ }
50
+ function props(node, env, via) {
51
+ return { kind: "object", layers: node.attributes.properties.map(attribute => ts.isJsxSpreadAttribute(attribute) ? reference(attribute.expression, env, via) : new Map([[attribute.name.text, attribute.initializer ? ts.isJsxExpression(attribute.initializer) ? reference(attribute.initializer.expression, env, via) : reference(attribute.initializer, env, via) : literal(true)]])) };
52
+ }
53
+ function render(input, location, context, path = [], pendingStyles = false) {
54
+ const value = resolve(input, next(context));
55
+ context = value.context ?? input.context ?? context;
56
+ if (value.kind === "union") { for (const item of value.values.slice(0, maxBranches)) render(item, location, next(context), path, pendingStyles); return; }
57
+ if (value.kind !== "node") { if (value.reason && pendingStyles) limit(location, [value.reason], path); return; }
58
+ const { node, env, via } = value;
59
+ if (ts.isJsxExpression(node)) { render(reference(node.expression, env, via), location, next(context), path, pendingStyles); return; }
60
+ if (ts.isJsxFragment(node)) { for (const child of node.children) render(reference(child, env, via), location, next(context), path); return; }
61
+ if (ts.isArrayLiteralExpression(node)) { for (const child of node.elements) render(reference(child, env, via), location, next(context), path); return; }
62
+ if (!ts.isJsxElement(node) && !ts.isJsxSelfClosingElement(node)) return;
63
+ const opening = ts.isJsxElement(node) ? node.openingElement : node;
64
+ const attributes = props(opening, env, via), classValue = get(attributes, "className", next(context));
65
+ const tag = opening.tagName.getText();
66
+ if (/^[a-z]/.test(tag) && !tag.includes(".")) {
67
+ inspect(classValue, location, context, at(opening, "element", tag), [...path, ...via]);
68
+ if (ts.isJsxElement(node)) for (const child of node.children) render(reference(child, env, via), location, next(context), path);
69
+ return;
70
+ }
71
+ function componentTarget(component) {
72
+ if (component.kind === "union") { for (const alternative of component.values.slice(0, maxBranches)) componentTarget(resolve(alternative, next(context))); return; }
73
+ if (component.kind === "literal" && typeof component.value === "string") {
74
+ inspect(classValue, location, context, at(opening, "element", component.value), [...path, ...via]);
75
+ } else if (component.kind === "node" && isFunction(component.node)) {
76
+ const passed = pendingStyles || styles(classValue, context).branches.some(branch => branch.length);
77
+ for (const output of returns(component, [attributes], next(context), "component")) render(output, location, output.context ?? next(context), [...path, at(opening, "component", tag)], passed);
78
+ } else {
79
+ const passed = styles(classValue, context);
80
+ if (passed.branches.some(branch => branch.length)) limit(location, [`Component ${tag.slice(0, 80)} could not be traced to its DOM className target.`, ...passed.limits], path);
81
+ }
82
+ }
83
+ componentTarget(resolve(reference(opening.tagName, env, via), next(context)));
84
+ }
85
+ function visit(node) {
86
+ if (ts.isCallExpression(node)) inspect(reference(node), node, state());
87
+ if (ts.isJsxElement(node) || ts.isJsxSelfClosingElement(node)) render(reference(node), node, state());
88
+ ts.forEachChild(node, visit);
89
+ }
90
+ visit(source);
91
+ };
92
+ }
@@ -4,6 +4,7 @@ import { execFile } from "node:child_process";
4
4
  import { promisify } from "node:util";
5
5
  import { build } from "vite";
6
6
  import wyw from "@wyw-in-js/vite";
7
+ import { linariaTagResolver } from "@reqvire-org/ads/linaria";
7
8
  import react from "@vitejs/plugin-react";
8
9
  import { runScenarioCompiler } from "@reqvire-org/ads/scenarios/compiler";
9
10
  import { createPreviewResources, previewEntriesPlugin } from "@reqvire-org/ads/scenarios/vite";
@@ -22,7 +23,7 @@ await runScenarioCompiler(root, {
22
23
  async buildGraph(entries, options, track) {
23
24
  const mount = path.join(options.dsRoot, "showcase/mcp/main.tsx");
24
25
  const output = await build({ configFile: false, root, base: "./", publicDir: false,
25
- plugins: [previewEntriesPlugin(entries.map(entry => ({ ...entry, root })), mount), wyw({ include: ["**/*.{ts,tsx}"], exclude: ["**/node_modules/**"] }), react(), track],
26
+ plugins: [previewEntriesPlugin(entries.map(entry => ({ ...entry, root })), mount), wyw({ tagResolver: linariaTagResolver, include: ["**/*.{ts,tsx}"], exclude: ["**/node_modules/**"] }), react(), track],
26
27
  resolve: { alias: { "@ds": path.join(options.dsRoot, options.config.publicEntry) } },
27
28
  worker: { format: "iife" },
28
29
  build: { write: false, cssCodeSplit: true, assetsInlineLimit: 0, modulePreload: false,
@@ -2,9 +2,10 @@ import path from "node:path";
2
2
  import { defineConfig } from "vite";
3
3
  import react from "@vitejs/plugin-react";
4
4
  import wyw from "@wyw-in-js/vite";
5
+ import { linariaTagResolver } from "@reqvire-org/ads/linaria";
5
6
 
6
7
  export default defineConfig({
7
- plugins: [wyw(), react()],
8
+ plugins: [wyw({ tagResolver: linariaTagResolver }), react()],
8
9
  resolve: {
9
10
  alias: { "@ds": path.resolve(import.meta.dirname, "design-system/index.ts") },
10
11
  },
package/dist/check.mjs CHANGED
@@ -20,6 +20,7 @@ import { numericCssUnit, tokenLengthLiteral } from "./css-values.mjs";
20
20
  import { foundationDeclaration, foundationSelector } from "./foundation.mjs";
21
21
  import { scopedTokens, scopedTokenOwner } from "./scoped-tokens.mjs";
22
22
  import { createAtomicCompositionReview } from "./atomic-composition.mjs";
23
+ import { createScenarioTrace } from "./scenario-trace.mjs";
23
24
 
24
25
  const scenarioLayoutGuidance = "Put demo-only sizing and framing in a public review/layout pattern. Keep required behavior in the runtime owner; add component props for deliberate product API needs.";
25
26
  const inlineStyleGuidance = `Own visual styles in component/pattern Linaria. ${scenarioLayoutGuidance} Reserve inlineStyleFiles for documented measured geometry, not demo layout.`;
@@ -52,13 +53,14 @@ export function checkProject(directory, { trackingRoot } = {}) {
52
53
  const program = ts.createProgram(scriptFiles, { ...compiler, paths: { ...compiler.paths, [config.publicAlias]: [path.join(dsRoot, config.publicEntry)] }, allowJs: true, noResolve: true, noLib: true, types: [], target: ts.ScriptTarget.Latest, module: ts.ModuleKind.ESNext, jsx: ts.JsxEmit.Preserve });
53
54
  const checker = program.getTypeChecker();
54
55
  const atomicCompositionReview = createAtomicCompositionReview(program, options);
56
+ const scenarioTrace = createScenarioTrace(program, options);
55
57
  function report(file, rule, message, line = 1, column = 1) {
56
58
  diagnostics.push({ file: path.relative(root, file).replaceAll("\\", "/"), rule, message, line, column });
57
59
  }
58
60
  inspectStyleBoundaries(options, program, report);
59
61
  inspectShowcasePages(options, program, resolveImport, report);
60
62
  inspectShowcaseCoverage(options, program, report);
61
- inspectScenarioCoverage(options, program, report, (file, rule, message) => advisories.push({ file: path.relative(root, file).replaceAll("\\", "/"), rule, message, line: 1, column: 1 }));
63
+ inspectScenarioCoverage(options, program, report, (file, rule, message) => advisories.push({ file: path.relative(root, file).replaceAll("\\", "/"), rule, message, line: 1, column: 1 }), scenarioTrace);
62
64
  const behaviors = inspectBehaviors(options, program);
63
65
  diagnostics.push(...behaviors.diagnostics);
64
66
  inspectControllers(options, program, report, behaviors.contracts);
@@ -162,6 +164,10 @@ export function checkProject(directory, { trackingRoot } = {}) {
162
164
  const position = source.getLineAndCharacterOfPosition(node.getStart(source));
163
165
  report(file, rule, message, position.line + 1, position.character + 1);
164
166
  }
167
+ function scenarioClass(node, expression, property = false) {
168
+ const proof = scenarioTrace.classValue(expression, property);
169
+ if (proof.present && !proof.ok) nodeReport(node, "showcase-class", `Scenarios define no custom class names. A public class helper must return runtime-owned Linaria styles; stable hooks authored in that owner may accompany real styles. Arbitrary strings, private styles and forwarded consumer classes do not qualify.${proof.reasons.length ? ` ${proof.reasons.join(" ")}` : ""} ${scenarioLayoutGuidance} See ads://guides/scenarios.`);
170
+ }
165
171
  if (!showcase && (isInside(path.join(dsRoot, "components"), file) || isInside(path.join(dsRoot, "patterns"), file))) inspectPublicProps(source, checker, nodeReport);
166
172
  function isLinariaTag(tag) {
167
173
  if (ts.isCallExpression(tag)) tag = tag.expression;
@@ -213,8 +219,10 @@ export function checkProject(directory, { trackingRoot } = {}) {
213
219
  if (property.name.text === "style" && (!inlineAllowed || showcase)) nodeReport(property, "no-inline-style", showcase ? `Scenarios define no inline styles. ${scenarioLayoutGuidance}` : inlineStyleGuidance);
214
220
  if (property.name.text === "style") inspectProperty(property.initializer);
215
221
  if (property.name.text === "className") inspectClass(property.initializer);
216
- if (showcase && property.name.text === "className") nodeReport(property, "showcase-class", `Scenarios define no custom class names. ${scenarioLayoutGuidance}`);
222
+ if (showcase && property.name.text === "className") scenarioClass(property, property.initializer);
217
223
  }
224
+ if (showcase && props && !ts.isObjectLiteralExpression(props)) scenarioClass(props, props, true);
225
+ if (showcase) for (const property of properties) if (ts.isSpreadAssignment(property)) scenarioClass(property, property.expression, true);
218
226
  if (showcase && tag && ts.isStringLiteralLike(tag)) {
219
227
  const role = properties.find(property => ts.isPropertyAssignment(property) && (ts.isIdentifier(property.name) || ts.isStringLiteralLike(property.name)) && property.name.text === "role")?.initializer;
220
228
  if (showcaseControls.has(tag.text) || role && ts.isStringLiteralLike(role) && widgetRoles.has(role.text)) nodeReport(node, "showcase-control", `Scenario controls must render public ADS components for actual interaction. ${scenarioLayoutGuidance}`);
@@ -230,7 +238,8 @@ export function checkProject(directory, { trackingRoot } = {}) {
230
238
  const roleValue = role && ts.isStringLiteralLike(role) ? role.text : role && ts.isJsxExpression(role) && role.expression && ts.isStringLiteralLike(role.expression) ? role.expression.text : null;
231
239
  if (showcaseControls.has(node.tagName.text) || widgetRoles.has(roleValue)) nodeReport(node, "showcase-control", `Scenario controls must render public ADS components for actual interaction. ${scenarioLayoutGuidance}`);
232
240
  }
233
- if (showcase && ts.isJsxAttribute(node) && ts.isIdentifier(node.name) && node.name.text === "className") nodeReport(node, "showcase-class", `Scenarios define no custom class names. ${scenarioLayoutGuidance}`);
241
+ if (showcase && ts.isJsxAttribute(node) && ts.isIdentifier(node.name) && node.name.text === "className") scenarioClass(node, node.initializer && ts.isJsxExpression(node.initializer) ? node.initializer.expression : node.initializer);
242
+ if (showcase && ts.isJsxSpreadAttribute(node)) scenarioClass(node, node.expression, true);
234
243
  let specifier;
235
244
  if ((ts.isImportDeclaration(node) || ts.isExportDeclaration(node)) && node.moduleSpecifier && ts.isStringLiteral(node.moduleSpecifier)) specifier = node.moduleSpecifier.text;
236
245
  if (ts.isCallExpression(node) && node.expression.kind === ts.SyntaxKind.ImportKeyword && node.arguments[0] && ts.isStringLiteral(node.arguments[0])) specifier = node.arguments[0].text;
@@ -262,5 +271,7 @@ export function checkProject(directory, { trackingRoot } = {}) {
262
271
  }
263
272
  visit(source);
264
273
  }
274
+ // Bounded clients should see actionable warnings before source-analysis limits.
275
+ advisories.sort((first, second) => Number(first.severity === "info") - Number(second.severity === "info"));
265
276
  return { ok: diagnostics.length === 0, diagnostics, ...(advisories.length ? { advisories } : {}) };
266
277
  }
@@ -0,0 +1,2 @@
1
+ const Processor = require("@linaria/atomic/processors/css").default;
2
+ exports.default = require("./preserve-values.cjs")(Processor);
@@ -0,0 +1,2 @@
1
+ const Processor = require("@linaria/atomic/processors/styled").default;
2
+ exports.default = require("./preserve-values.cjs")(Processor);
@@ -0,0 +1,32 @@
1
+ const { createHash } = require("node:crypto");
2
+ const postcss = require("postcss");
3
+
4
+ /** Stylis must not tokenize an unevaluated custom-property value as ordinary CSS.
5
+ * Keep the project's atomizer, selectors and priorities; restore opaque values
6
+ * after its nesting pass and before the bundler's CSS optimizer sees them. */
7
+ module.exports = function preserveCustomProperties(Processor) {
8
+ return class PreservedCustomProperties extends Processor {
9
+ extractRules(valueCache, cssText, loc) {
10
+ const source = postcss.parse(cssText);
11
+ const values = new Map();
12
+ source.walkDecls(declaration => {
13
+ if (!declaration.prop.startsWith("--")) return;
14
+ const value = declaration.raws.value?.raw ?? declaration.value;
15
+ const marker = `ads-preserved-${createHash("sha256").update(value).digest("hex")}`;
16
+ values.set(marker, value);
17
+ declaration.value = marker;
18
+ delete declaration.raws.value;
19
+ });
20
+ if (!values.size) return super.extractRules(valueCache, cssText, loc);
21
+ const rules = super.extractRules(valueCache, source.toString(), loc);
22
+ return Object.fromEntries(Object.entries(rules).map(([key, rule]) => {
23
+ const output = postcss.parse(rule.cssText);
24
+ output.walkDecls(declaration => {
25
+ if (declaration.prop.startsWith("--") && values.has(declaration.value)) declaration.value = values.get(declaration.value);
26
+ });
27
+ const restored = output.toString();
28
+ return [rule.atom ? restored : key, { ...rule, cssText: restored }];
29
+ }));
30
+ }
31
+ };
32
+ };
@@ -0,0 +1 @@
1
+ export function linariaTagResolver(source: string, tag: string): string | null;
@@ -0,0 +1,9 @@
1
+ import { fileURLToPath } from "node:url";
2
+
3
+ /** WyW's supported tag resolver; non-atomic imports retain their normal processor.
4
+ * Import this in every project build that extracts atomic Linaria, including
5
+ * the product build and its independently configured MCP preview builder. */
6
+ export function linariaTagResolver(source, tag) {
7
+ if (source !== "@linaria/atomic" || !["css", "styled"].includes(tag)) return null;
8
+ return fileURLToPath(new URL(`./linaria/atomic-${tag}.cjs`, import.meta.url));
9
+ }
@@ -100,7 +100,7 @@ Boolean requesting whether a visible border and background is provided by the ho
100
100
  - omitted: host decides border`)}),Bx=d.object({method:d.literal("ui/request-display-mode"),params:d.object({mode:da.describe("The display mode being requested.")})}),Dv=d.object({mode:da.describe("The display mode that was actually set. May differ from requested if not supported.")}).passthrough(),Vv=d.union([d.literal("model"),d.literal("app")]).describe("Tool visibility scope - who can access the tool."),Wx=d.object({resourceUri:d.string().optional(),visibility:d.array(Vv).optional().describe(`Who can access this tool. Default: ["model", "app"]
101
101
  - "model": Tool visible to and callable by the agent
102
102
  - "app": Tool callable by the app from this server only`),csp:d.never().optional(),permissions:d.never().optional()}),Gx=d.object({mimeTypes:d.array(d.string()).optional().describe('Array of supported MIME types for UI resources.\nMust include `"text/html;profile=mcp-app"` for MCP Apps support.')}),Kx=d.object({method:d.literal("ui/download-file"),params:d.object({contents:d.array(d.union([go,So])).describe("Resource contents to download \u2014 embedded (inline data) or linked (host fetches). Uses standard MCP resource types.")})}),Yx=d.object({method:d.literal("ui/message"),params:d.object({role:d.literal("user").describe('Message role, currently only "user" is supported.'),content:d.array(Fe).describe("Message content blocks (text, image, etc.).")})}),Qx=d.object({method:d.literal("ui/notifications/sandbox-resource-ready"),params:d.object({html:d.string().describe("HTML content to load into the inner iframe."),sandbox:d.string().optional().describe("Optional override for the inner iframe's sandbox attribute."),csp:_f.optional().describe("CSP configuration from resource metadata."),permissions:yf.optional().describe("Sandbox permissions from resource metadata.")})}),Hv=d.object({method:d.literal("ui/notifications/tool-result"),params:Ot.describe("Standard MCP tool execution result.")}),x_=d.object({toolInfo:d.object({id:Ne.optional().describe("JSON-RPC id of the tools/call request."),tool:It.describe("Tool definition including name, inputSchema, etc.")}).optional().describe("Metadata of the tool call that instantiated this App."),theme:Tv.optional().describe("Current color theme preference."),styles:Av.optional().describe("Style configuration for theming the app."),displayMode:da.optional().describe("How the UI is currently displayed."),availableDisplayModes:d.array(da).optional().describe("Display modes the host supports."),containerDimensions:d.union([d.object({height:d.number().describe("Fixed container height in pixels.")}),d.object({maxHeight:d.union([d.number(),d.undefined()]).optional().describe("Maximum container height in pixels.")})]).and(d.union([d.object({width:d.number().describe("Fixed container width in pixels.")}),d.object({maxWidth:d.union([d.number(),d.undefined()]).optional().describe("Maximum container width in pixels.")})])).optional().describe(`Container dimensions. Represents the dimensions of the iframe or other
103
- container holding the app. Specify either width or maxWidth, and either height or maxHeight.`),locale:d.string().optional().describe("User's language and region preference in BCP 47 format."),timeZone:d.string().optional().describe("User's timezone in IANA format."),userAgent:d.string().optional().describe("Host application identifier."),platform:d.union([d.literal("web"),d.literal("desktop"),d.literal("mobile")]).optional().describe("Platform type for responsive design decisions."),deviceCapabilities:d.object({touch:d.boolean().optional().describe("Whether the device supports touch input."),hover:d.boolean().optional().describe("Whether the device supports hover interactions.")}).optional().describe("Device input capabilities."),safeAreaInsets:d.object({top:d.number().describe("Top safe area inset in pixels."),right:d.number().describe("Right safe area inset in pixels."),bottom:d.number().describe("Bottom safe area inset in pixels."),left:d.number().describe("Left safe area inset in pixels.")}).optional().describe("Mobile safe area boundaries in pixels.")}).passthrough(),Fv=d.object({method:d.literal("ui/notifications/host-context-changed"),params:x_.describe("Partial context update containing only changed fields.")}),Xx=d.object({method:d.literal("ui/update-model-context"),params:d.object({content:d.array(Fe).optional().describe("Context content blocks (text, image, etc.)."),structuredContent:d.record(d.string(),d.unknown().describe("Structured content for machine-readable context data.")).optional().describe("Structured content for machine-readable context data.")})}),eP=d.object({method:d.literal("ui/initialize"),params:d.object({appInfo:Ve.describe("App identification (name and version)."),appCapabilities:Zv.describe("Features and capabilities this app provides."),protocolVersion:d.string().describe("Protocol version this app supports.")})}),Jv=d.object({protocolVersion:d.string().describe('Negotiated protocol version string (e.g., "2025-11-21").'),hostInfo:Ve.describe("Host application identification and version."),hostCapabilities:Uv.describe("Features and capabilities provided by the host."),hostContext:x_.describe("Rich context about the host environment.")}).passthrough(),Bv={target:"draft-2020-12"};async function R_(e,t){let o=e["~standard"];if(o.jsonSchema)return o.jsonSchema[t](Bv);throw Error(`Schema (vendor: ${o.vendor}) does not implement Standard JSON Schema (~standard.jsonSchema). Use a library that does (zod v4, ArkType, Valibot) or wrap your schema accordingly.`)}async function w_(e,t,o=""){let r=await e["~standard"].validate(t);if(r.issues){let n=r.issues.map(s=>{let i=s.path?.map(c=>typeof c=="object"?c.key:c).join(".");return i?`${i}: ${s.message}`:s.message}).join("; ");throw Error(o+n)}return r.value}function Wv(e,t){let o={...e,...t};return t.experimental!==void 0&&(o.experimental={...e.experimental,...t.experimental}),t.tools!==void 0&&(o.tools={...e.tools,...t.tools}),o}var Jl=class e extends mf{_appInfo;_appCapabilities;options;_hostCapabilities;_hostInfo;_hostContext;_registeredTools={};_initializedSent=!1;_registeredEvents=new Set;_events=new ff;_methods=new gf;setRequestHandler=(...t)=>{let[o,...r]=z_("request",t)??t;this._methods.claim(o,"setRequestHandler"),super.setRequestHandler.call(this,o,...r)};setNotificationHandler=(...t)=>{let[o,...r]=z_("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)};_assertInitialized(t){if(this._initializedSent)return;let o=`[ext-apps] App.${t}() called before connect() completed the ui/initialize handshake. Await app.connect() before calling this method, or move data loading to an ontoolresult handler.`;if(this.options?.strict)throw Error(o);console.warn(`${o}. This will throw in a future release.`)}eventSchemas={toolinput:Ov,toolinputpartial:Nv,toolresult:Hv,toolcancelled:Mv,hostcontextchanged:Fv};static ONE_SHOT_EVENTS=new Set(["toolinput","toolinputpartial","toolresult","toolcancelled"]);_everHadListener=new Set;_assertHandlerTiming(t){if(!e.ONE_SHOT_EVENTS.has(t)||this._everHadListener.has(t)||(this._everHadListener.add(t),!this._initializedSent))return;let o=`[ext-apps] "${String(t)}" handler registered after connect() completed the ui/initialize handshake. The host may have already sent this notification. Register handlers before calling app.connect().`;if(this.options?.strict)throw Error(o);console.warn(o)}setEventHandler(t,o){o&&this._assertHandlerTiming(t),this._ensureEventSlot(t),this._events.setHandler(t,o)}addEventListener(t,o){this._assertHandlerTiming(t),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)}`);this.setNotificationHandler(o.shape.method.value,{params:o.shape.params},r=>{let n=r;t==="hostcontextchanged"&&(this._hostContext={...this._hostContext,...n}),this._events.dispatch(t,n)}),this._registeredEvents.add(t)}getEventHandler(t){return this._events.getHandler(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={autoResize:!0}){super(r),this._appInfo=t,this._appCapabilities=o,this.options=r,this._methods.replace("notifications/cancelled"),this._methods.replace("notifications/progress"),!r.allowUnsafeEval&&d.config({jitless:!0}),this.replaceRequestHandler("ping",n=>(console.log("Received ping:",n.params),{})),this.setEventHandler("hostcontextchanged",void 0)}buildContext(t){return t}registerAppCapabilities(t){if(this.transport)throw Error("Cannot register capabilities after transport is established");this._appCapabilities=Wv(this._appCapabilities,t)}registerTool(t,o,r){if(this._registeredTools[t])throw Error(`Tool ${t} is already registered`);let n=this,s=()=>{n._initializedSent&&n._appCapabilities.tools?.listChanged&&n.sendToolListChanged()},i=o.inputSchema!==void 0,c={title:o.title,description:o.description,inputSchema:o.inputSchema,outputSchema:o.outputSchema,annotations:o.annotations,_meta:o._meta,enabled:!0,enable(){this.enabled=!0,s()},disable(){this.enabled=!1,s()},update(l){Object.assign(this,l),s()},remove(){n._registeredTools[t]===c&&(delete n._registeredTools[t],s())},handler:async(l,u)=>{if(!c.enabled)throw Error(`Tool ${t} is disabled`);let m;if(i){let S=c.inputSchema,_=S?await w_(S,l??{},`Invalid input for tool ${t}: `):l??{};m=await r(_,u)}else m=await r(u);return c.outputSchema&&!m.isError&&(m.structuredContent=await w_(c.outputSchema,m.structuredContent,`Invalid output for tool ${t}: `)),m}};return this._registeredTools[t]=c,!this._appCapabilities.tools&&!this.transport&&this.registerAppCapabilities({tools:{listChanged:!0}}),this.ensureToolHandlersInitialized(),s(),c}_toolHandlersInitialized=!1;ensureToolHandlersInitialized(){this._toolHandlersInitialized||(this._toolHandlersInitialized=!0,this.oncalltool=async(t,o)=>{let r=this._registeredTools[t.name];if(!r)throw Error(`Tool ${t.name} not found`);return r.handler(t.arguments,o)},this.onlisttools=async(t,o)=>({tools:await Promise.all(Object.entries(this._registeredTools).filter(([r,n])=>n.enabled).map(async([r,n])=>{let s={name:r,title:n.title,description:n.description,inputSchema:n.inputSchema?await R_(n.inputSchema,"input"):{type:"object",properties:{}}};return n.outputSchema&&(s.outputSchema=await R_(n.outputSchema,"output")),n.annotations&&(s.annotations=n.annotations),n._meta&&(s._meta=n._meta),s}))}))}async sendToolListChanged(t={}){this._assertInitialized("sendToolListChanged"),await this.notification({method:"notifications/tools/list_changed",params:t})}getHostCapabilities(){return this._hostCapabilities}getHostVersion(){return this._hostInfo}getHostContext(){return this._hostContext}get ontoolinput(){return this.getEventHandler("toolinput")}set ontoolinput(t){this.setEventHandler("toolinput",t)}get ontoolinputpartial(){return this.getEventHandler("toolinputpartial")}set ontoolinputpartial(t){this.setEventHandler("toolinputpartial",t)}get ontoolresult(){return this.getEventHandler("toolresult")}set ontoolresult(t){this.setEventHandler("toolresult",t)}get ontoolcancelled(){return this.getEventHandler("toolcancelled")}set ontoolcancelled(t){this.setEventHandler("toolcancelled",t)}get onhostcontextchanged(){return this.getEventHandler("hostcontextchanged")}set onhostcontextchanged(t){this.setEventHandler("hostcontextchanged",t)}_onteardown;get onteardown(){return this._onteardown}set onteardown(t){this.warnIfRequestHandlerReplaced("onteardown",this._onteardown,t),this._onteardown=t,this.replaceRequestHandler("ui/resource-teardown",{params:jv.shape.params,result:Lv},(o,r)=>{if(!this._onteardown)throw Error("No onteardown handler set");return this._onteardown(o,r)})}_oncalltool;get oncalltool(){return this._oncalltool}set oncalltool(t){this.warnIfRequestHandlerReplaced("oncalltool",this._oncalltool,t),this._oncalltool=t,this.replaceRequestHandler("tools/call",(o,r)=>{if(!this._oncalltool)throw Error("No oncalltool handler set");return this._oncalltool(o.params,r)})}_onlisttools;get onlisttools(){return this._onlisttools}set onlisttools(t){this.warnIfRequestHandlerReplaced("onlisttools",this._onlisttools,t),this._onlisttools=t,this.replaceRequestHandler("tools/list",(o,r)=>{if(!this._onlisttools)throw Error("No onlisttools handler set");return this._onlisttools(o.params,r)})}assertCapabilityForMethod(t){switch(t){case"sampling/createMessage":if(!this._hostCapabilities?.sampling)throw Error(`Host does not support sampling (required for ${t})`);break}}assertRequestHandlerCapability(t){switch(t){case"tools/call":case"tools/list":if(!this._appCapabilities.tools)throw Error(`Client does not support tool capability (required for ${t})`);return;default:return}}assertNotificationCapability(t){}async callServerTool(t,o){if(this._assertInitialized("callServerTool"),typeof t=="string")throw Error(`callServerTool() expects an object as its first argument, but received a string ("${t}"). Did you mean: callServerTool({ name: "${t}", arguments: { ... } })?`);return await this.request({method:"tools/call",params:t},{onprogress:()=>{},resetTimeoutOnProgress:!0,...o})}async readServerResource(t,o){return this._assertInitialized("readServerResource"),await this.request({method:"resources/read",params:t},o)}async listServerResources(t,o){return this._assertInitialized("listServerResources"),await this.request({method:"resources/list",params:t},o)}async createSamplingMessage(t,o){return this._assertInitialized("createSamplingMessage"),await this.request({method:"sampling/createMessage",params:t},o)}sendMessage(t,o){return this._assertInitialized("sendMessage"),this.request({method:"ui/message",params:t},Iv,o)}sendLog(t){return this.notification({method:"notifications/message",params:t})}updateModelContext(t,o){return this._assertInitialized("updateModelContext"),this.request({method:"ui/update-model-context",params:t},Ct,o)}openLink(t,o){return this._assertInitialized("openLink"),this.request({method:"ui/open-link",params:t},$v,o)}sendOpenLink=this.openLink;downloadFile(t,o){return this._assertInitialized("downloadFile"),this.request({method:"ui/download-file",params:t},Cv,o)}requestTeardown(t={}){return this.notification({method:"ui/notifications/request-teardown",params:t})}requestDisplayMode(t,o){return this._assertInitialized("requestDisplayMode"),this.request({method:"ui/request-display-mode",params:t},Dv,o)}sendSizeChanged(t){return this.notification({method:"ui/notifications/size-changed",params:t})}setupSizeChangedNotifications(){let t=!1,o=0,r=0,n=()=>{t||(t=!0,requestAnimationFrame(()=>{t=!1;let i=document.documentElement,c=i.style.height;i.style.height="max-content";let l=Math.ceil(i.getBoundingClientRect().height);i.style.height=c;let u=Math.ceil(window.innerWidth);(u!==o||l!==r)&&(o=u,r=l,this.sendSizeChanged({width:u,height:l}))}))};n();let s=new ResizeObserver(n);return s.observe(document.documentElement),s.observe(document.body),()=>s.disconnect()}async connect(t=new Sf(window.parent,window.parent),o){if(this.transport)throw Error("App is already connected. Call close() before connecting again.");this._initializedSent=!1;try{await super.connect(t);let r=await this.request({method:"ui/initialize",params:{appCapabilities:this._appCapabilities,appInfo:this._appInfo,protocolVersion:xv}},Jv,o);if(r===void 0)throw Error(`Server sent invalid initialize result: ${r}`);this._hostCapabilities=r.hostCapabilities,this._hostInfo=r.hostInfo,this._hostContext=r.hostContext,await this.notification({method:"ui/notifications/initialized"}),this._initializedSent=!0,this.options?.autoResize&&this.setupSizeChangedNotifications()}catch(r){throw this.close(),r}}};var Bl=class{constructor(t){this.read=t,this.records=new Map,this.bytes=new Map,this.urls=new Map,this.styles=new Map,this.styleReady=new Map,this.revisions=new Set,this.importing=Promise.resolve(),this.generation=0,this.closed=!1,globalThis.__dsPreviewImport=o=>this.importModule(o)}register(t){if(t.schemaVersion!==1||!Array.isArray(t.scenarios)||!t.assets||typeof t.revision!="string")throw new Error("Unsupported preview manifest. Rebuild previews with the installed ADS version.");if(this.revisions.add(t.revision),this.revisions.size>20)throw Object.assign(new Error("Reopen the preview to release modules retained across 20 build revisions."),{code:"ADS_PREVIEW_REOPEN"});for(let[o,r]of Object.entries(t.assets)){if(!/^ads:\/\/assets\/[a-f0-9]{64}\.[a-z0-9]+$/.test(o)||!/^sha256:[a-f0-9]{64}$/.test(r.digest)||!["imports","dynamicImports","styles","references"].every(n=>Array.isArray(r[n])&&r[n].every(s=>Object.hasOwn(t.assets,s))))throw new Error("Invalid preview dependency graph.");if(this.records.has(o)&&this.records.get(o).digest!==r.digest)throw new Error("An immutable preview resource changed its content.");this.records.set(o,r)}}async resource(t){if(!this.bytes.has(t)){let o=(async()=>{let r=this.records.get(t);if(!r)throw new Error(`Unknown preview dependency: ${t}`);let n=await this.read(t);if(this.closed)throw new Error("The preview App was closed.");let s=n.contents.find(u=>u.uri===t);if(!s)throw new Error(`Missing preview dependency: ${t}`);let i=typeof s.text=="string"?new TextEncoder().encode(s.text):Uint8Array.from(atob(s.blob),u=>u.charCodeAt(0)),c=await crypto.subtle.digest("SHA-256",i),l="sha256:"+[...new Uint8Array(c)].map(u=>u.toString(16).padStart(2,"0")).join("");if(i.length!==r.size||l!==r.digest)throw new Error("Preview resource integrity check failed. Rebuild and reopen the preview.");return i})();this.bytes.set(t,o),o.catch(()=>this.bytes.delete(t))}return this.bytes.get(t)}closure(t){let o=[],r=new Set,n=s=>{if(r.has(s))return;r.add(s);let i=this.records.get(s);if(!i)throw new Error(`Missing preview dependency metadata: ${s}`);for(let c of[...i.imports,...i.styles,...i.references])n(c);o.push(s)};return n(t),o}async materialize(t){let o=this.closure(t);await Promise.all(o.map(n=>this.resource(n)));let r={};for(let n of o){if(this.closed)throw new Error("The preview App was closed.");if(this.urls.has(n))continue;let s=this.records.get(n),i=await this.resource(n);if(s.kind==="module"||s.mimeType==="text/css"){let l=new TextDecoder().decode(i);for(let u of s.references){let m=this.urls.get(u);if(!m)throw new Error("Unsupported cyclic asset reference.");l=l.replaceAll(u,m)}i=l}let c=URL.createObjectURL(new Blob([i],{type:s.mimeType}));this.urls.set(n,c),s.kind==="module"&&(r[n]=c)}if(Object.keys(r).length){let n=document.createElement("script");n.type="importmap",n.textContent=JSON.stringify({imports:r}),document.head.append(n)}return o}async importModule(t){let o=this.importing.then(()=>this.materialize(t));this.importing=o.catch(()=>{});let r=await o;for(let n of r){if(this.records.get(n).mimeType!=="text/css")continue;if(this.styles.has(n)){await this.styleReady.get(n);continue}let i=document.createElement("link");i.rel="stylesheet",i.href=this.urls.get(n),this.styles.set(n,i);let c=new Promise((l,u)=>{i.onload=l,i.onerror=()=>u(new Error("The host CSP blocked a preview stylesheet."))});this.styleReady.set(n,c),document.head.append(i);try{await c}catch(l){throw i.remove(),this.styles.delete(n),this.styleReady.delete(n),l}}return import(t)}async mount(t,o,r){let n=++this.generation;this.register(t);let s=t.scenarios.find(l=>l.id===o);if(!s)throw new Error("The selected scenario is no longer registered.");let i=await this.importModule(s.module);if(n!==this.generation||(await this.unmount?.(),n!==this.generation||this.closed))return!1;r.replaceChildren();let c=new Set(this.closure(s.module));for(let[l,u]of this.styles)c.has(l)||(u.remove(),this.styles.delete(l),this.styleReady.delete(l));for(let l of this.closure(s.module))this.styles.has(l)&&document.head.append(this.styles.get(l));if(typeof i.mount!="function")throw new Error("The compiled scenario has no mount adapter.");if(this.unmount=i.mount(r),typeof this.unmount!="function")throw new Error("The scenario mount adapter must return an unmount callback.");return!0}async dispose(){if(!this.closed){this.closed=!0,++this.generation,await this.unmount?.();for(let t of this.styles.values())t.remove();for(let t of this.urls.values())URL.revokeObjectURL(t);this.styles.clear(),this.styleReady.clear(),this.urls.clear(),this.bytes.clear(),this.records.clear(),delete globalThis.__dsPreviewImport}}};function P_(e){return Array.isArray(e)&&e.length>0&&e.every(t=>t&&typeof t=="object"&&typeof t.token=="string"&&/^--[\w-]+$/.test(t.token)&&typeof t.property=="string"&&/^-?[a-z][a-z0-9-]*$/.test(t.property)&&typeof t.probe=="string"&&t.probe.trim().length>0)&&new Set(e.map(t=>`${t.token}:${t.property}`)).size===e.length}function T_(e,t){let o=t.getComputedStyle(e),r=e.getBoundingClientRect();if(o.display==="none"||o.visibility!=="visible"||!e.getClientRects().length||!(r.width||r.height))return!1;for(let n=e;n;n=n.parentElement)if(Number(t.getComputedStyle(n).opacity)===0)return!1;return!0}function k_(e,t){if(!P_(t))return{ok:!1,diagnostics:["Token preview needs explicit { token, property, probe } samples from the project fixture. Rebuild with the current compiler."]};let o=e.ownerDocument,r=o.defaultView,n=o.documentElement,s=[],i=[],c=[...e.querySelectorAll("[data-ads-token], [data-ads-property]")],l=new Set(t.map(u=>`${u.token}:${u.property}`));for(let u of c)l.has(`${u.dataset.adsToken}:${u.dataset.adsProperty}`)||s.push(`Undeclared rendered token sample ${u.dataset.adsToken}:${u.dataset.adsProperty}.`);for(let u of t){if(!r.CSS.supports(u.property,u.probe)){s.push(`${u.token}: the fixture probe is invalid for ${u.property}.`);continue}let m=r.getComputedStyle(n).getPropertyValue(u.token).trim();if(!m||!r.CSS.supports(u.property,m)){s.push(`${u.token}: its current value is invalid for the fixture's ${u.property} binding.`);continue}let S=c.filter(y=>y.dataset.adsToken===u.token&&y.dataset.adsProperty===u.property);if(!S.length){s.push(`${u.token} needs a rendered ${u.property} sample, not only a name/value label.`);continue}let _=!1;for(let y of S){let P=[];for(let q=y.parentElement;q&&q!==e;q=q.parentElement)q.tagName==="DETAILS"&&!q.open&&(q.open=!0,P.push(q));let E=n.getAttribute("style"),C=y.getAttribute("style");try{y.style.setProperty("transition","none","important");let q=r.getComputedStyle(y).getPropertyValue(u.property),J=T_(y,r);n.style.setProperty(u.token,u.probe,"important");let L=r.getComputedStyle(y).getPropertyValue(u.property);_=!!(L&&L!==q&&(J||T_(y,r)))}finally{E===null?n.removeAttribute("style"):n.setAttribute("style",E),C===null?y.removeAttribute("style"):y.setAttribute("style",C);for(let q of P)q.open=!1}if(_)break}_?i.push(u):s.push(`${u.token} has no visible ${u.property} sample that responds to its value. Hardcoded copies do not satisfy coverage.`)}return{ok:!s.length,verified:i,diagnostics:s}}var We=new Jl({name:"ADS resource preview",version:"1.0.0"},{},{autoResize:!0}),E_=document.getElementById("root"),bf=document.getElementById("ads-preview-status"),vf=async e=>{if(!We.getHostCapabilities()?.serverResources)throw new Error("This host does not forward MCP resource reads. Open the same preview in the ADS showcase.");try{return await We.readServerResource({uri:e},{timeout:15e3})}catch(t){throw t.code===-32601||t.message==="Method not found"?new Error("This host does not forward MCP resource reads. Open the same preview in the ADS showcase."):t}},Gl=new Bl(vf),Vr,ha,$_,Rf,zf=!1,vo=0,pa={},Be="";function Mt(e,t=!1){bf.textContent=e,bf.hidden=!e,bf.setAttribute("role",t?"alert":"status")}async function Wl(e,t={}){try{We.getHostCapabilities()?.logging&&await We.sendLog({level:e==="error"?"error":"info",logger:"ads-preview",data:{state:e,id:Vr,...t}})}catch{}}function C_(e){pa={...pa,...e,styles:{...pa.styles,...e.styles}},["light","dark"].includes(pa.theme)&&(document.documentElement.dataset.theme=pa.theme)}async function I_(){return JSON.parse((await vf("ads://preview/manifest")).contents.find(e=>typeof e.text=="string").text)}async function wf(e,t){if(e===Vr&&t&&t===ha){Mt(Be,!!Be),await Wl(Be?"error":"ready",Be?{message:Be}:{revision:ha});return}let o=++vo;++Gl.generation,Vr=e,document.documentElement.dataset.adsScenario=e,document.documentElement.dataset.adsPreviewState="loading",Be="",Mt(""),await Wl("loading");try{let r=await I_();if(t&&r.revision!==t)throw new Error("Preview changed while opening. Select it again to use the current build.");if(o!==vo||!await Gl.mount(r,e,E_)||o!==vo||(ha=r.revision,$_=r.session,await new Promise(i=>requestAnimationFrame(()=>requestAnimationFrame(i))),await document.fonts.ready,o!==vo))return;let s=r.scenarios.find(i=>i.id===e);if(s?.kind==="tokens"){let i=k_(E_,s.samples);if(!i.ok)throw Be=`Token preview verification failed:
103
+ container holding the app. Specify either width or maxWidth, and either height or maxHeight.`),locale:d.string().optional().describe("User's language and region preference in BCP 47 format."),timeZone:d.string().optional().describe("User's timezone in IANA format."),userAgent:d.string().optional().describe("Host application identifier."),platform:d.union([d.literal("web"),d.literal("desktop"),d.literal("mobile")]).optional().describe("Platform type for responsive design decisions."),deviceCapabilities:d.object({touch:d.boolean().optional().describe("Whether the device supports touch input."),hover:d.boolean().optional().describe("Whether the device supports hover interactions.")}).optional().describe("Device input capabilities."),safeAreaInsets:d.object({top:d.number().describe("Top safe area inset in pixels."),right:d.number().describe("Right safe area inset in pixels."),bottom:d.number().describe("Bottom safe area inset in pixels."),left:d.number().describe("Left safe area inset in pixels.")}).optional().describe("Mobile safe area boundaries in pixels.")}).passthrough(),Fv=d.object({method:d.literal("ui/notifications/host-context-changed"),params:x_.describe("Partial context update containing only changed fields.")}),Xx=d.object({method:d.literal("ui/update-model-context"),params:d.object({content:d.array(Fe).optional().describe("Context content blocks (text, image, etc.)."),structuredContent:d.record(d.string(),d.unknown().describe("Structured content for machine-readable context data.")).optional().describe("Structured content for machine-readable context data.")})}),eP=d.object({method:d.literal("ui/initialize"),params:d.object({appInfo:Ve.describe("App identification (name and version)."),appCapabilities:Zv.describe("Features and capabilities this app provides."),protocolVersion:d.string().describe("Protocol version this app supports.")})}),Jv=d.object({protocolVersion:d.string().describe('Negotiated protocol version string (e.g., "2025-11-21").'),hostInfo:Ve.describe("Host application identification and version."),hostCapabilities:Uv.describe("Features and capabilities provided by the host."),hostContext:x_.describe("Rich context about the host environment.")}).passthrough(),Bv={target:"draft-2020-12"};async function R_(e,t){let o=e["~standard"];if(o.jsonSchema)return o.jsonSchema[t](Bv);throw Error(`Schema (vendor: ${o.vendor}) does not implement Standard JSON Schema (~standard.jsonSchema). Use a library that does (zod v4, ArkType, Valibot) or wrap your schema accordingly.`)}async function w_(e,t,o=""){let r=await e["~standard"].validate(t);if(r.issues){let n=r.issues.map(s=>{let i=s.path?.map(c=>typeof c=="object"?c.key:c).join(".");return i?`${i}: ${s.message}`:s.message}).join("; ");throw Error(o+n)}return r.value}function Wv(e,t){let o={...e,...t};return t.experimental!==void 0&&(o.experimental={...e.experimental,...t.experimental}),t.tools!==void 0&&(o.tools={...e.tools,...t.tools}),o}var Jl=class e extends mf{_appInfo;_appCapabilities;options;_hostCapabilities;_hostInfo;_hostContext;_registeredTools={};_initializedSent=!1;_registeredEvents=new Set;_events=new ff;_methods=new gf;setRequestHandler=(...t)=>{let[o,...r]=z_("request",t)??t;this._methods.claim(o,"setRequestHandler"),super.setRequestHandler.call(this,o,...r)};setNotificationHandler=(...t)=>{let[o,...r]=z_("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)};_assertInitialized(t){if(this._initializedSent)return;let o=`[ext-apps] App.${t}() called before connect() completed the ui/initialize handshake. Await app.connect() before calling this method, or move data loading to an ontoolresult handler.`;if(this.options?.strict)throw Error(o);console.warn(`${o}. This will throw in a future release.`)}eventSchemas={toolinput:Ov,toolinputpartial:Nv,toolresult:Hv,toolcancelled:Mv,hostcontextchanged:Fv};static ONE_SHOT_EVENTS=new Set(["toolinput","toolinputpartial","toolresult","toolcancelled"]);_everHadListener=new Set;_assertHandlerTiming(t){if(!e.ONE_SHOT_EVENTS.has(t)||this._everHadListener.has(t)||(this._everHadListener.add(t),!this._initializedSent))return;let o=`[ext-apps] "${String(t)}" handler registered after connect() completed the ui/initialize handshake. The host may have already sent this notification. Register handlers before calling app.connect().`;if(this.options?.strict)throw Error(o);console.warn(o)}setEventHandler(t,o){o&&this._assertHandlerTiming(t),this._ensureEventSlot(t),this._events.setHandler(t,o)}addEventListener(t,o){this._assertHandlerTiming(t),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)}`);this.setNotificationHandler(o.shape.method.value,{params:o.shape.params},r=>{let n=r;t==="hostcontextchanged"&&(this._hostContext={...this._hostContext,...n}),this._events.dispatch(t,n)}),this._registeredEvents.add(t)}getEventHandler(t){return this._events.getHandler(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={autoResize:!0}){super(r),this._appInfo=t,this._appCapabilities=o,this.options=r,this._methods.replace("notifications/cancelled"),this._methods.replace("notifications/progress"),!r.allowUnsafeEval&&d.config({jitless:!0}),this.replaceRequestHandler("ping",n=>(console.log("Received ping:",n.params),{})),this.setEventHandler("hostcontextchanged",void 0)}buildContext(t){return t}registerAppCapabilities(t){if(this.transport)throw Error("Cannot register capabilities after transport is established");this._appCapabilities=Wv(this._appCapabilities,t)}registerTool(t,o,r){if(this._registeredTools[t])throw Error(`Tool ${t} is already registered`);let n=this,s=()=>{n._initializedSent&&n._appCapabilities.tools?.listChanged&&n.sendToolListChanged()},i=o.inputSchema!==void 0,c={title:o.title,description:o.description,inputSchema:o.inputSchema,outputSchema:o.outputSchema,annotations:o.annotations,_meta:o._meta,enabled:!0,enable(){this.enabled=!0,s()},disable(){this.enabled=!1,s()},update(l){Object.assign(this,l),s()},remove(){n._registeredTools[t]===c&&(delete n._registeredTools[t],s())},handler:async(l,u)=>{if(!c.enabled)throw Error(`Tool ${t} is disabled`);let m;if(i){let S=c.inputSchema,_=S?await w_(S,l??{},`Invalid input for tool ${t}: `):l??{};m=await r(_,u)}else m=await r(u);return c.outputSchema&&!m.isError&&(m.structuredContent=await w_(c.outputSchema,m.structuredContent,`Invalid output for tool ${t}: `)),m}};return this._registeredTools[t]=c,!this._appCapabilities.tools&&!this.transport&&this.registerAppCapabilities({tools:{listChanged:!0}}),this.ensureToolHandlersInitialized(),s(),c}_toolHandlersInitialized=!1;ensureToolHandlersInitialized(){this._toolHandlersInitialized||(this._toolHandlersInitialized=!0,this.oncalltool=async(t,o)=>{let r=this._registeredTools[t.name];if(!r)throw Error(`Tool ${t.name} not found`);return r.handler(t.arguments,o)},this.onlisttools=async(t,o)=>({tools:await Promise.all(Object.entries(this._registeredTools).filter(([r,n])=>n.enabled).map(async([r,n])=>{let s={name:r,title:n.title,description:n.description,inputSchema:n.inputSchema?await R_(n.inputSchema,"input"):{type:"object",properties:{}}};return n.outputSchema&&(s.outputSchema=await R_(n.outputSchema,"output")),n.annotations&&(s.annotations=n.annotations),n._meta&&(s._meta=n._meta),s}))}))}async sendToolListChanged(t={}){this._assertInitialized("sendToolListChanged"),await this.notification({method:"notifications/tools/list_changed",params:t})}getHostCapabilities(){return this._hostCapabilities}getHostVersion(){return this._hostInfo}getHostContext(){return this._hostContext}get ontoolinput(){return this.getEventHandler("toolinput")}set ontoolinput(t){this.setEventHandler("toolinput",t)}get ontoolinputpartial(){return this.getEventHandler("toolinputpartial")}set ontoolinputpartial(t){this.setEventHandler("toolinputpartial",t)}get ontoolresult(){return this.getEventHandler("toolresult")}set ontoolresult(t){this.setEventHandler("toolresult",t)}get ontoolcancelled(){return this.getEventHandler("toolcancelled")}set ontoolcancelled(t){this.setEventHandler("toolcancelled",t)}get onhostcontextchanged(){return this.getEventHandler("hostcontextchanged")}set onhostcontextchanged(t){this.setEventHandler("hostcontextchanged",t)}_onteardown;get onteardown(){return this._onteardown}set onteardown(t){this.warnIfRequestHandlerReplaced("onteardown",this._onteardown,t),this._onteardown=t,this.replaceRequestHandler("ui/resource-teardown",{params:jv.shape.params,result:Lv},(o,r)=>{if(!this._onteardown)throw Error("No onteardown handler set");return this._onteardown(o,r)})}_oncalltool;get oncalltool(){return this._oncalltool}set oncalltool(t){this.warnIfRequestHandlerReplaced("oncalltool",this._oncalltool,t),this._oncalltool=t,this.replaceRequestHandler("tools/call",(o,r)=>{if(!this._oncalltool)throw Error("No oncalltool handler set");return this._oncalltool(o.params,r)})}_onlisttools;get onlisttools(){return this._onlisttools}set onlisttools(t){this.warnIfRequestHandlerReplaced("onlisttools",this._onlisttools,t),this._onlisttools=t,this.replaceRequestHandler("tools/list",(o,r)=>{if(!this._onlisttools)throw Error("No onlisttools handler set");return this._onlisttools(o.params,r)})}assertCapabilityForMethod(t){switch(t){case"sampling/createMessage":if(!this._hostCapabilities?.sampling)throw Error(`Host does not support sampling (required for ${t})`);break}}assertRequestHandlerCapability(t){switch(t){case"tools/call":case"tools/list":if(!this._appCapabilities.tools)throw Error(`Client does not support tool capability (required for ${t})`);return;default:return}}assertNotificationCapability(t){}async callServerTool(t,o){if(this._assertInitialized("callServerTool"),typeof t=="string")throw Error(`callServerTool() expects an object as its first argument, but received a string ("${t}"). Did you mean: callServerTool({ name: "${t}", arguments: { ... } })?`);return await this.request({method:"tools/call",params:t},{onprogress:()=>{},resetTimeoutOnProgress:!0,...o})}async readServerResource(t,o){return this._assertInitialized("readServerResource"),await this.request({method:"resources/read",params:t},o)}async listServerResources(t,o){return this._assertInitialized("listServerResources"),await this.request({method:"resources/list",params:t},o)}async createSamplingMessage(t,o){return this._assertInitialized("createSamplingMessage"),await this.request({method:"sampling/createMessage",params:t},o)}sendMessage(t,o){return this._assertInitialized("sendMessage"),this.request({method:"ui/message",params:t},Iv,o)}sendLog(t){return this.notification({method:"notifications/message",params:t})}updateModelContext(t,o){return this._assertInitialized("updateModelContext"),this.request({method:"ui/update-model-context",params:t},Ct,o)}openLink(t,o){return this._assertInitialized("openLink"),this.request({method:"ui/open-link",params:t},$v,o)}sendOpenLink=this.openLink;downloadFile(t,o){return this._assertInitialized("downloadFile"),this.request({method:"ui/download-file",params:t},Cv,o)}requestTeardown(t={}){return this.notification({method:"ui/notifications/request-teardown",params:t})}requestDisplayMode(t,o){return this._assertInitialized("requestDisplayMode"),this.request({method:"ui/request-display-mode",params:t},Dv,o)}sendSizeChanged(t){return this.notification({method:"ui/notifications/size-changed",params:t})}setupSizeChangedNotifications(){let t=!1,o=0,r=0,n=()=>{t||(t=!0,requestAnimationFrame(()=>{t=!1;let i=document.documentElement,c=i.style.height;i.style.height="max-content";let l=Math.ceil(i.getBoundingClientRect().height);i.style.height=c;let u=Math.ceil(window.innerWidth);(u!==o||l!==r)&&(o=u,r=l,this.sendSizeChanged({width:u,height:l}))}))};n();let s=new ResizeObserver(n);return s.observe(document.documentElement),s.observe(document.body),()=>s.disconnect()}async connect(t=new Sf(window.parent,window.parent),o){if(this.transport)throw Error("App is already connected. Call close() before connecting again.");this._initializedSent=!1;try{await super.connect(t);let r=await this.request({method:"ui/initialize",params:{appCapabilities:this._appCapabilities,appInfo:this._appInfo,protocolVersion:xv}},Jv,o);if(r===void 0)throw Error(`Server sent invalid initialize result: ${r}`);this._hostCapabilities=r.hostCapabilities,this._hostInfo=r.hostInfo,this._hostContext=r.hostContext,await this.notification({method:"ui/notifications/initialized"}),this._initializedSent=!0,this.options?.autoResize&&this.setupSizeChangedNotifications()}catch(r){throw this.close(),r}}};var Bl=class{constructor(t){this.read=t,this.records=new Map,this.bytes=new Map,this.urls=new Map,this.styles=new Map,this.styleReady=new Map,this.revisions=new Set,this.importing=Promise.resolve(),this.generation=0,this.closed=!1,globalThis.__dsPreviewImport=o=>this.importModule(o)}register(t){if(t.schemaVersion!==1||!Array.isArray(t.scenarios)||!t.assets||typeof t.revision!="string")throw new Error("Unsupported preview manifest. Rebuild previews with the installed ADS version.");if(this.revisions.add(t.revision),this.revisions.size>20)throw Object.assign(new Error("Reopen the preview to release modules retained across 20 build revisions."),{code:"ADS_PREVIEW_REOPEN"});for(let[o,r]of Object.entries(t.assets)){if(!/^ads:\/\/assets\/[a-f0-9]{64}\.[a-z0-9]+$/.test(o)||!/^sha256:[a-f0-9]{64}$/.test(r.digest)||!["imports","dynamicImports","styles","references"].every(n=>Array.isArray(r[n])&&r[n].every(s=>Object.hasOwn(t.assets,s))))throw new Error("Invalid preview dependency graph.");if(this.records.has(o)&&this.records.get(o).digest!==r.digest)throw new Error("An immutable preview resource changed its content.");this.records.set(o,r)}}async resource(t){if(!this.bytes.has(t)){let o=(async()=>{let r=this.records.get(t);if(!r)throw new Error(`Unknown preview dependency: ${t}`);let n;try{n=await this.read(t)}catch(u){throw new Error(`Preview resource read failed for ${t}: ${u.message}`,{cause:u})}if(this.closed)throw new Error("The preview App was closed.");let s=n.contents.find(u=>u.uri===t);if(!s)throw new Error(`Missing preview dependency: ${t}`);let i=typeof s.text=="string"?new TextEncoder().encode(s.text):Uint8Array.from(atob(s.blob),u=>u.charCodeAt(0)),c=await crypto.subtle.digest("SHA-256",i),l="sha256:"+[...new Uint8Array(c)].map(u=>u.toString(16).padStart(2,"0")).join("");if(i.length!==r.size||l!==r.digest)throw new Error("Preview resource integrity check failed. Rebuild and reopen the preview.");return i})();this.bytes.set(t,o),o.catch(()=>this.bytes.delete(t))}return this.bytes.get(t)}closure(t){let o=[],r=new Set,n=s=>{if(r.has(s))return;r.add(s);let i=this.records.get(s);if(!i)throw new Error(`Missing preview dependency metadata: ${s}`);for(let c of[...i.imports,...i.styles,...i.references])n(c);o.push(s)};return n(t),o}async materialize(t){let o=this.closure(t);await Promise.all(o.map(n=>this.resource(n)));let r={};for(let n of o){if(this.closed)throw new Error("The preview App was closed.");if(this.urls.has(n))continue;let s=this.records.get(n),i=await this.resource(n);if(s.kind==="module"||s.mimeType==="text/css"){let l=new TextDecoder().decode(i);for(let u of s.references){let m=this.urls.get(u);if(!m)throw new Error("Unsupported cyclic asset reference.");l=l.replaceAll(u,m)}i=l}let c=URL.createObjectURL(new Blob([i],{type:s.mimeType}));this.urls.set(n,c),s.kind==="module"&&(r[n]=c)}if(Object.keys(r).length){let n=document.createElement("script");n.type="importmap",n.textContent=JSON.stringify({imports:r}),document.head.append(n)}return o}async importModule(t){let o=this.importing.then(()=>this.materialize(t));this.importing=o.catch(()=>{});let r=await o;for(let n of r){if(this.records.get(n).mimeType!=="text/css")continue;if(this.styles.has(n)){await this.styleReady.get(n);continue}let i=document.createElement("link");i.rel="stylesheet",i.href=this.urls.get(n),this.styles.set(n,i);let c=new Promise((l,u)=>{i.onload=l,i.onerror=()=>u(new Error("The host CSP blocked a preview stylesheet."))});this.styleReady.set(n,c),document.head.append(i);try{await c}catch(l){throw i.remove(),this.styles.delete(n),this.styleReady.delete(n),l}}try{return await import(t)}catch(n){throw new Error(`Preview module import failed for ${t}: ${n.message}. Inspect the App resource bridge, browser network/CSP diagnostics and current build.`,{cause:n})}}async mount(t,o,r){let n=++this.generation;this.register(t);let s=t.scenarios.find(l=>l.id===o);if(!s)throw new Error("The selected scenario is no longer registered.");let i=await this.importModule(s.module);if(n!==this.generation||(await this.unmount?.(),n!==this.generation||this.closed))return!1;r.replaceChildren();let c=new Set(this.closure(s.module));for(let[l,u]of this.styles)c.has(l)||(u.remove(),this.styles.delete(l),this.styleReady.delete(l));for(let l of this.closure(s.module))this.styles.has(l)&&document.head.append(this.styles.get(l));if(typeof i.mount!="function")throw new Error("The compiled scenario has no mount adapter.");if(this.unmount=i.mount(r),typeof this.unmount!="function")throw new Error("The scenario mount adapter must return an unmount callback.");return!0}async dispose(){if(!this.closed){this.closed=!0,++this.generation,await this.unmount?.();for(let t of this.styles.values())t.remove();for(let t of this.urls.values())URL.revokeObjectURL(t);this.styles.clear(),this.styleReady.clear(),this.urls.clear(),this.bytes.clear(),this.records.clear(),delete globalThis.__dsPreviewImport}}};function P_(e){return Array.isArray(e)&&e.length>0&&e.every(t=>t&&typeof t=="object"&&typeof t.token=="string"&&/^--[\w-]+$/.test(t.token)&&typeof t.property=="string"&&/^-?[a-z][a-z0-9-]*$/.test(t.property)&&typeof t.probe=="string"&&t.probe.trim().length>0)&&new Set(e.map(t=>`${t.token}:${t.property}`)).size===e.length}function T_(e,t){let o=t.getComputedStyle(e),r=e.getBoundingClientRect();if(o.display==="none"||o.visibility!=="visible"||!e.getClientRects().length||!(r.width||r.height))return!1;for(let n=e;n;n=n.parentElement)if(Number(t.getComputedStyle(n).opacity)===0)return!1;return!0}function k_(e,t){if(!P_(t))return{ok:!1,diagnostics:["Token preview needs explicit { token, property, probe } samples from the project fixture. Rebuild with the current compiler."]};let o=e.ownerDocument,r=o.defaultView,n=o.documentElement,s=[],i=[],c=[...e.querySelectorAll("[data-ads-token], [data-ads-property]")],l=new Set(t.map(u=>`${u.token}:${u.property}`));for(let u of c)l.has(`${u.dataset.adsToken}:${u.dataset.adsProperty}`)||s.push(`Undeclared rendered token sample ${u.dataset.adsToken}:${u.dataset.adsProperty}.`);for(let u of t){if(!r.CSS.supports(u.property,u.probe)){s.push(`${u.token}: the fixture probe is invalid for ${u.property}.`);continue}let m=r.getComputedStyle(n).getPropertyValue(u.token).trim();if(!m||!r.CSS.supports(u.property,m)){s.push(`${u.token}: its current value is invalid for the fixture's ${u.property} binding.`);continue}let S=c.filter(y=>y.dataset.adsToken===u.token&&y.dataset.adsProperty===u.property);if(!S.length){s.push(`${u.token} needs a rendered ${u.property} sample, not only a name/value label.`);continue}let _=!1;for(let y of S){let P=[];for(let q=y.parentElement;q&&q!==e;q=q.parentElement)q.tagName==="DETAILS"&&!q.open&&(q.open=!0,P.push(q));let E=n.getAttribute("style"),C=y.getAttribute("style");try{y.style.setProperty("transition","none","important");let q=r.getComputedStyle(y).getPropertyValue(u.property),J=T_(y,r);n.style.setProperty(u.token,u.probe,"important");let L=r.getComputedStyle(y).getPropertyValue(u.property);_=!!(L&&L!==q&&(J||T_(y,r)))}finally{E===null?n.removeAttribute("style"):n.setAttribute("style",E),C===null?y.removeAttribute("style"):y.setAttribute("style",C);for(let q of P)q.open=!1}if(_)break}_?i.push(u):s.push(`${u.token} has no visible ${u.property} sample that responds to its value. Hardcoded copies do not satisfy coverage.`)}return{ok:!s.length,verified:i,diagnostics:s}}var We=new Jl({name:"ADS resource preview",version:"1.0.0"},{},{autoResize:!0}),E_=document.getElementById("root"),bf=document.getElementById("ads-preview-status"),vf=async e=>{if(!We.getHostCapabilities()?.serverResources)throw new Error("This host does not forward MCP resource reads. Open the same preview in the ADS showcase.");try{return await We.readServerResource({uri:e},{timeout:15e3})}catch(t){throw t.code===-32601||t.message==="Method not found"?new Error("This host does not forward MCP resource reads. Open the same preview in the ADS showcase."):t}},Gl=new Bl(vf),Vr,ha,$_,Rf,zf=!1,vo=0,pa={},Be="";function Mt(e,t=!1){bf.textContent=e,bf.hidden=!e,bf.setAttribute("role",t?"alert":"status")}async function Wl(e,t={}){try{We.getHostCapabilities()?.logging&&await We.sendLog({level:e==="error"?"error":"info",logger:"ads-preview",data:{state:e,id:Vr,...t}})}catch{}}function C_(e){pa={...pa,...e,styles:{...pa.styles,...e.styles}},["light","dark"].includes(pa.theme)&&(document.documentElement.dataset.theme=pa.theme)}async function I_(){return JSON.parse((await vf("ads://preview/manifest")).contents.find(e=>typeof e.text=="string").text)}async function wf(e,t){if(e===Vr&&t&&t===ha){Mt(Be,!!Be),await Wl(Be?"error":"ready",Be?{message:Be}:{revision:ha});return}let o=++vo;++Gl.generation,Vr=e,document.documentElement.dataset.adsScenario=e,document.documentElement.dataset.adsPreviewState="loading",Be="",Mt(""),await Wl("loading");try{let r=await I_();if(t&&r.revision!==t)throw new Error("Preview changed while opening. Select it again to use the current build.");if(o!==vo||!await Gl.mount(r,e,E_)||o!==vo||(ha=r.revision,$_=r.session,await new Promise(i=>requestAnimationFrame(()=>requestAnimationFrame(i))),await document.fonts.ready,o!==vo))return;let s=r.scenarios.find(i=>i.id===e);if(s?.kind==="tokens"){let i=k_(E_,s.samples);if(!i.ok)throw Be=`Token preview verification failed:
104
104
  ${i.diagnostics.slice(0,20).join(`
105
105
  `)}`,new Error(Be)}o===vo&&(document.documentElement.dataset.adsPreviewState="ready",Mt(""),await Wl("ready",{revision:ha}))}catch(r){o===vo&&(document.documentElement.dataset.adsPreviewState="error",Mt(r.message,!0),await Wl("error",{message:r.message,...r.code?{code:r.code}:{}}))}}async function Gv(){if(!(!Vr||zf||document.visibilityState==="hidden")){zf=!0;try{let t=JSON.parse((await vf("ads://project/build")).contents.find(o=>typeof o.text=="string").text).previews.find(o=>o.id===Vr);if(!t||!t.current){let o=t?.diagnostics?.[0]?.message;Mt(o?`Build failed. Showing the previous preview.
106
106
  ${o}`:"Rebuilding. This is the previous preview.",!!o)}else{let o=await I_();o.revision!==ha||o.session!==$_?await wf(Vr):Mt(Be,!!Be)}}catch(e){Mt(`Preview freshness unavailable: ${e.message}`,!0)}finally{zf=!1}}}We.ontoolresult=e=>{let t=e.structuredContent;t?.scenario?.id&&wf(t.scenario.id,t.manifestRevision)};We.onhostcontextchanged=C_;We.onteardown=async()=>(++vo,clearInterval(Rf),await Gl.dispose(),{});try{if(await We.connect(),C_(We.getHostContext()??{}),!We.getHostCapabilities()?.serverResources)throw new Error("This host does not forward MCP resource reads. Open the same preview in the ADS showcase.");Rf=setInterval(()=>{Gv()},2e3);let e=document.getElementById("ads-preview-selection");e&&wf(JSON.parse(e.textContent).id)}catch(e){Mt(e.message,!0)}window.addEventListener("pagehide",()=>{clearInterval(Rf),Gl.dispose()},{once:!0});
@@ -29,7 +29,9 @@ export class PreviewResourceLoader {
29
29
  const task = (async () => {
30
30
  const record = this.records.get(uri);
31
31
  if (!record) throw new Error(`Unknown preview dependency: ${uri}`);
32
- const response = await this.read(uri);
32
+ let response;
33
+ try { response = await this.read(uri); }
34
+ catch (error) { throw new Error(`Preview resource read failed for ${uri}: ${error.message}`, { cause: error }); }
33
35
  if (this.closed) throw new Error("The preview App was closed.");
34
36
  const content = response.contents.find(item => item.uri === uri);
35
37
  if (!content) throw new Error(`Missing preview dependency: ${uri}`);
@@ -105,7 +107,8 @@ export class PreviewResourceLoader {
105
107
  document.head.append(style);
106
108
  try { await ready; } catch (error) { style.remove(); this.styles.delete(id); this.styleReady.delete(id); throw error; }
107
109
  }
108
- return import(/* @vite-ignore */ uri);
110
+ try { return await import(/* @vite-ignore */ uri); }
111
+ catch (error) { throw new Error(`Preview module import failed for ${uri}: ${error.message}. Inspect the App resource bridge, browser network/CSP diagnostics and current build.`, { cause: error }); }
109
112
  }
110
113
  async mount(manifest, id, container) {
111
114
  const generation = ++this.generation;