@reqvire-org/ads 0.3.18 → 0.3.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -0
- package/dist/behaviors.mjs +19 -9
- package/dist/bootstrap-ds/ADS.md +2 -2
- package/dist/bootstrap-ds/README.md +2 -2
- 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 → Foundation.ads.scenario.tsx} +2 -2
- package/dist/bootstrap-ds/ds.config.json +1 -1
- package/dist/bootstrap-ds/tsconfig.build.json +9 -2
- package/dist/bootstrap-ds/tsconfig.json +18 -3
- package/dist/bootstrap-ds/vite.config.ts +1 -1
- package/dist/boundaries.mjs +6 -2
- package/dist/check.mjs +44 -17
- package/dist/config.mjs +33 -5
- package/dist/contract-migration.mjs +110 -0
- package/dist/contracts.d.mts +22 -0
- package/dist/evidence.mjs +1 -1
- package/dist/index.mjs +1 -0
- package/dist/init.mjs +18 -15
- package/dist/layers.mjs +288 -0
- package/dist/lint-config.mjs +1 -1
- package/dist/mcp/catalog.mjs +9 -2
- package/dist/mcp/context.mjs +2 -1
- package/dist/mcp/edit-preparation.mjs +9 -6
- package/dist/mcp/mutations.mjs +1 -1
- package/dist/mcp/preview-widget.bundle.js +41 -39
- package/dist/mcp/prompts.mjs +1 -1
- package/dist/mcp/server.mjs +9 -3
- package/dist/mcp/widget.bundle.js +1 -1
- package/dist/oxlint.mjs +3 -1
- package/dist/paint-contracts.mjs +124 -0
- package/dist/preview-resources.mjs +12 -2
- package/dist/public-api.mjs +13 -6
- package/dist/public-props.mjs +2 -2
- package/dist/runtime-contracts.mjs +225 -0
- package/dist/scenario-trace.mjs +37 -9
- package/dist/scenarios-vite.mjs +3 -2
- package/dist/scenarios.mjs +89 -30
- package/dist/scoped-tokens.mjs +9 -4
- package/dist/showcase/client.bundle.js +1 -1
- package/dist/showcase/sandbox.bundle.js +1 -1
- package/dist/skills/ads-break-ui/SKILL.md +25 -6
- package/dist/skills/ads-break-ui/references/contracts.md +250 -0
- 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 +26 -7
- package/dist/skills/ads-designer/assets/bootstrap-ds/ADS.md +2 -2
- package/dist/skills/ads-designer/assets/bootstrap-ds/README.md +2 -2
- 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 → Foundation.ads.scenario.tsx} +2 -2
- package/dist/skills/ads-designer/assets/bootstrap-ds/ds.config.json +1 -1
- package/dist/skills/ads-designer/assets/bootstrap-ds/tsconfig.build.json +9 -2
- 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/contracts.md +250 -0
- 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 +25 -6
- package/dist/skills/ads-import/references/contracts.md +250 -0
- 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 +25 -6
- package/dist/skills/ads-migrate/references/contracts.md +250 -0
- 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 +26 -7
- package/dist/skills/ads-motion/references/audit.md +1 -1
- package/dist/skills/ads-motion/references/contracts.md +250 -0
- 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/token-origins.mjs +24 -0
- package/dist/tokens.mjs +4 -3
- package/dist/unit-contracts.mjs +203 -0
- package/dist/units.mjs +26 -17
- package/dist/verification/browser.mjs +156 -26
- package/dist/verification/playwright-reporter.cjs +13 -3
- package/docs/ADS.md +40 -6
- package/docs/ARCHITECTURE.md +73 -4
- package/docs/BEHAVIORS.md +15 -0
- package/docs/CHANGELOG.md +42 -0
- package/docs/CONTRACTS.md +250 -0
- package/docs/GUIDE.md +29 -3
- package/docs/MCP.md +42 -6
- package/package.json +4 -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/bootstrap-ds/design-system/tokens/{Foundation.fixture.ts → Foundation.ads.fixture.ts} +0 -0
- /package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/{patterns → review}/token-preview/TokenPreview.tsx +0 -0
- /package/dist/skills/ads-designer/assets/bootstrap-ds/design-system/tokens/{Foundation.fixture.ts → Foundation.ads.fixture.ts} +0 -0
package/dist/mcp/prompts.mjs
CHANGED
|
@@ -2,7 +2,7 @@ export const reasoning = `ADS is development tooling. The project owns its compo
|
|
|
2
2
|
Start with ads_status and use its projectRoot, workflow and project commands; the MCP workspace may be nested inside the client repository. If ADS tools are not exposed, read ads://project/guide then ads://project/status. Find existing public UI with ads_catalog and load one relevant ADS workflow via ads_skills/ads_read_skill. UI implementation/refinement uses ads-designer; library adaptation ads-migrate; ADS transfer ads-import; motion ads-motion; stress testing ads-break-ui. Domain-model workflows are separate. Inspect the relevant public API, local guide, token definitions and real showcase fixture before proposing changes. Keep source/instruction discovery scoped to the target project, ancestry and selected external sources; broad scans of /mnt or home are not needed to discover this ADS.
|
|
3
3
|
When ads_status.mutations.enabled, discover one owner with ads_units, read relevant ranges with ads_read_file and send only a Git unified diff through ads_apply_patch({unit,patch,dryRun}). A unit includes its own scenarios/fixtures; imported peers remain separate. tokens is the token foundation unit. Only that owner's public re-exports may change in the shared entry; preserve unrelated statements including export {};. Token manifests list companionFiles: include required foundation import changes only for canonical token files created/deleted in the same patch, preserving other imports/comments and canonical order. Use dryRun to review validation. Rejected patches leave source unchanged; fix diagnostics rather than bypassing checks with direct writes. Shared globals use foundation; guides use documentation; assets use discovered assets/* units. Intentional configuration, registered project guards/builders and rule exceptions use configuration, which validates the proposed settings and refreshes its compiler. Keep mandatory ADS gates. For large/binary diffs, pass patchFile pointing to .ds/patches/<name>.patch rather than loading bytes into context; Git binary patches require installed Git. When editing is disabled, use local editing tools.
|
|
4
4
|
Reason through Data (design-token values, semantic roles and relationships), UX (structure, behavior, accessibility, states and layout), and Skin (token-backed identity and visual states). Business data is a separate application-owned input, supplied through typed props or injected callbacks/services. The project uses Linaria extraction for owned styles; MCP adds no production runtime.
|
|
5
|
-
Choose an existing public component or pattern first. Keep application data/routing/effects in the application and fixture data/state beside its scenario. Change presentation in its owning ADS implementation, then demonstrate the same public API in the showcase. Put demo-only sizing and framing in a
|
|
5
|
+
Choose an existing public component or pattern first. Keep application data/routing/effects in the application and fixture data/state beside its scenario. Change presentation in its owning ADS implementation, then demonstrate the same public API in the showcase. Put demo-only sizing and framing in a development-only review/ helper, exported through the separate review entry rather than the production barrel; add component props for deliberate runtime API needs and preserve required behavior. Do not create a widget-only replica or override component internals.
|
|
6
6
|
For token source inspection, call ads_tokens with a focused query. For token samples, components, patterns and composition, use ads_scenarios to select a path-derived id and invoke ads_show_scenario. Showcase and MCP mount that exact entry and its colocated fixture; no page initialization is required. The showcase is a minimal tool-owned shell plus an MCP client and Apps host. Its menus come from tools; it invokes the same entry tools and refreshes the active scenario through notifications. The reusable App loads framework runtime, foundation CSS, fonts and selected modules via MCP resources inside its sandbox. Keep project execution and styles out of the outer host; preserve mount/unmount cleanup and lazy dependencies. Use the existing ads_status showcase URL, or ads showcase when no MCP runtime is connected. Projects supply scenarios/fixtures and the compiler, not browser page files or another shell. Tool metadata selects an MCP App; a resources/read of HTML alone is not proof of rendered UI. If the host has no Apps view, give the configured showcase URL and use text/resources.
|
|
7
7
|
Check ads_status.previewWatch before launching a separate preview builder. A configured watcher is managed by ads mcp. Check ads_status.build or ads://project/build after changes: running does not mean ready, and unreported freshness is not evidence of current source. Reported preview failures include structured diagnostics; fix the owning source and wait for a successful rebuild, then invoke the preview tool again. Notifications may not reach the agent, so read status explicitly. Build revisions identify reported compilation attempts, not proof of runtime/visual correctness. Do not invent replacement UI to bypass a failed preview.
|
|
8
8
|
Skills: use ads_skills to select a workflow and read its SKILL.md with ads_read_skill. Resolve relative supporting files from that skill URI. Reading instructions does not prove native skill activation or authorize unrelated actions.
|
package/dist/mcp/server.mjs
CHANGED
|
@@ -117,7 +117,7 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
117
117
|
function resources() {
|
|
118
118
|
const context = project();
|
|
119
119
|
const documents = getDocuments();
|
|
120
|
-
const promoted = new Set([context.publicEntryUri, ...context.guides.map((guide) => guide.uri)]);
|
|
120
|
+
const promoted = new Set([context.publicEntryUri, context.review.sourceUri, ...context.guides.map((guide) => guide.uri)]);
|
|
121
121
|
const skillEntries = new Set(skills.skills.map((skill) => skill.uri));
|
|
122
122
|
return [
|
|
123
123
|
{ uri: GUIDE_URI, name: "Start here: design-system workflow", description: "Read first for UI/ADS tasks, especially when only resource access is exposed. Explains the exact workspace, tool sequence, skill selection, previews, project commands and fallback.", mimeType: "text/markdown" },
|
|
@@ -125,6 +125,9 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
125
125
|
{ uri: "ads://guides/evidence", name: "Failure boundaries and compact investigations", mimeType: "text/markdown" },
|
|
126
126
|
{ uri: "ads://guides/atomic-styles", name: "Atomic styles: composition conflicts and custom-property extraction", mimeType: "text/markdown" },
|
|
127
127
|
{ uri: "ads://guides/scenarios", name: "Scenario coverage and public class-helper contracts", mimeType: "text/markdown" },
|
|
128
|
+
{ uri: "ads://guides/layers", name: "Component capabilities, pattern composition and dependency ownership", mimeType: "text/markdown" },
|
|
129
|
+
{ uri: "ads://guides/contracts", name: "Static owner contracts, typed paints and guarded migration", mimeType: "text/markdown" },
|
|
130
|
+
{ uri: "ads://guides/review", name: "Development-only review UI, token renderers and migration", mimeType: "text/markdown" },
|
|
128
131
|
{ uri: "ads://guides/captures", name: "On-demand captures and candidate visual review", mimeType: "text/markdown" },
|
|
129
132
|
{ uri: `${PROJECT_URI}/status`, name: "ADS project status", mimeType: "application/json" },
|
|
130
133
|
{ uri: BUILD_URI, name: "Preview build status and diagnostics", mimeType: "application/json" },
|
|
@@ -170,6 +173,9 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
170
173
|
if (uri === "ads://guides/evidence") return { contents: [{ uri, mimeType: "text/markdown", text: readFileSync(new URL("../skills/ads-designer/references/evidence.md", import.meta.url), "utf8") }] };
|
|
171
174
|
if (uri === "ads://guides/atomic-styles") return { contents: [{ uri, mimeType: "text/markdown", text: readFileSync(new URL("../skills/ads-designer/references/atomic-styles.md", import.meta.url), "utf8") }] };
|
|
172
175
|
if (uri === "ads://guides/scenarios") return { contents: [{ uri, mimeType: "text/markdown", text: readFileSync(new URL("../skills/ads-designer/references/scenarios.md", import.meta.url), "utf8") }] };
|
|
176
|
+
if (uri === "ads://guides/layers") return { contents: [{ uri, mimeType: "text/markdown", text: readFileSync(new URL("../skills/ads-designer/references/layers.md", import.meta.url), "utf8") }] };
|
|
177
|
+
if (uri === "ads://guides/contracts") return { contents: [{ uri, mimeType: "text/markdown", text: readFileSync(new URL("../skills/ads-designer/references/contracts.md", import.meta.url), "utf8") }] };
|
|
178
|
+
if (uri === "ads://guides/review") return { contents: [{ uri, mimeType: "text/markdown", text: readFileSync(new URL("../skills/ads-designer/references/review.md", import.meta.url), "utf8") }] };
|
|
173
179
|
if (uri === "ads://guides/captures") return { contents: [{ uri, mimeType: "text/markdown", text: readFileSync(new URL("../skills/ads-designer/references/captures.md", import.meta.url), "utf8") }] };
|
|
174
180
|
if (skills.files.has(uri)) return { contents: [skills.read(uri)] };
|
|
175
181
|
let value;
|
|
@@ -309,12 +315,12 @@ export function createAdsMcp(directory, { era = "legacy", skillDirectory, pollIn
|
|
|
309
315
|
const result = await verification.run(args, signal, progress);
|
|
310
316
|
return finishExecution("verification", {unit:args.unit,...result});
|
|
311
317
|
} },
|
|
312
|
-
{ name: "ads_units", description: "Discover editable ADS units and their files: components/patterns with colocated previews; shared controllers; composition; tokens; foundation shared files; documentation including shared credits/license notices; assets/brand, assets/fonts and asset groups; configuration with ds.config.json, package/build/lint configuration and registered project guard/compiler sources. List summaries with query, or pass an exact unit ID for its manifest. Manifests include filePolicy for supported new documentation paths and companionFiles with permitted shared-entry or owner-only style-boundary bookkeeping; preserve peer permissions and unrelated configuration. Imported peers stay separate. New owners follow the existing colocation convention.", schema: query.extend({ unit: z.string().min(1).max(256).optional() }), call: ({ unit, query: search = "", limit }) => {
|
|
318
|
+
{ name: "ads_units", description: "Discover editable ADS units and their files: components/patterns with colocated previews; shared controllers; development review/ helpers with a separate export entry; composition; tokens; foundation shared files; documentation including shared credits/license notices; assets/brand, assets/fonts and asset groups; configuration with ds.config.json, package/build/lint configuration and registered project guard/compiler sources. List summaries with query, or pass an exact unit ID for its manifest. Manifests include filePolicy for supported new documentation paths and companionFiles with permitted shared-entry or owner-only style-boundary bookkeeping; preserve peer permissions and unrelated configuration. Imported peers stay separate. New owners follow the existing colocation convention.", schema: query.extend({ unit: z.string().min(1).max(256).optional() }), call: ({ unit, query: search = "", limit }) => {
|
|
313
319
|
const matches = edits.units().filter(item => unit ? item.id === unit : item.id.toLowerCase().includes(search.toLowerCase()));
|
|
314
320
|
return json({ total: matches.length, units: matches.slice(0, limit).map(item => ({ id: item.id, kind: item.kind, fileCount: item.files.length, ...(unit ? { files: item.files.slice(0, 200), filesTruncated: item.files.length > 200, ...(item.companionFiles ? { companionFiles: item.companionFiles } : {}), ...(item.filePolicy ? { filePolicy: item.filePolicy } : {}) } : {}) })) });
|
|
315
321
|
} },
|
|
316
322
|
{ name: "ads_read_file", description: "Read a bounded range of ADS or registered configuration/extension source. Returns unit, full-file digest, startLine and nextOffset; default/max is 12000 UTF-16 characters. Binary assets return byte count/digest and no encoded content. Paths come from ads_units or discovery. Missing new paths return digest null. Read only relevant context before preparing a Git patch.", schema: z.object({ path: z.string().min(1).max(500), offset: z.number().int().min(0).default(0), limit: z.number().int().min(1).max(mutationLimits.readChars).default(mutationLimits.readChars) }).strict(), call: ({ path: file, ...range }) => json(edits.read(file, range)) },
|
|
317
|
-
...(mutations ? [{ name: "ads_prepare_patch", description: "Prepare a Git patch without a separate project mirror. Create copies only one owner plus its declared companions into a local draft; edit that directory with local file tools. Diff checks baseline/source freshness and owner paths, then writes a patchFile under .ds/patches. Discard removes the draft. Live source is never changed; ads_apply_patch still validates all gates. Remote clients can continue sending inline Git diffs instead.", schema:z.object({unit:z.string().min(1).max(256),action:z.enum(["create","diff","discard"]).default("create"),draft:z.string().max(100).optional()}).strict(), call:args=>json(prepareUnitPatch(options,args)) },
|
|
323
|
+
...(mutations ? [{ name: "ads_prepare_patch", description: "Prepare a Git patch without a separate project mirror. Create copies only one owner plus its declared companions into a local draft; edit that directory with local file tools. Diff checks baseline/source freshness and owner paths, then writes a patchFile under .ds/patches. Migrate prepares mechanical .ads. role names and legacy contracts in that draft, retaining unresolved sections for review. Discard removes the draft. Live source is never changed; ads_apply_patch still validates all gates. Remote clients can continue sending inline Git diffs instead.", schema:z.object({unit:z.string().min(1).max(256),action:z.enum(["create","migrate","diff","discard"]).default("create"),draft:z.string().max(100).optional()}).strict(), call:args=>json(prepareUnitPatch(options,args)) },
|
|
318
324
|
{name:"ads_prepare_token_samples",description:"Reconcile a token scenario's consumed literal fixture samples. Removes undeclared tokens, preserves existing project bindings, and accepts explicit real-token/property/probe bindings. Reports unresolved token names; never guesses CSS meaning from token names. Returns an owner patchFile without changing source. Apply it through ads_apply_patch and review the real preview.",schema:z.object({id:z.string().min(1).max(256),bindings:z.array(z.object({token:z.string().max(256),property:z.string().max(100),probe:z.string().max(1000)}).passthrough()).max(1000).optional()}).strict(),call:args=>{
|
|
319
325
|
const {patch,...result}=reconcileTokenSamples(options,args);
|
|
320
326
|
return json({...result,removed:result.removed.slice(0,50),unresolved:result.unresolved.slice(0,50),unresolvedCount:result.unresolved.length,...storePreparedPatch(options,patch)});
|
|
@@ -138,7 +138,7 @@ Boolean requesting whether a visible border and background is provided by the ho
|
|
|
138
138
|
- omitted: host decides border`)}),q_=i.object({method:i.literal("ui/request-display-mode"),params:i.object({mode:Sa.describe("The display mode being requested.")})}),N_=i.object({mode:Sa.describe("The display mode that was actually set. May differ from requested if not supported.")}).passthrough(),wR=i.union([i.literal("model"),i.literal("app")]).describe("Tool visibility scope - who can access the tool."),KT=i.object({resourceUri:i.string().optional(),visibility:i.array(wR).optional().describe(`Who can access this tool. Default: ["model", "app"]
|
|
139
139
|
- "model": Tool visible to and callable by the agent
|
|
140
140
|
- "app": Tool callable by the app from this server only`),csp:i.never().optional(),permissions:i.never().optional()}),GT=i.object({mimeTypes:i.array(i.string()).optional().describe('Array of supported MIME types for UI resources.\nMust include `"text/html;profile=mcp-app"` for MCP Apps support.')}),xR=i.object({method:i.literal("ui/download-file"),params:i.object({contents:i.array(i.union([ht,mt])).describe("Resource contents to download \u2014 embedded (inline data) or linked (host fetches). Uses standard MCP resource types.")})}),TR=i.object({method:i.literal("ui/message"),params:i.object({role:i.literal("user").describe('Message role, currently only "user" is supported.'),content:i.array(Ee).describe("Message content blocks (text, image, etc.).")})}),YT=i.object({method:i.literal("ui/notifications/sandbox-resource-ready"),params:i.object({html:i.string().describe("HTML content to load into the inner iframe."),sandbox:i.string().optional().describe("Optional override for the inner iframe's sandbox attribute."),csp:Ef.optional().describe("CSP configuration from resource metadata."),permissions:kf.optional().describe("Sandbox permissions from resource metadata.")})}),QT=i.object({method:i.literal("ui/notifications/tool-result"),params:Ye.describe("Standard MCP tool execution result.")}),j_=i.object({toolInfo:i.object({id:Te.optional().describe("JSON-RPC id of the tools/call request."),tool:Ge.describe("Tool definition including name, inputSchema, etc.")}).optional().describe("Metadata of the tool call that instantiated this App."),theme:cR.optional().describe("Current color theme preference."),styles:_R.optional().describe("Style configuration for theming the app."),displayMode:Sa.optional().describe("How the UI is currently displayed."),availableDisplayModes:i.array(Sa).optional().describe("Display modes the host supports."),containerDimensions:i.union([i.object({height:i.number().describe("Fixed container height in pixels.")}),i.object({maxHeight:i.union([i.number(),i.undefined()]).optional().describe("Maximum container height in pixels.")})]).and(i.union([i.object({width:i.number().describe("Fixed container width in pixels.")}),i.object({maxWidth:i.union([i.number(),i.undefined()]).optional().describe("Maximum container width in pixels.")})])).optional().describe(`Container dimensions. Represents the dimensions of the iframe or other
|
|
141
|
-
container holding the app. Specify either width or maxWidth, and either height or maxHeight.`),locale:i.string().optional().describe("User's language and region preference in BCP 47 format."),timeZone:i.string().optional().describe("User's timezone in IANA format."),userAgent:i.string().optional().describe("Host application identifier."),platform:i.union([i.literal("web"),i.literal("desktop"),i.literal("mobile")]).optional().describe("Platform type for responsive design decisions."),deviceCapabilities:i.object({touch:i.boolean().optional().describe("Whether the device supports touch input."),hover:i.boolean().optional().describe("Whether the device supports hover interactions.")}).optional().describe("Device input capabilities."),safeAreaInsets:i.object({top:i.number().describe("Top safe area inset in pixels."),right:i.number().describe("Right safe area inset in pixels."),bottom:i.number().describe("Bottom safe area inset in pixels."),left:i.number().describe("Left safe area inset in pixels.")}).optional().describe("Mobile safe area boundaries in pixels.")}).passthrough(),XT=i.object({method:i.literal("ui/notifications/host-context-changed"),params:j_.describe("Partial context update containing only changed fields.")}),PR=i.object({method:i.literal("ui/update-model-context"),params:i.object({content:i.array(Ee).optional().describe("Context content blocks (text, image, etc.)."),structuredContent:i.record(i.string(),i.unknown().describe("Structured content for machine-readable context data.")).optional().describe("Structured content for machine-readable context data.")})}),ER=i.object({method:i.literal("ui/initialize"),params:i.object({appInfo:Pe.describe("App identification (name and version)."),appCapabilities:zR.describe("Features and capabilities this app provides."),protocolVersion:i.string().describe("Protocol version this app supports.")})}),kR=i.object({protocolVersion:i.string().describe('Negotiated protocol version string (e.g., "2025-11-21").'),hostInfo:Pe.describe("Host application identification and version."),hostCapabilities:vR.describe("Features and capabilities provided by the host."),hostContext:j_.describe("Rich context about the host environment.")}).passthrough();var Ql=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=ct.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!==aR&&console.debug("Sending message",t),this.eventTarget.postMessage(t,"*")}async close(){window.removeEventListener("message",this.messageListener),this.onclose?.()}onclose;onerror;onmessage;sessionId;setProtocolVersion};var $R=[A_],Xl=class extends ma{_client;_hostInfo;_hostCapabilities;_appCapabilities;_hostContext={};_appInfo;_initializedReceived=!1;_registeredEvents=new Set;_events=new Tf;_methods=new Pf;setRequestHandler=(...t)=>{let[o,...r]=O_("request",t)??t;this._methods.claim(o,"setRequestHandler"),super.setRequestHandler.call(this,o,...r)};setNotificationHandler=(...t)=>{let[o,...r]=O_("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:gR,sandboxready:fR,initialized:RR,requestteardown:yR,loggingmessage:bo};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:ER.shape.params,result:kR},s=>this._onAppsInitialize(s)),this.replaceRequestHandler("ping",(s,a)=>(this.onping?.(s.params,a),{})),this.replaceRequestHandler("ui/request-display-mode",{params:q_.shape.params,result:N_},()=>({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:TR.shape.params,result:mR},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:dR.shape.params,result:pR},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:xR.shape.params,result:hR},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:q_.shape.params,result:N_},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:PR.shape.params,result:We},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:$R.includes(o)?o:A_,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],a=t[n];CR(s,a)||(o[n]=a,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},bR,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 CR(e,t){return JSON.stringify(e)===JSON.stringify(t)}function L_(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 n=o.querySelector("base")??o.createElement("base");n.setAttribute("href","about:srcdoc"),r.after(n)}return"<!doctype html>"+o.documentElement.outerHTML}var $e=new Yl({name:"ADS preview",version:"1.0.0"},{},{autoResize:!0}),Wr=document.getElementById("ads-token-root"),Lt=document.getElementById("ads-widget-status"),tu=document.getElementById("ads-token-theme"),eu=document.getElementById("ads-scenario-root"),To,Me=0,U_="",Br=30,Z_=document.createElement("style");Wr&&document.head.append(Z_);function ou(e){e!=="light"&&e!=="dark"||(document.documentElement.dataset.theme=e,tu&&(tu.value=e),Wr&&To&&wo())}function Oe(e,t){let o=document.createElement(e);return o.textContent=t,o}function IR(e){let t=Oe("li","");t.dataset.token=e.name;let o=getComputedStyle(document.documentElement).getPropertyValue(e.name).trim(),r=Oe("details","");r.className="token-source";let n=Oe("summary","");n.title=`${e.name}: ${o||"Conditional or unset"}. Click to inspect source definitions.`;let s=Oe("code",e.name);s.className="token-name";let a=Oe("span",o||"Conditional or unset");n.append(s,a);let l=Oe("dl","");l.append(Oe("dt","Computed in preview"),Oe("dd",o||"Conditional or unset")),e.meaning&&l.append(Oe("dt","Meaning"),Oe("dd",e.meaning));for(let u of e.definitions)l.append(Oe("dt",u.scope),Oe("dd",`${u.value} \xB7 ${u.file}:${u.line}`));return r.append(n,l),t.append(r),t}function wo(){Z_.textContent=To.css;let e=To.tokens.filter(t=>`${t.name} ${t.type??""}`.toLowerCase().includes(U_.toLowerCase()));document.getElementById("ads-token-list").replaceChildren(...e.slice(Me,Me+Br).map(IR)),document.querySelector("footer").hidden=e.length<=Br,document.getElementById("ads-token-count").textContent=e.length?`${Me+1}\u2013${Math.min(Me+Br,e.length)} of ${e.length}`:"No matching tokens",document.getElementById("ads-token-prev").disabled=Me===0,document.getElementById("ads-token-next").disabled=Me+Br>=e.length,Lt.textContent=`${e.length} matching definitions \xB7 ${To.diagnostics.length} diagnostics`}Wr&&($e.ontoolinput=()=>{Me=0,To&&wo()},To=JSON.parse(document.getElementById("ads-token-seed").textContent),ou("light"),document.getElementById("ads-token-search").addEventListener("input",e=>{U_=e.target.value,Me=0,wo()}),tu.addEventListener("change",()=>ou(tu.value)),document.getElementById("ads-token-prev").addEventListener("click",()=>{Me=Math.max(0,Me-Br),wo()}),document.getElementById("ads-token-next").addEventListener("click",()=>{Me+=Br,wo()}),document.getElementById("ads-token-refresh").addEventListener("click",async()=>{try{let e=await $e.readServerResource({uri:"ads://project/tokens"});To=JSON.parse(e.contents.find(t=>"text"in t).text),Me=0,wo()}catch(e){Lt.textContent=`Refresh failed: ${e.message}`}}));var xo,$f,D_=!1,_a=0;async function H_(e){if($f=e,!D_||!eu)return;let t=e?.structuredContent?.scenario?.resourceUri;if(!t||!t.startsWith("ui://ads/previews/"))return;let o=++_a;Wr&&(Wr.hidden=!0,Lt=document.getElementById("ads-scenario-status")),eu.hidden=!1;try{if(!$e.getHostCapabilities()?.serverResources)throw new Error("This Apps host does not forward server resources. Use the project showcase URL from the tool result.");let n=(await $e.readServerResource({uri:t})).contents.find(u=>u.mimeType==="text/html;profile=mcp-app"&&typeof u.text=="string");if(!n)throw new Error("Scenario HTML is unavailable.");if(o!==_a)return;xo&&(await xo.close(),xo=void 0);let s=document.createElement("iframe");s.title=e.structuredContent.scenario.title,s.setAttribute("sandbox","allow-scripts"),eu.replaceChildren(s);let a=new Xl(null,{name:"ADS scenario host",version:"1.0.0"},{serverResources:{},serverTools:{}},{hostContext:$e.getHostContext()});xo=a,a.onreadresource=u=>$e.readServerResource(u),a.oncalltool=u=>$e.callServerTool(u),a.onsizechange=({height:u})=>{u&&(s.height=String(Math.min(1e4,Math.max(120,u))))},a.oninitialized=async()=>{await a.sendToolInput({arguments:{}}),await a.sendToolResult(e),o===_a&&window.parent.postMessage({type:"ads-preview-ready"},"*")},await a.connect(new Ql(s.contentWindow,s.contentWindow));let l="default-src 'none'; script-src 'unsafe-inline' blob:; style-src 'unsafe-inline'; img-src data: blob:; font-src data:; worker-src blob: data:; connect-src 'none'; base-uri about:; object-src 'none'";s.srcdoc=L_(n.text,l),Lt.hidden=!0}catch(r){o===_a&&(Lt.hidden=!1,Lt.textContent=r.message,window.parent.postMessage({type:"ads-preview-ready"},"*"))}}eu&&($e.ontoolresult=e=>{H_(e)},$e.onteardown=async()=>{++_a;let e=xo;xo=void 0;try{await e?.teardownResource({},{timeout:500})}catch{}return await e?.close(),{}});$e.onhostcontextchanged=e=>{ou(e.theme),xo?.setHostContext(e)};try{await $e.connect(),D_=!0,$f&&H_($f),ou($e.getHostContext()?.theme),Wr&&(document.getElementById("ads-token-refresh").disabled=!$e.getHostCapabilities()?.serverResources,wo())}catch{Lt&&(Lt.textContent="Apps bridge unavailable. Samples remain usable; inspect project data through ADS tools/resources.")}
|
|
141
|
+
container holding the app. Specify either width or maxWidth, and either height or maxHeight.`),locale:i.string().optional().describe("User's language and region preference in BCP 47 format."),timeZone:i.string().optional().describe("User's timezone in IANA format."),userAgent:i.string().optional().describe("Host application identifier."),platform:i.union([i.literal("web"),i.literal("desktop"),i.literal("mobile")]).optional().describe("Platform type for responsive design decisions."),deviceCapabilities:i.object({touch:i.boolean().optional().describe("Whether the device supports touch input."),hover:i.boolean().optional().describe("Whether the device supports hover interactions.")}).optional().describe("Device input capabilities."),safeAreaInsets:i.object({top:i.number().describe("Top safe area inset in pixels."),right:i.number().describe("Right safe area inset in pixels."),bottom:i.number().describe("Bottom safe area inset in pixels."),left:i.number().describe("Left safe area inset in pixels.")}).optional().describe("Mobile safe area boundaries in pixels.")}).passthrough(),XT=i.object({method:i.literal("ui/notifications/host-context-changed"),params:j_.describe("Partial context update containing only changed fields.")}),PR=i.object({method:i.literal("ui/update-model-context"),params:i.object({content:i.array(Ee).optional().describe("Context content blocks (text, image, etc.)."),structuredContent:i.record(i.string(),i.unknown().describe("Structured content for machine-readable context data.")).optional().describe("Structured content for machine-readable context data.")})}),ER=i.object({method:i.literal("ui/initialize"),params:i.object({appInfo:Pe.describe("App identification (name and version)."),appCapabilities:zR.describe("Features and capabilities this app provides."),protocolVersion:i.string().describe("Protocol version this app supports.")})}),kR=i.object({protocolVersion:i.string().describe('Negotiated protocol version string (e.g., "2025-11-21").'),hostInfo:Pe.describe("Host application identification and version."),hostCapabilities:vR.describe("Features and capabilities provided by the host."),hostContext:j_.describe("Rich context about the host environment.")}).passthrough();var Ql=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=ct.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!==aR&&console.debug("Sending message",t),this.eventTarget.postMessage(t,"*")}async close(){window.removeEventListener("message",this.messageListener),this.onclose?.()}onclose;onerror;onmessage;sessionId;setProtocolVersion};var $R=[A_],Xl=class extends ma{_client;_hostInfo;_hostCapabilities;_appCapabilities;_hostContext={};_appInfo;_initializedReceived=!1;_registeredEvents=new Set;_events=new Tf;_methods=new Pf;setRequestHandler=(...t)=>{let[o,...r]=O_("request",t)??t;this._methods.claim(o,"setRequestHandler"),super.setRequestHandler.call(this,o,...r)};setNotificationHandler=(...t)=>{let[o,...r]=O_("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:gR,sandboxready:fR,initialized:RR,requestteardown:yR,loggingmessage:bo};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:ER.shape.params,result:kR},s=>this._onAppsInitialize(s)),this.replaceRequestHandler("ping",(s,a)=>(this.onping?.(s.params,a),{})),this.replaceRequestHandler("ui/request-display-mode",{params:q_.shape.params,result:N_},()=>({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:TR.shape.params,result:mR},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:dR.shape.params,result:pR},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:xR.shape.params,result:hR},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:q_.shape.params,result:N_},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:PR.shape.params,result:We},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:$R.includes(o)?o:A_,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],a=t[n];CR(s,a)||(o[n]=a,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},bR,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 CR(e,t){return JSON.stringify(e)===JSON.stringify(t)}function L_(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 n=o.querySelector("base")??o.createElement("base");n.setAttribute("href","about:srcdoc"),r.after(n)}return"<!doctype html>"+o.documentElement.outerHTML}var $e=new Yl({name:"ADS preview",version:"1.0.0"},{},{autoResize:!0}),Wr=document.getElementById("ads-token-root"),Lt=document.getElementById("ads-widget-status"),tu=document.getElementById("ads-token-theme"),eu=document.getElementById("ads-scenario-root"),To,Me=0,U_="",Br=30,Z_=document.createElement("style");Wr&&document.head.append(Z_);function ou(e){e!=="light"&&e!=="dark"||(document.documentElement.dataset.theme=e,tu&&(tu.value=e),Wr&&To&&wo())}function Oe(e,t){let o=document.createElement(e);return o.textContent=t,o}function IR(e){let t=Oe("li","");t.dataset.token=e.name;let o=getComputedStyle(document.documentElement).getPropertyValue(e.name).trim(),r=Oe("details","");r.className="token-source";let n=Oe("summary","");n.title=`${e.name}: ${o||"Conditional or unset"}. Click to inspect source definitions.`;let s=Oe("code",e.name);s.className="token-name";let a=Oe("span",o||"Conditional or unset");n.append(s,a);let l=Oe("dl","");l.append(Oe("dt","Computed in preview"),Oe("dd",o||"Conditional or unset")),e.meaning&&l.append(Oe("dt","Meaning"),Oe("dd",e.meaning));for(let u of e.definitions)l.append(Oe("dt",u.scope),Oe("dd",`${u.value} \xB7 ${u.file}:${u.line}`));return r.append(n,l),t.append(r),t}function wo(){Z_.textContent=To.css;let e=To.tokens.filter(t=>`${t.name} ${t.type??""}`.toLowerCase().includes(U_.toLowerCase()));document.getElementById("ads-token-list").replaceChildren(...e.slice(Me,Me+Br).map(IR)),document.querySelector("footer").hidden=e.length<=Br,document.getElementById("ads-token-count").textContent=e.length?`${Me+1}\u2013${Math.min(Me+Br,e.length)} of ${e.length}`:"No matching tokens",document.getElementById("ads-token-prev").disabled=Me===0,document.getElementById("ads-token-next").disabled=Me+Br>=e.length,Lt.textContent=`${e.length} matching definitions \xB7 ${To.diagnostics.length} diagnostics`}Wr&&($e.ontoolinput=()=>{Me=0,To&&wo()},To=JSON.parse(document.getElementById("ads-token-seed").textContent),ou("light"),document.getElementById("ads-token-search").addEventListener("input",e=>{U_=e.target.value,Me=0,wo()}),tu.addEventListener("change",()=>ou(tu.value)),document.getElementById("ads-token-prev").addEventListener("click",()=>{Me=Math.max(0,Me-Br),wo()}),document.getElementById("ads-token-next").addEventListener("click",()=>{Me+=Br,wo()}),document.getElementById("ads-token-refresh").addEventListener("click",async()=>{try{let e=await $e.readServerResource({uri:"ads://project/tokens"});To=JSON.parse(e.contents.find(t=>"text"in t).text),Me=0,wo()}catch(e){Lt.textContent=`Refresh failed: ${e.message}`}}));var xo,$f,D_=!1,_a=0;async function H_(e){if($f=e,!D_||!eu)return;let t=e?.structuredContent?.scenario?.resourceUri;if(!t||!t.startsWith("ui://ads/previews/"))return;let o=++_a;Wr&&(Wr.hidden=!0,Lt=document.getElementById("ads-scenario-status")),eu.hidden=!1;try{if(!$e.getHostCapabilities()?.serverResources)throw new Error("This Apps host does not forward server resources. Use the project showcase URL from the tool result.");let n=(await $e.readServerResource({uri:t})).contents.find(u=>u.mimeType==="text/html;profile=mcp-app"&&typeof u.text=="string");if(!n)throw new Error("Scenario HTML is unavailable.");if(o!==_a)return;xo&&(await xo.close(),xo=void 0);let s=document.createElement("iframe");s.title=e.structuredContent.scenario.title,s.setAttribute("sandbox","allow-scripts allow-forms"),eu.replaceChildren(s);let a=new Xl(null,{name:"ADS scenario host",version:"1.0.0"},{serverResources:{},serverTools:{}},{hostContext:$e.getHostContext()});xo=a,a.onreadresource=u=>$e.readServerResource(u),a.oncalltool=u=>$e.callServerTool(u),a.onsizechange=({height:u})=>{u&&(s.height=String(Math.min(1e4,Math.max(120,u))))},a.oninitialized=async()=>{await a.sendToolInput({arguments:{}}),await a.sendToolResult(e),o===_a&&window.parent.postMessage({type:"ads-preview-ready"},"*")},await a.connect(new Ql(s.contentWindow,s.contentWindow));let l="default-src 'none'; script-src 'unsafe-inline' blob:; style-src 'unsafe-inline'; img-src data: blob:; font-src data:; worker-src blob: data:; connect-src 'none'; base-uri about:; object-src 'none'; form-action 'none'";s.srcdoc=L_(n.text,l),Lt.hidden=!0}catch(r){o===_a&&(Lt.hidden=!1,Lt.textContent=r.message,window.parent.postMessage({type:"ads-preview-ready"},"*"))}}eu&&($e.ontoolresult=e=>{H_(e)},$e.onteardown=async()=>{++_a;let e=xo;xo=void 0;try{await e?.teardownResource({},{timeout:500})}catch{}return await e?.close(),{}});$e.onhostcontextchanged=e=>{ou(e.theme),xo?.setHostContext(e)};try{await $e.connect(),D_=!0,$f&&H_($f),ou($e.getHostContext()?.theme),Wr&&(document.getElementById("ads-token-refresh").disabled=!$e.getHostCapabilities()?.serverResources,wo())}catch{Lt&&(Lt.textContent="Apps bridge unavailable. Samples remain usable; inspect project data through ADS tools/resources.")}
|
|
142
142
|
/*! Bundled license information:
|
|
143
143
|
|
|
144
144
|
@modelcontextprotocol/client/dist/src-WCy6ifGf.mjs:
|
package/dist/oxlint.mjs
CHANGED
|
@@ -9,6 +9,8 @@ const schema = [{
|
|
|
9
9
|
dsDir: { type: "string" },
|
|
10
10
|
sourceDir: { type: "string" },
|
|
11
11
|
publicAlias: { type: "string" },
|
|
12
|
+
publicEntry: { type: "string" },
|
|
13
|
+
reviewEntry: { type: "string" },
|
|
12
14
|
stylesEntry: { type: "string" },
|
|
13
15
|
inlineStyleFiles: { type: "array", items: { type: "string" } },
|
|
14
16
|
configurationFiles: { type: "array", items: { type: "string" } },
|
|
@@ -66,7 +68,7 @@ export default {
|
|
|
66
68
|
},
|
|
67
69
|
},
|
|
68
70
|
"no-inline-style": {
|
|
69
|
-
meta: { type: "problem", schema, messages: { inline: "Own visual styles in component/pattern Linaria. Put demo-only sizing and framing in a
|
|
71
|
+
meta: { type: "problem", schema, messages: { inline: "Own visual styles in component/pattern Linaria. Put demo-only sizing and framing in a development-only review/ helper. Reserve inlineStyleFiles for documented measured geometry; add component props for deliberate product API needs." } },
|
|
70
72
|
create(context) {
|
|
71
73
|
const { root, config } = options(context);
|
|
72
74
|
const allowed = config.inlineStyleFiles.some((file) => path.resolve(root, file) === path.resolve(context.getFilename()));
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import ts from "typescript";
|
|
3
|
+
import valueParser from "postcss-value-parser";
|
|
4
|
+
import { isContractFile, isInside } from "./config.mjs";
|
|
5
|
+
import { inspectUnitContracts, implementationIdentity, runtimeBindingOwner } from "./unit-contracts.mjs";
|
|
6
|
+
import { createSourceValues, reference } from "./source-values.mjs";
|
|
7
|
+
|
|
8
|
+
export function paintReference(text) {
|
|
9
|
+
if (typeof text !== "string" || text.length > 1000) return null;
|
|
10
|
+
text=text.trim();
|
|
11
|
+
if (["none", "currentColor"].includes(text)) return {kind:"keyword",value:text};
|
|
12
|
+
const nodes = valueParser(text).nodes.filter(node => !["space","comment"].includes(node.type));
|
|
13
|
+
if (nodes.length !== 1 || nodes[0].type !== "function" || nodes[0].unclosed) return null;
|
|
14
|
+
const node=nodes[0], children=node.nodes.filter(item => !["space","comment"].includes(item.type));
|
|
15
|
+
if (node.value === "var" && children.length === 1 && children[0].type === "word" && /^--[\w-]+$/.test(children[0].value)) return {kind:"variable",name:children[0].value};
|
|
16
|
+
if (node.value === "url" && children.length === 1 && ["word","string"].includes(children[0].type) && /^#[^\s"'()#\\\p{Cc}]+$/u.test(children[0].value)) return {kind:"resource",id:children[0].value.slice(1)};
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function supportedType(type, seen = new Set()) {
|
|
21
|
+
if (!type || seen.has(type)) return false;
|
|
22
|
+
seen=new Set([...seen,type]);
|
|
23
|
+
if (type.flags & ts.TypeFlags.Undefined) return true;
|
|
24
|
+
if (type.flags & ts.TypeFlags.StringLiteral) return !!paintReference(type.value);
|
|
25
|
+
if (type.isUnion()) return type.types.every(item=>supportedType(item,seen));
|
|
26
|
+
if (type.flags & ts.TypeFlags.TemplateLiteral) return type.texts.length===2 && (type.texts[0]==="var(--"||type.texts[0]==="url(#") && type.texts[1]===")" && type.types.every(item=>!!(item.flags & (ts.TypeFlags.String|ts.TypeFlags.StringLiteral)));
|
|
27
|
+
return false;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function inspectPaintContracts(options) {
|
|
31
|
+
if(options.paintContracts)return options.paintContracts;
|
|
32
|
+
const declarations=inspectUnitContracts(options), result={allowedProperties:new Set(),diagnostics:[],contracts:declarations.contracts};
|
|
33
|
+
options.paintContracts=result;
|
|
34
|
+
if(!declarations.program)return result;
|
|
35
|
+
// TypeScript source identities use absolute paths, independently of process cwd.
|
|
36
|
+
for(const contract of declarations.contracts)for(const input of contract.paintInputs??[]) {
|
|
37
|
+
const checker=declarations.program.getTypeChecker();
|
|
38
|
+
const source=declarations.program.getSourceFile(path.resolve(options.root,input.api.source));
|
|
39
|
+
let declaration;
|
|
40
|
+
function find(node){if(node.pos===input.api.position&&(ts.isFunctionDeclaration(node)||ts.isVariableDeclaration(node)||ts.isClassDeclaration(node)))declaration=node;else ts.forEachChild(node,find);}
|
|
41
|
+
if(source)find(source);
|
|
42
|
+
const signature=declaration&&checker.getTypeAtLocation(declaration.name).getCallSignatures()[0];
|
|
43
|
+
let values=signature?.parameters[0]?[{type:checker.getTypeOfSymbolAtLocation(signature.parameters[0],declaration)}]:[];
|
|
44
|
+
for(const segment of input.property.split(".")) {
|
|
45
|
+
const next=[];
|
|
46
|
+
for(const item of values)for(const type of item.type.isUnion()?item.type.types:[item.type]){
|
|
47
|
+
if(type.flags & (ts.TypeFlags.Undefined|ts.TypeFlags.Null))continue;
|
|
48
|
+
if(segment==="*") {
|
|
49
|
+
const indexed=type.getStringIndexType()??type.getNumberIndexType();
|
|
50
|
+
if(indexed)next.push({type:indexed});else for(const prop of type.getProperties())next.push({type:checker.getTypeOfSymbolAtLocation(prop,prop.valueDeclaration??declaration),symbol:prop});
|
|
51
|
+
}else {const prop=type.getProperty(segment);if(prop)next.push({type:checker.getTypeOfSymbolAtLocation(prop,prop.valueDeclaration??declaration),symbol:prop});}
|
|
52
|
+
}
|
|
53
|
+
values=next;
|
|
54
|
+
}
|
|
55
|
+
if(!values.length||values.some(value=>!supportedType(value.type)))result.diagnostics.push({file:contract.file,rule:"paint-contract",line:1,column:1,message:`${input.apiName}.${input.property} must resolve to constrained token/local-resource paint references, not string, any or unknown. A declared role does not waive the actual type contract.`});
|
|
56
|
+
else for(const value of values)for(const node of value.symbol?.declarations??[])result.allowedProperties.add(`${node.getSourceFile().fileName}:${node.pos}`);
|
|
57
|
+
}
|
|
58
|
+
return result;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const paintProp=/^(?:background(?:Color)?|color|fill|stroke|stopColor|stop-color|floodColor|flood-color|(?:\w+)?ColorToken)$/i;
|
|
62
|
+
|
|
63
|
+
/** Check resolved values at the consuming source, so exports cannot launder privacy. */
|
|
64
|
+
export function inspectTokenValueFlows(program, options, inventory, scoped, report) {
|
|
65
|
+
if(!scoped.definitions.length && !inspectUnitContracts(options).contracts.some(contract=>contract.paintInputs?.length))return;
|
|
66
|
+
const values=createSourceValues(program,options), checker=program.getTypeChecker();
|
|
67
|
+
const declarations=inspectUnitContracts(options).contracts;
|
|
68
|
+
function strings(input, context=values.state()) {
|
|
69
|
+
const value=values.resolve(input,context);
|
|
70
|
+
if(value.kind==="literal")return typeof value.value==="string"?[value.value]:[];
|
|
71
|
+
if(value.kind==="union")return value.values.slice(0,32).flatMap(item=>strings(item,values.next(context)));
|
|
72
|
+
if(value.kind==="node"&&ts.isTemplateExpression(value.node)) {
|
|
73
|
+
let result=[value.node.head.text];
|
|
74
|
+
for(const span of value.node.templateSpans) {const parts=strings(reference(span.expression,value.env,value.via),values.next(context));result=result.flatMap(prefix=>parts.map(part=>prefix+part+span.literal.text)).slice(0,32);}
|
|
75
|
+
return result;
|
|
76
|
+
}
|
|
77
|
+
if(value.kind==="node"&&ts.isBinaryExpression(value.node)&&value.node.operatorToken.kind===ts.SyntaxKind.PlusToken) {
|
|
78
|
+
const left=strings(reference(value.node.left,value.env,value.via),values.next(context)),right=strings(reference(value.node.right,value.env,value.via),values.next(context));
|
|
79
|
+
return left.flatMap(first=>right.map(second=>first+second)).slice(0,32);
|
|
80
|
+
}
|
|
81
|
+
return [];
|
|
82
|
+
}
|
|
83
|
+
for(const source of program.getSourceFiles()) {
|
|
84
|
+
if(!isInside(options.root,source.fileName)||source.fileName.includes(`${path.sep}node_modules${path.sep}`)||isContractFile(source.fileName))continue;
|
|
85
|
+
const owner=scoped.inventory.owner(scoped.inventory.relative(path.relative(options.root,source.fileName)));
|
|
86
|
+
const emitted=new Set();
|
|
87
|
+
function check(node,text,bare=false,requirePaint=false,allowedBindings=null) {
|
|
88
|
+
const names=bare&&/^--[\w-]+$/.test(text)?[text]:valueParser(text).nodes.flatMap(function walk(value){return value.type==="function"&&value.value==="var"?[value.nodes.find(item=>item.type==="word")?.value]:value.nodes?value.nodes.flatMap(walk):[];}).filter(Boolean);
|
|
89
|
+
for(const name of names) {
|
|
90
|
+
const tokenOwner=scoped.definitions.find(entry=>name.startsWith(entry.namespace));
|
|
91
|
+
const binding=runtimeBindingOwner(options,name);
|
|
92
|
+
const issue=tokenOwner&&tokenOwner.unit!==owner?`${name} is private to ${tokenOwner.unit}; ${owner??"application source"} cannot consume it through props, aliases or helpers. Promote a cross-owner design value to shared tokens.`:binding&&(binding.unit!==owner||allowedBindings&&!allowedBindings.includes(binding.id))?`${name} is an instance binding private to ${binding.unit} and is not permitted by this consuming paint input.`:requirePaint&&!inventory.tokens.some(token=>token.name===name)&&!binding?`Undefined paint reference ${name}. Declare a real accessible token or a scoped runtime binding.`:null;
|
|
93
|
+
if(issue&&!emitted.has(`${node.pos}:${name}`)) {emitted.add(`${node.pos}:${name}`);const p=source.getLineAndCharacterOfPosition(node.getStart());report(source.fileName,"token-reference",issue,p.line+1,p.character+1);}
|
|
94
|
+
}
|
|
95
|
+
if(requirePaint&&!paintReference(text)) {const p=source.getLineAndCharacterOfPosition(node.getStart());report(source.fileName,"paint-reference",`Invalid paint reference ${JSON.stringify(text)}. Use an accessible token, declared scoped binding, local SVG paint resource, none or currentColor.`,p.line+1,p.character+1);}
|
|
96
|
+
}
|
|
97
|
+
function visit(node) {
|
|
98
|
+
if(ts.isCallExpression(node)&&ts.isPropertyAccessExpression(node.expression)&&["getPropertyValue","setProperty","removeProperty"].includes(node.expression.name.text)) {
|
|
99
|
+
for(const text of strings(reference(node.arguments[0])))check(node,text,true);
|
|
100
|
+
if(node.expression.name.text==="setProperty")for(const text of strings(reference(node.arguments[1])))check(node,text);
|
|
101
|
+
}
|
|
102
|
+
if(ts.isBinaryExpression(node)&&node.operatorToken.kind===ts.SyntaxKind.EqualsToken&&ts.isPropertyAccessExpression(node.left)&&ts.isPropertyAccessExpression(node.left.expression)&&node.left.expression.name.text==="style")for(const text of strings(reference(node.right)))check(node,text);
|
|
103
|
+
if(ts.isJsxOpeningElement(node)||ts.isJsxSelfClosingElement(node)) {
|
|
104
|
+
const attributes=values.props(node,new Map(),[]);
|
|
105
|
+
for(const property of values.keys(attributes,values.state())) {
|
|
106
|
+
if(paintProp.test(property))for(const text of strings(values.get(attributes,property,values.state())))check(node,text);
|
|
107
|
+
if(property==="style") {
|
|
108
|
+
const styles=values.get(attributes,"style",values.state());
|
|
109
|
+
for(const property of values.keys(styles,values.state()))for(const text of strings(values.get(styles,property,values.state())))check(node,text);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
let symbol=checker.getSymbolAtLocation(node.tagName);if(symbol?.flags&ts.SymbolFlags.Alias)symbol=checker.getAliasedSymbol(symbol);
|
|
113
|
+
const declaration=symbol?.valueDeclaration;
|
|
114
|
+
if(declaration)for(const contract of declarations)for(const input of contract.paintInputs??[])if(input.api.identity===implementationIdentity(declaration,options.root)) {
|
|
115
|
+
let current=[values.props(node,new Map(),[])];
|
|
116
|
+
for(const segment of input.property.split("."))current=current.flatMap(value=>segment==="*"?values.arrays(value,values.state())??(values.keys(value,values.state())??[]).map(key=>values.get(value,key,values.state())):[values.get(value,segment,values.state())]);
|
|
117
|
+
for(const value of current)for(const text of strings(value))check(node,text,false,true,input.bindings??[]);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
ts.forEachChild(node,visit);
|
|
121
|
+
}
|
|
122
|
+
visit(source);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -3,7 +3,7 @@ import { existsSync, mkdirSync, readFileSync, readdirSync, realpathSync, renameS
|
|
|
3
3
|
import path from "node:path";
|
|
4
4
|
import ts from "typescript";
|
|
5
5
|
import valueParser from "postcss-value-parser";
|
|
6
|
-
import { isInside } from "./config.mjs";
|
|
6
|
+
import { isInside, loadConfig } from "./config.mjs";
|
|
7
7
|
import { previewArtifactPath, previewManifestPath } from "./preview-scope.mjs";
|
|
8
8
|
|
|
9
9
|
export const PREVIEW_APP_URI = "ui://ads/preview";
|
|
@@ -21,9 +21,19 @@ function atomic(file, bytes) {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
/** Registry entries remain project-owned; these virtual entries normalize mount/unmount. */
|
|
24
|
+
export function configureReviewAlias(config, { dsRoot, config: ads }) {
|
|
25
|
+
const aliases = config.resolve?.alias ?? [];
|
|
26
|
+
const existing = Array.isArray(aliases) ? aliases : Object.entries(aliases).map(([find, replacement]) => ({ find, replacement }));
|
|
27
|
+
config.resolve = { ...config.resolve, alias: [{ find: `${ads.publicAlias}/review`, replacement: path.join(dsRoot, ads.reviewEntry) }, ...existing] };
|
|
28
|
+
}
|
|
29
|
+
|
|
24
30
|
export function previewEntriesPlugin(entries, mountEntry) {
|
|
25
31
|
const ids = new Map(entries.map((entry, index) => [`ads-preview-entry:${index}`, entry]));
|
|
26
32
|
return { name: "ads-preview-resource-entries",
|
|
33
|
+
config(config) {
|
|
34
|
+
if (!entries[0]?.root) return;
|
|
35
|
+
configureReviewAlias(config, loadConfig(entries[0].root));
|
|
36
|
+
},
|
|
27
37
|
resolveId(id) { if (ids.has(id)) return `\0${id}`; },
|
|
28
38
|
load(id) {
|
|
29
39
|
const entry = ids.get(id.slice(1));
|
|
@@ -115,7 +125,7 @@ export function createPreviewResources(output, options, entries) {
|
|
|
115
125
|
const scenarios = entries.map((entry, index) => {
|
|
116
126
|
const chunk = bundle.find(item => item.type === "chunk" && item.facadeModuleId === `\0ads-preview-entry:${index}`);
|
|
117
127
|
if (!chunk) throw new Error(`Missing compiled scenario entry: ${entry.id}`);
|
|
118
|
-
return { id: entry.id, title: entry.title, kind: entry.kind, ...(entry.samples ? { samples: entry.samples } : {}), module: uris.get(chunk.fileName) };
|
|
128
|
+
return { id: entry.id, title: entry.title, kind: entry.kind, ...(entry.samples ? { samples: entry.samples } : {}), ...(entry.paintBindings?.length ? {paintBindings:entry.paintBindings} : {}), ...(entry.paintAudit?{paintAudit:true}:{}), module: uris.get(chunk.fileName) };
|
|
119
129
|
});
|
|
120
130
|
const graph = { schemaVersion: 1, framework: options.config.framework, assets: records, scenarios };
|
|
121
131
|
const revision = hash(JSON.stringify(graph));
|
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/public-props.mjs
CHANGED
|
@@ -18,7 +18,7 @@ function stringKeys(node, checker, seen = new Set()) {
|
|
|
18
18
|
|
|
19
19
|
// Inspect locally declared exported *Props APIs without loading React at runtime.
|
|
20
20
|
// Follow local aliases and inheritance, and distinguish Omit/Pick's actual keys.
|
|
21
|
-
export function inspectPublicProps(source, checker, report) {
|
|
21
|
+
export function inspectPublicProps(source, checker, report, allowedPaintProperties = new Set()) {
|
|
22
22
|
function inspect(node, excluded = new Set(), seen = new Set()) {
|
|
23
23
|
if (!node || seen.has(node)) return;
|
|
24
24
|
seen = new Set([...seen, node]);
|
|
@@ -26,7 +26,7 @@ export function inspectPublicProps(source, checker, report) {
|
|
|
26
26
|
const name = propertyName(node.name);
|
|
27
27
|
if (excluded.has(name)) return;
|
|
28
28
|
if (name === "style") report(node, "public-style-prop", "Public ADS props must omit style. Use semantic props and documented token hooks.");
|
|
29
|
-
if (visualName.test(name ?? "") && arbitraryString(node.type, new Set())) report(node, "visual-prop", "Public visual props must use semantic variants
|
|
29
|
+
if (visualName.test(name ?? "") && arbitraryString(node.type, new Set()) && !allowedPaintProperties.has(`${node.getSourceFile().fileName}:${node.pos}`)) report(node, "visual-prop", "Public visual props must use semantic variants, a finite token union or an owner-registered constrained paint-reference contract, not arbitrary strings.");
|
|
30
30
|
return;
|
|
31
31
|
}
|
|
32
32
|
if (ts.isTypeReferenceNode(node) || ts.isExpressionWithTypeArguments(node)) {
|