@ggui-ai/ui-gen 0.9.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/generation-dispatch.d.ts +9 -2
- package/dist/adapters/generation-dispatch.js +693 -89
- package/dist/adapters/generation-dispatch.js.map +1 -1
- package/dist/adapters/index.js +28 -11
- package/dist/adapters/index.js.map +1 -1
- package/dist/advanced/index.d.ts +5 -3
- package/dist/advanced/index.js +714 -101
- package/dist/advanced/index.js.map +1 -1
- package/dist/blueprint-validator.js +267 -3
- package/dist/blueprint-validator.js.map +1 -1
- package/dist/boilerplate.js +14 -2
- package/dist/boilerplate.js.map +1 -1
- package/dist/check/index.d.ts +1 -1
- package/dist/check/index.js +11 -1
- package/dist/check/index.js.map +1 -1
- package/dist/{create-ui-generator-B9kV4U9h.d.ts → create-ui-generator-CF0EL0Li.d.ts} +13 -1
- package/dist/{dispatch-Deo9qzrG.d.ts → dispatch-D75M-OfZ.d.ts} +1 -1
- package/dist/evaluation/axis-checks/index.d.ts +3 -3
- package/dist/evaluation/axis-checks/index.js +101 -0
- package/dist/evaluation/axis-checks/index.js.map +1 -1
- package/dist/evaluation/axis-checks/registry.d.ts +1 -1
- package/dist/evaluation/axis-checks/registry.js +101 -0
- package/dist/evaluation/axis-checks/registry.js.map +1 -1
- package/dist/evaluation/index.d.ts +46 -3
- package/dist/evaluation/index.js +358 -17
- package/dist/evaluation/index.js.map +1 -1
- package/dist/harness/check/runtime-render/index.d.ts +2 -2
- package/dist/harness/check/runtime-render/index.js +268 -3
- package/dist/harness/check/runtime-render/index.js.map +1 -1
- package/dist/harness/check/runtime-render/render-check-worker.d.ts +2 -0
- package/dist/harness/check/runtime-render/render-check-worker.js +1332 -0
- package/dist/harness/check/runtime-render/render-check-worker.js.map +1 -0
- package/dist/harness/index.d.ts +2 -2
- package/dist/harness/index.js +263 -18
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/llm-trace-sink.d.ts +1 -1
- package/dist/harness/llm-trace-sink.js.map +1 -1
- package/dist/harness/prompts.js +17 -3
- package/dist/harness/prompts.js.map +1 -1
- package/dist/harness/result-types.d.ts +1 -1
- package/dist/harness/runtime.d.ts +9 -2
- package/dist/harness/runtime.js +20 -6
- package/dist/harness/runtime.js.map +1 -1
- package/dist/harness/types-public.d.ts +1 -1
- package/dist/harness/types-public.js +14 -2
- package/dist/harness/types-public.js.map +1 -1
- package/dist/harness/validator-trace-sink.d.ts +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +708 -95
- package/dist/index.js.map +1 -1
- package/dist/{llm-router-1TyI0_TV.d.ts → llm-router-BEVtbAb9.d.ts} +61 -4
- package/dist/patch.d.ts +1 -1
- package/dist/provider-adapter.d.ts +16 -6
- package/dist/provider-adapter.js.map +1 -1
- package/dist/providers/index.d.ts +19 -6
- package/dist/providers/index.js.map +1 -1
- package/dist/{render-check-CNZjHD5g.d.ts → render-check-BZoIE8-u.d.ts} +21 -1
- package/dist/{types-public-C36k1q6s.d.ts → types-public-DIpjC-OA.d.ts} +1 -1
- package/dist/workflows.d.ts +1 -1
- package/package.json +12 -12
- package/src/adapters/generation-dispatch.ts +9 -0
- package/src/adapters/openrouter/client.ts +2 -2
- package/src/adapters/openrouter/types.ts +10 -0
- package/src/boilerplate/generate.ts +10 -2
- package/src/boilerplate/json-schema-ts.ts +22 -2
- package/src/boilerplate/system-prompt.ts +1 -0
- package/src/compile.ts +1 -1
- package/src/contract-context.ts +10 -2
- package/src/create-ui-generator.ts +14 -0
- package/src/design-system-docs.ts +6 -4
- package/src/evaluation/axis-checks/checks/state-ui-affordance.ts +162 -0
- package/src/evaluation/axis-checks/registry.ts +2 -0
- package/src/evaluation/index.ts +11 -0
- package/src/evaluation/llm-evaluator.ts +14 -1
- package/src/evaluation/visual-evaluator.ts +44 -65
- package/src/harness/check/runtime-render/adapter.ts +18 -0
- package/src/harness/check/runtime-render/index.ts +8 -0
- package/src/harness/check/runtime-render/prepare-mockup.ts +14 -0
- package/src/harness/check/runtime-render/production-act-shim.ts +158 -0
- package/src/harness/check/runtime-render/render-check-host.ts +201 -0
- package/src/harness/check/runtime-render/render-check-worker.ts +80 -0
- package/src/harness/check/runtime-render/render-check.ts +112 -1
- package/src/harness/coding/run-eval-round.ts +61 -5
- package/src/harness/index.ts +1 -1
- package/src/harness/llm-router.ts +397 -18
- package/src/harness/llm-trace-sink.ts +2 -1
- package/src/harness/runtime.ts +7 -0
- package/src/index.ts +5 -5
- package/src/provider-adapter.ts +16 -6
- package/src/providers/bedrock.ts +5 -4
- package/src/providers/index.ts +16 -4
- package/src/tools/render-check.ts +19 -7
|
@@ -36,7 +36,7 @@ import '@ggui-ai/protocol';
|
|
|
36
36
|
/** Provider tag that produced the call. Mirrors {@link AgentConfig.provider}. */
|
|
37
37
|
type LlmTraceProvider = 'anthropic' | 'openai' | 'google' | 'openrouter';
|
|
38
38
|
/** Which `LLMAgent` method initiated the call. */
|
|
39
|
-
type LlmTraceKind = 'callText' | 'callTools' | 'callWithTools' | 'callStructured';
|
|
39
|
+
type LlmTraceKind = 'callText' | 'callTools' | 'callWithTools' | 'callStructured' | 'callVision';
|
|
40
40
|
/**
|
|
41
41
|
* One LLM call. Emitted **after** the call completes (success or error)
|
|
42
42
|
* — single event, not start/end split. Devtools UI is timeline-friendly
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/harness/llm-trace-sink.ts"],"names":[],"mappings":";
|
|
1
|
+
{"version":3,"sources":["../../src/harness/llm-trace-sink.ts"],"names":[],"mappings":";AAwGA,IAAI,UAAA,GAAkC,IAAA;AAM/B,SAAS,gBAAgB,IAAA,EAAiC;AAC/D,EAAA,UAAA,GAAa,IAAA;AACf;AAGO,SAAS,eAAA,GAAuC;AACrD,EAAA,OAAO,UAAA;AACT;AAOO,SAAS,kBAAkB,KAAA,EAA4B;AAC5D,EAAA,MAAM,IAAA,GAAO,UAAA;AACb,EAAA,IAAI,CAAC,IAAA,EAAM;AACX,EAAA,IAAI;AACF,IAAA,IAAA,CAAK,KAAK,KAAK,CAAA;AAAA,EACjB,CAAA,CAAA,MAAQ;AAAA,EAER;AACF;AAGO,SAAS,eACd,KAAA,EACwE;AACxE,EAAA,OAAO,KAAA,CAAM,GAAA,CAAI,CAAC,CAAA,MAAO,EAAE,IAAA,EAAM,CAAA,CAAE,IAAA,EAAM,WAAA,EAAa,CAAA,CAAE,WAAA,EAAY,CAAE,CAAA;AACxE;AAMO,SAAS,aAAA,GAAwB;AACtC,EAAA,OACE,IAAA,CAAK,GAAA,EAAI,CAAE,QAAA,CAAS,EAAE,CAAA,GACtB,IAAA,CAAK,MAAA,EAAO,CAAE,QAAA,CAAS,EAAE,CAAA,CAAE,KAAA,CAAM,GAAG,EAAE,CAAA;AAE1C","file":"llm-trace-sink.js","sourcesContent":["/**\n * Live LLM trace sink — devtools introspection of every LLM call the\n * generation harness makes.\n *\n * **Distinct from {@link TelemetrySink} and {@link AuditSink}.**\n * - **Telemetry** (mcp-server-core) = ops signals (`pair.completed`,\n * `render.duration_ms`). Sink may be lossy and drops on backpressure.\n * Attributes are flat scalars (`string | number | boolean`).\n * - **Audit** (mcp-server-core) = compliance log of privileged\n * mutations. Durable, async, may throw.\n * - **LLM trace** (this) = devtools-only, hostable in-memory ring\n * buffer of every LLM call's full payload (system prompt, user\n * prompt, tool defs, completion, token counts, cache hits, error).\n * Lossy by default. A hosted closed runtime may swap in a durable\n * sink for internal debugging; OSS ships an in-memory sink the\n * `/devtools/llm-trace` page reads.\n *\n * **Why module-level registry instead of constructor injection.** The\n * harness constructs LLM agents on every generation via\n * {@link createAgent}. Threading a sink through every call site\n * (createAgent → run-coding-turn → llm-evaluator → file-agent → …)\n * touches ~5 packages for a devtools-only surface. The OSS ggui server\n * is a single process per CLI invocation — global state has no\n * confusion-cost there. A hosted closed runtime isolates per request\n * via process-pool, so a global per-pool is also safe. If we ever\n * multi-tenant inside one process we'll thread it then.\n *\n * **Default = no sink.** When unset, the router emits nothing and\n * spends no CPU formatting events. Passing `null` removes a previously\n * registered sink.\n */\nimport type { LLMToolDef } from '../llm.js';\n\n/** Provider tag that produced the call. Mirrors {@link AgentConfig.provider}. */\nexport type LlmTraceProvider = 'anthropic' | 'openai' | 'google' | 'openrouter';\n\n/** Which `LLMAgent` method initiated the call. */\nexport type LlmTraceKind =\n | 'callText'\n | 'callTools'\n | 'callWithTools'\n | 'callStructured'\n | 'callVision';\n\n/**\n * One LLM call. Emitted **after** the call completes (success or error)\n * — single event, not start/end split. Devtools UI is timeline-friendly\n * because each event carries `at` (start) + `endedAt` (end).\n */\nexport interface LlmTraceEvent {\n /** Random per-event ID. */\n readonly id: string;\n /** Epoch ms when the call was issued. */\n readonly at: number;\n /** Epoch ms when the call completed (success or error). */\n readonly endedAt: number;\n /** `endedAt - at` — convenience. */\n readonly durationMs: number;\n readonly provider: LlmTraceProvider;\n readonly model: string;\n readonly kind: LlmTraceKind;\n readonly systemPrompt?: string;\n readonly userPrompt?: string;\n /**\n * Tool defs sent on the call (if any). Stripped to `name + description`\n * — full JSON schema would balloon the trace and is rarely useful in\n * the operator UI.\n */\n readonly tools?: ReadonlyArray<{\n readonly name: string;\n readonly description: string;\n }>;\n /** Set on successful completion. */\n readonly result?: {\n readonly inputTokens: number;\n readonly outputTokens: number;\n /** Anthropic only. Bytes counted toward the cache write. */\n readonly cacheCreated?: number;\n /** Anthropic only. Bytes served from cache (90% discounted). */\n readonly cacheRead?: number;\n /** Set when the call returned text content. */\n readonly text?: string;\n /** Set when the call returned tool_use blocks. */\n readonly toolCalls?: ReadonlyArray<{\n readonly name: string;\n readonly input: unknown;\n }>;\n /** Set for `callWithTools` — number of agentic turns consumed. */\n readonly turnsUsed?: number;\n };\n /** Set on failure. `result` is undefined. */\n readonly error?: { readonly message: string };\n}\n\n/**\n * Sink that receives one event per LLM call. Implementations MUST be\n * sync + non-throwing — the harness fires events on the hot path and\n * cannot tolerate backpressure or rejected promises. Buffer + drop or\n * fan out to a queue inside the implementation.\n */\nexport interface LlmTraceSink {\n emit(event: LlmTraceEvent): void;\n}\n\nlet activeSink: LlmTraceSink | null = null;\n\n/**\n * Register the active sink. Pass `null` to remove. Subsequent\n * {@link emitLlmTraceEvent} calls dispatch to this sink.\n */\nexport function setLlmTraceSink(sink: LlmTraceSink | null): void {\n activeSink = sink;\n}\n\n/** Read the active sink. Mostly for tests. */\nexport function getLlmTraceSink(): LlmTraceSink | null {\n return activeSink;\n}\n\n/**\n * Internal — used by `LLMAgent` adapters. No-op when no sink is\n * registered. Swallows sink-thrown errors (a broken devtools sink must\n * not break generation).\n */\nexport function emitLlmTraceEvent(event: LlmTraceEvent): void {\n const sink = activeSink;\n if (!sink) return;\n try {\n sink.emit(event);\n } catch {\n // Devtools sink is allowed to be buggy — generation must not die.\n }\n}\n\n/** Strip `LLMToolDef[]` to the trace-friendly subset. */\nexport function summarizeTools(\n tools: ReadonlyArray<LLMToolDef>,\n): ReadonlyArray<{ readonly name: string; readonly description: string }> {\n return tools.map((t) => ({ name: t.name, description: t.description }));\n}\n\n/**\n * Crockford-style random ID. Crypto.randomUUID() would do, but we want\n * to keep this dep-free for non-Node runtimes the harness might run in.\n */\nexport function newLlmTraceId(): string {\n return (\n Date.now().toString(36) +\n Math.random().toString(36).slice(2, 10)\n );\n}\n"]}
|
package/dist/harness/prompts.js
CHANGED
|
@@ -17,6 +17,15 @@ function jsonSchemaTypeToTs(schema) {
|
|
|
17
17
|
if (schema.enum?.length) {
|
|
18
18
|
return schema.enum.map((v) => typeof v === "string" ? `'${v}'` : String(v)).join(" | ");
|
|
19
19
|
}
|
|
20
|
+
if (Array.isArray(schema.type)) {
|
|
21
|
+
const { nullable: _nullable, ...rest } = schema;
|
|
22
|
+
const members = [...new Set(schema.type)].map(
|
|
23
|
+
(member) => jsonSchemaTypeToTs({ ...rest, type: member })
|
|
24
|
+
);
|
|
25
|
+
const unique = [...new Set(members)];
|
|
26
|
+
if (schema.nullable && !unique.includes("null")) unique.push("null");
|
|
27
|
+
return unique.join(" | ");
|
|
28
|
+
}
|
|
20
29
|
let result;
|
|
21
30
|
switch (schema.type) {
|
|
22
31
|
case "string":
|
|
@@ -34,7 +43,8 @@ function jsonSchemaTypeToTs(schema) {
|
|
|
34
43
|
case "array": {
|
|
35
44
|
if (schema.items) {
|
|
36
45
|
const itemType = jsonSchemaTypeToTs(schema.items);
|
|
37
|
-
|
|
46
|
+
const itemsDeclareObject = Array.isArray(schema.items.type) ? schema.items.type.includes("object") : schema.items.type === "object";
|
|
47
|
+
result = itemsDeclareObject && schema.items.properties ? `Array<${itemType}>` : itemType.includes("|") ? `(${itemType})[]` : `${itemType}[]`;
|
|
38
48
|
} else {
|
|
39
49
|
result = "unknown[]";
|
|
40
50
|
}
|
|
@@ -247,11 +257,15 @@ ${JSON.stringify(clientCapabilities, null, 2)}
|
|
|
247
257
|
const uiRequirements = [];
|
|
248
258
|
if (contract.propsSpec?.properties) {
|
|
249
259
|
for (const [name, entry] of Object.entries(contract.propsSpec.properties)) {
|
|
250
|
-
const required = entry.required;
|
|
251
|
-
if (required
|
|
260
|
+
const required = entry.required === true;
|
|
261
|
+
if (required) {
|
|
252
262
|
uiRequirements.push(
|
|
253
263
|
`- **Required prop \`${name}\`** must appear somewhere in rendered DOM (display the value, or use it to drive a label/aria/key).`
|
|
254
264
|
);
|
|
265
|
+
} else {
|
|
266
|
+
uiRequirements.push(
|
|
267
|
+
`- **Optional prop \`${name}\`** may be ABSENT at runtime \u2014 the component must render without it (guard the access or apply its default) and must display it when it IS provided.`
|
|
268
|
+
);
|
|
255
269
|
}
|
|
256
270
|
}
|
|
257
271
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../../src/boilerplate/json-schema-ts.ts","../../src/check/contract-validation.ts","../../src/check/type-checker.ts","../../src/contract-context.ts"],"names":["ts","listContractGadgets","HOOK_NAME_RE"],"mappings":"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","file":"prompts.js","sourcesContent":["// packages/ui-gen/src/boilerplate/json-schema-ts.ts\n//\n// JSON Schema → TypeScript type string converter.\n//\n// Pure, self-contained, no env reads, no framework deps. Exposed\n// publicly on `@ggui-ai/ui-gen/boilerplate` because it's the primitive\n// that turns a contract's JSON Schema into the `interface Props { ... }`\n// block the boilerplate generator writes.\n\nimport type { JsonSchema } from '@ggui-ai/protocol';\n\n/**\n * Convert a JsonSchema to a TypeScript type string — recursive.\n *\n * Handles: string, number, integer, boolean, null, array (+ tuples), object,\n * enum, oneOf/anyOf (unions), nullable, const, additionalProperties.\n */\nexport function jsonSchemaTypeToTs(schema: JsonSchema): string {\n // ── Union types (oneOf / anyOf) ──\n const unionMembers = schema.oneOf ?? schema.anyOf;\n if (unionMembers?.length) {\n const types = [...new Set(unionMembers.map(s => jsonSchemaTypeToTs(s)))];\n return types.length === 1 ? types[0] : types.join(' | ');\n }\n\n // ── Const (literal type) ──\n if (schema.const !== undefined) {\n return typeof schema.const === 'string' ? `'${schema.const}'` : String(schema.const);\n }\n\n // ── Enum (union of literals) ──\n if (schema.enum?.length) {\n return schema.enum\n .map(v => typeof v === 'string' ? `'${v}'` : String(v))\n .join(' | ');\n }\n\n let result: string;\n\n switch (schema.type) {\n case 'string':\n result = 'string';\n break;\n case 'number':\n case 'integer':\n result = 'number';\n break;\n case 'boolean':\n result = 'boolean';\n break;\n case 'null':\n return 'null';\n\n case 'array': {\n if (schema.items) {\n const itemType = jsonSchemaTypeToTs(schema.items);\n // Use Array<T> for complex object types, T[] for simple\n result = (schema.items.type === 'object' && schema.items.properties)\n ? `Array<${itemType}>`\n : (itemType.includes('|') ? `(${itemType})[]` : `${itemType}[]`);\n } else {\n result = 'unknown[]';\n }\n break;\n }\n\n case 'object': {\n if (schema.properties) {\n const required = schema.required ?? [];\n const fields = Object.entries(schema.properties)\n .map(([key, prop]) => {\n const opt = !required.includes(key);\n return `${key}${opt ? '?' : ''}: ${jsonSchemaTypeToTs(prop)}`;\n })\n .join('; ');\n result = `{ ${fields} }`;\n } else if (schema.additionalProperties && typeof schema.additionalProperties === 'object') {\n result = `Record<string, ${jsonSchemaTypeToTs(schema.additionalProperties)}>`;\n } else {\n result = 'Record<string, unknown>';\n }\n break;\n }\n\n default:\n result = 'unknown';\n }\n\n // Apply nullable (OpenAPI 3.0 shorthand)\n if (schema.nullable && result !== 'unknown') {\n return `${result} | null`;\n }\n\n return result;\n}\n","// packages/ui-gen/src/check/contract-validation.ts\n//\n// Shared validation utilities for data-contract conformance —\n// deterministic tier-0 gate for `PropsSpec` / `StreamSpec` / `ActionSpec`\n// agreement between the LLM-emitted componentCode and the contract the\n// agent handed to `ggui_render`.\n//\n// Used by:\n// (1) Self-check soft gate in the coding agent\n// (`coding-agent/self-check.ts`) — surfaces mismatches as\n// structured violations for the LLM retry loop.\n// (2) Tier-0 hard gate in the evaluation pipeline — errors (severity\n// `error`) are blocking; warnings surface as advisory.\n// (3) Adapter tool-family wiring for multi-SDK generation runs.\n// (4) Benchmarks + LLM prompt construction — `jsonSchemaTypeToTs` +\n// `propsSpecToTypeScript` + `inferPropsSpecFromSampleData` are\n// pure helpers reused outside the check path. Re-exported here so\n// those callers don't need a separate import for the subpath.\n//\n// Deps are pure: `@ggui-ai/protocol` (public types) + `typescript`\n// (compiler API) + the in-package `jsonSchemaTypeToTs` primitive from\n// `../boilerplate/json-schema-ts.js`.\n\nimport type {\n PropsSpec,\n StreamSpec,\n ActionSpec,\n DataContract,\n JsonSchema,\n JsonValue,\n PropEntry,\n JsonObject,\n} from \"@ggui-ai/protocol\";\nimport ts from \"typescript\";\n\n// Re-export the pure JSON-Schema → TypeScript-type converter from the\n// in-package `boilerplate/` primitive, so consumers of the tier-0 check\n// surface (self-check, tiers, benchmarks, prompts, adapters) import a\n// single symbol from a single subpath.\nexport { jsonSchemaTypeToTs } from \"../boilerplate/json-schema-ts.js\";\nimport { jsonSchemaTypeToTs } from \"../boilerplate/json-schema-ts.js\";\n\n// =============================================================================\n// Contract Issue\n// =============================================================================\n\nexport interface ContractIssue {\n severity: \"error\" | \"warning\";\n field: string;\n message: string;\n fix: string;\n}\n\n// =============================================================================\n// Props Interface Extraction (AST-based)\n// =============================================================================\n\ninterface ExtractedProp {\n name: string;\n type: string;\n optional: boolean;\n}\n\n/**\n * Extract field names and types from a TypeScript Props interface in source code.\n * Uses the TypeScript compiler API — handles nested types, generics, unions correctly.\n */\nexport function extractPropsInterface(code: string): ExtractedProp[] | null {\n const sf = ts.createSourceFile(\"component.tsx\", code, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);\n const props: ExtractedProp[] = [];\n\n function visitMembers(members: ts.NodeArray<ts.TypeElement>): void {\n for (const member of members) {\n if (ts.isPropertySignature(member) && member.name) {\n const name = member.name.getText(sf);\n const type = member.type ? member.type.getText(sf) : \"unknown\";\n const optional = !!member.questionToken;\n props.push({ name, type, optional });\n }\n }\n }\n\n function visit(node: ts.Node): void {\n // interface Props { ... }\n if (ts.isInterfaceDeclaration(node) && node.name.text === \"Props\") {\n visitMembers(node.members);\n return;\n }\n // type Props = { ... }\n if (ts.isTypeAliasDeclaration(node) && node.name.text === \"Props\") {\n if (ts.isTypeLiteralNode(node.type)) {\n visitMembers(node.type.members);\n }\n return;\n }\n ts.forEachChild(node, visit);\n }\n\n visit(sf);\n return props.length > 0 ? props : null;\n}\n\n// =============================================================================\n// Contract Validation\n// =============================================================================\n\n/**\n * Compare extracted Props interface against a PropsSpec contract.\n * Returns issues: errors for missing required fields, warnings for type mismatches.\n */\nexport function validatePropsAgainstSchema(\n code: string,\n spec: PropsSpec,\n): ContractIssue[] {\n const issues: ContractIssue[] = [];\n const extracted = extractPropsInterface(code);\n\n if (!extracted) {\n // Can't extract — not necessarily an error (might use inline types)\n return [];\n }\n\n const extractedMap = new Map(extracted.map((p) => [p.name, p]));\n\n for (const [propName, entry] of Object.entries(spec.properties)) {\n const extractedProp = extractedMap.get(propName);\n\n if (!extractedProp) {\n if (entry.required) {\n issues.push({\n severity: \"error\",\n field: propName,\n message: `Props interface is missing required field '${propName}' from the data contract`,\n fix: `Add \\`${propName}${entry.required ? \"\" : \"?\"}: ${jsonSchemaTypeToTs(entry.schema)}\\` to your Props interface`,\n });\n } else {\n issues.push({\n severity: \"warning\",\n field: propName,\n message: `Props interface is missing optional field '${propName}' from the data contract`,\n fix: `Consider adding \\`${propName}?: ${jsonSchemaTypeToTs(entry.schema)}\\` to your Props interface`,\n });\n }\n continue;\n }\n\n // Basic type compatibility check (only if we have type info)\n if (extractedProp.type !== \"unknown\") {\n const expectedTsType = jsonSchemaTypeToTs(entry.schema);\n if (!isTypeCompatible(extractedProp.type, expectedTsType, entry.schema)) {\n issues.push({\n severity: \"warning\",\n field: propName,\n message: `Field '${propName}' has type '${extractedProp.type}' but contract expects '${expectedTsType}'`,\n fix: `Change \\`${propName}\\` type to \\`${expectedTsType}\\``,\n });\n }\n }\n }\n\n return issues;\n}\n\n/**\n * Check if a TypeScript type string is compatible with a JSON Schema type.\n * Loose comparison — we only check the base type, not full structural equality.\n */\nfunction isTypeCompatible(tsType: string, expectedTsType: string, schema: JsonSchema): boolean {\n const normalized = tsType.replace(/\\s/g, \"\").toLowerCase();\n\n switch (schema.type) {\n case \"string\":\n return normalized.includes(\"string\");\n case \"number\":\n case \"integer\":\n return normalized.includes(\"number\");\n case \"boolean\":\n return normalized.includes(\"boolean\");\n case \"array\":\n return normalized.includes(\"[]\") || normalized.includes(\"array\");\n case \"object\":\n return ![\"string\", \"number\", \"boolean\"].some((t) => normalized === t);\n default:\n return true;\n }\n}\n\n// =============================================================================\n// JSON Schema → TypeScript Conversion (for LLM prompts)\n// =============================================================================\n\n// `jsonSchemaTypeToTs` lives in `@ggui-ai/ui-gen/boilerplate` and is\n// re-exported at the top of this file.\n\n/**\n * Convert a full PropsSpec to a TypeScript interface string for LLM prompt injection.\n * Includes required/optional markers and descriptions as comments.\n */\nexport function propsSpecToTypeScript(spec: PropsSpec, indent = 2): string {\n const pad = \" \".repeat(indent);\n const lines: string[] = [];\n\n for (const [propName, entry] of Object.entries(spec.properties)) {\n if (entry.description) {\n lines.push(`${pad}/** ${entry.description} */`);\n }\n const optional = !entry.required;\n const tsType = jsonSchemaTypeToTs(entry.schema);\n const defaultStr = entry.default !== undefined ? ` // default: ${JSON.stringify(entry.default)}` : \"\";\n lines.push(`${pad}${propName}${optional ? \"?\" : \"\"}: ${tsType};${defaultStr}`);\n // Include example as a comment so the LLM sees the exact data shape\n if (entry.example !== undefined) {\n const exampleStr = JSON.stringify(entry.example, null, 2)\n .split(\"\\n\")\n .map((l, i) => (i === 0 ? `${pad}// Example: ${l}` : `${pad}// ${l}`))\n .join(\"\\n\");\n lines.push(exampleStr);\n }\n }\n\n return `{\\n${lines.join(\"\\n\")}\\n}`;\n}\n\n// =============================================================================\n// StreamSpec Validation\n// =============================================================================\n\n/**\n * Validate that the generated source code properly handles stream events.\n * Requires useStream() wire hooks — legacy DOM event patterns are no longer accepted.\n */\nexport function validateStreamSpecConformance(\n code: string,\n spec: StreamSpec,\n): ContractIssue[] {\n const issues: ContractIssue[] = [];\n\n const usesWireStream = code.includes(\"useStream\");\n\n if (!usesWireStream) {\n issues.push({\n severity: \"error\",\n field: \"__streamSpec__\",\n message: \"Component does not handle stream events — use useStream() from the boilerplate wire hooks\",\n fix: \"Use the useStream('eventName') hook from the boilerplate to receive real-time data from the agent\",\n });\n return issues;\n }\n\n // Check each declared channel is referenced in the code. `spec` is a\n // flat `Record<channelName, StreamChannelEntry>` — iterate directly.\n for (const [channelName, entry] of Object.entries(spec)) {\n if (!code.includes(`'${channelName}'`) && !code.includes(`\"${channelName}\"`)) {\n issues.push({\n severity: \"warning\",\n field: channelName,\n message: `StreamSpec declares channel '${channelName}' but component doesn't reference it`,\n fix: `Add useStream('${channelName}') to receive ${entry.description || channelName} events`,\n });\n }\n }\n\n return issues;\n}\n\n// =============================================================================\n// ActionSpec Validation\n// =============================================================================\n\n/**\n * Validate that the generated source code wires the declared actions.\n * Checks that action IDs are referenced via useAction() wire hooks, string literals, or prop callbacks.\n */\nexport function validateActionSpecConformance(\n code: string,\n spec: ActionSpec,\n): ContractIssue[] {\n const issues: ContractIssue[] = [];\n\n // `spec` is a flat `Record<actionId, ActionEntry>` — iterate directly.\n for (const [actionId, entry] of Object.entries(spec)) {\n // Check the action ID or its label is referenced in the code\n const idReferenced = code.includes(`'${actionId}'`) || code.includes(`\"${actionId}\"`);\n const labelReferenced = code.includes(entry.label);\n const callbackName = `on${actionId.charAt(0).toUpperCase()}${actionId.slice(1)}`;\n const callbackReferenced = code.includes(callbackName);\n\n if (!idReferenced && !labelReferenced && !callbackReferenced) {\n issues.push({\n severity: \"error\",\n field: actionId,\n message: `ActionSpec declares action '${actionId}' (\"${entry.label}\") but component doesn't wire it`,\n fix: `Use the useAction('${actionId}') hook from the boilerplate and wire it to a button or form submit`,\n });\n }\n }\n\n return issues;\n}\n\n// =============================================================================\n// Unified Contract Validation\n// =============================================================================\n\n/**\n * Validate all contract (props, stream, actions) against the source code.\n * Returns combined issues from all three validations.\n */\nexport function validateAllContracts(\n code: string,\n contract: DataContract,\n): ContractIssue[] {\n const issues: ContractIssue[] = [];\n\n if (contract.propsSpec) {\n issues.push(...validatePropsAgainstSchema(code, contract.propsSpec));\n }\n\n if (contract.streamSpec) {\n issues.push(...validateStreamSpecConformance(code, contract.streamSpec));\n }\n\n if (contract.actionSpec) {\n issues.push(...validateActionSpecConformance(code, contract.actionSpec));\n }\n\n return issues;\n}\n\n// =============================================================================\n// Sample Data → PropsSpec Inference (backward compat)\n// =============================================================================\n\n/**\n * Infer a PropsSpec from sample data.\n * Convenience utility for migration and for agents that pass sample data instead of schemas.\n * All fields are marked as required (can't distinguish required/optional from data alone).\n */\nexport function inferPropsSpecFromSampleData(data: JsonObject): PropsSpec {\n const properties: Record<string, PropEntry> = {};\n\n for (const [key, value] of Object.entries(data)) {\n properties[key] = {\n schema: inferJsonSchema(value),\n required: true,\n example: value as JsonValue,\n };\n }\n\n return { properties };\n}\n\nfunction inferJsonSchema(value: unknown): JsonSchema {\n if (value === null || value === undefined) return { type: \"null\" };\n if (typeof value === \"string\") return { type: \"string\" };\n if (typeof value === \"number\") return { type: \"number\" };\n if (typeof value === \"boolean\") return { type: \"boolean\" };\n\n if (Array.isArray(value)) {\n if (value.length === 0) return { type: \"array\" };\n return {\n type: \"array\",\n items: inferJsonSchema(value[0]),\n };\n }\n\n if (typeof value === \"object\") {\n const properties: Record<string, JsonSchema> = {};\n const required: string[] = [];\n for (const [k, v] of Object.entries(value as JsonObject)) {\n properties[k] = inferJsonSchema(v);\n required.push(k);\n }\n return { type: \"object\", properties, required };\n }\n\n return { type: \"string\" };\n}\n","// packages/ui-gen/src/check/type-checker.ts\n//\n// TypeScript type-checker for LLM-generated UI components. Uses the\n// TypeScript compiler API with a virtual filesystem to type-check TSX\n// code against React types and `@ggui-ai/design` primitives, giving\n// the LLM familiar TS error feedback without writing anything to disk.\n//\n// The checker builds a virtual filesystem seeded with `typescript/lib`,\n// `@types/react`, `@ggui-ai/design/dist`, and `@ggui-ai/wire/dist`\n// `.d.ts` content, then drives `ts.createProgram` against a synthetic\n// `Component.tsx`. Exposed on `@ggui-ai/ui-gen/check` so\n// `createUiGenerator` runs the same tier-0 gate as the hosted\n// generation path.\n//\n// Path-resolution note: the three workspace-rooted lookups —\n// `.pnpm/@types+react@*`, `packages/design/dist`, and\n// `packages/wire/dist` — are anchored relative to this file. tsup\n// bundles `src/check/index.ts` (splitting: false) into a flat\n// `dist/check/index.js`, so the src and dist locations sit at the same\n// depth and both resolve correctly. `typescript/lib/typescript.js`\n// keeps using `createRequire(import.meta.url).resolve` —\n// position-independent, so no change.\n//\n// Used by:\n// (1) Tier-0 `type_check` — blocking errors (see BLOCKING_CODES)\n// map to PRODUCTION_FAILED; non-blocking TS diagnostics surface\n// as warnings so the LLM can iterate on non-crash-risk findings\n// without stalling the harness.\n// (2) The `self_check` tool — called from generator SDKs (Anthropic /\n// OpenAI / Google) to give the LLM TS feedback mid-generation.\n// (3) The tier-0 orchestrator `runTier0Checks` — runs in parallel\n// with the wire-preservation + lint checks under the same tier\n// budget.\n//\n// Design notes that motivate non-obvious choices:\n// - Classic React JSX mode (`ts.JsxEmit.React`). The synthetic\n// prefix (see SYNTHETIC_PREFIX) supplies `import React from\n// 'react'` for the classic JSX factory PLUS a self-contained\n// global `JSX` namespace. `@types/react` v19 removed the global\n// `JSX` namespace that classic mode resolves intrinsic elements +\n// the `key`/`ref` carve-out through; without the shim, `<div>`\n// degrades to `any` and every typed component falsely rejects the\n// intrinsic `key` prop. Automatic mode was tried but its\n// `react/jsx-runtime` resolution does not survive the VFS.\n// - `strict: false` + `strictNullChecks: true`: we want the runtime\n// crash classes (`undefined.foo()`, `null.bar`) to surface as\n// blocking, but not the optional-chaining / exhaustive-check\n// noise that full `strict` would emit on LLM code.\n// - `types: []`: prevents the VFS from auto-pulling every\n// `@types/*` package that happens to be hoisted — only react and\n// the design/wire dist types should be reachable, matching the\n// forbidden-import policy enforced by `react-linter` elsewhere.\n// - TS2307 (\"Cannot find module\") is deliberately NOT blocking —\n// Lambda bundles code without type declarations, so the VFS can't\n// see every package that may exist at runtime. Forbidden imports\n// are caught by `runSelfChecks` regex instead.\n\nimport ts from 'typescript';\nimport fs from 'fs';\nimport path from 'path';\nimport { createRequire } from 'module';\nimport { describeAllowedImports } from '../validation/allowed-imports.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface TypeCheckDiagnostic {\n code: number;\n line: number;\n message: string;\n fix: string;\n}\n\nexport interface TypeCheckResult {\n errors: TypeCheckDiagnostic[]; // Blocking\n warnings: TypeCheckDiagnostic[]; // Non-blocking\n}\n\n// ---------------------------------------------------------------------------\n// Blocking error codes — these would break at runtime\n// ---------------------------------------------------------------------------\n\nconst BLOCKING_CODES = new Set([\n 2304, // Cannot find name\n // 2307 (Cannot find module) is NOT blocking — Lambda bundles code without\n // type declarations, so the VFS can't resolve react/@ggui-ai/design.\n // Forbidden imports are caught by runSelfChecks regex instead.\n 2305, // Module has no exported member\n 2322, // Type not assignable\n 2339, // Property does not exist on type\n 2741, // Missing required property\n 2769, // No overload matches this call\n 17004, // Cannot use JSX unless '--jsx' flag\n 18047, // 'X' is possibly 'null' — causes runtime crash\n 18048, // 'X' is possibly 'undefined' — causes runtime crash\n]);\n\n// ---------------------------------------------------------------------------\n// Fix suggestions per error code\n// ---------------------------------------------------------------------------\n\nfunction generateFix(code: number, message?: string, sourceLine?: string): string {\n // Source-line hint: when the failing line is short and self-contained,\n // appending it gives the LLM a precise target. Skip for very long lines\n // (>140 chars — usually multi-prop JSX where the line itself is the\n // diagnostic) since they bloat the violation envelope without helping.\n const sourceHint =\n sourceLine && sourceLine.length > 0 && sourceLine.length <= 140\n ? ` Offending line: \\`${sourceLine}\\``\n : '';\n\n // Detect event handler on non-interactive primitive\n if ((code === 2322 || code === 2339) && message && /onClick|onDoubleClick|onMouseEnter|onMouseLeave|onPress/.test(message)) {\n return `This structural primitive has no event handlers of its own — add the trait as a PROP: as={Clickable} (then onClick works), imported from @ggui-ai/design. Do NOT wrap it in <Clickable>…</Clickable>; as is a prop, not a wrapper element.${sourceHint}`;\n }\n\n // Detect underscore-prefix-on-prop trap: `Property '_X' does not exist`.\n // LLM saw an unused-var warning on `X` and tried to silence it by renaming\n // the destructure to `_X`, breaking prop access. Steer back.\n if (code === 2339 && message && /Property '_[a-zA-Z]/.test(message)) {\n return `You renamed a prop with a leading underscore to silence \\`no-unused-vars\\`, but the prop on \\`Props\\` doesn't have that prefix. Restore the original name; better, don't destructure props you won't render — access them via \\`props.fieldName\\` only when needed.${sourceHint}`;\n }\n\n // Detect \"Cannot find name\" for destructured props\n if (code === 2304 && message && !message.includes('module') && !message.includes('import')) {\n return `This name is not defined in scope. Either you destructured props (use \\`props.fieldName\\` directly instead) OR you removed a helper declaration in this patch but kept a JSX/expression reference to it. Read your full patch and either restore the declaration or remove the reference.${sourceHint}`;\n }\n\n // Detect \"unknown is not assignable to ReactNode\"\n if ((code === 2322 || code === 2769) && message && /unknown.*ReactNode|ReactNode.*unknown/.test(message)) {\n return `This value has type 'unknown' and cannot be rendered in JSX. Cast it: String(value) or add a type annotation.${sourceHint}`;\n }\n\n switch (code) {\n case 2307:\n return `Only these imports are allowed: ${describeAllowedImports()}${sourceHint}`;\n case 2322:\n case 2769:\n return `Type mismatch on this expression. Check the offending line below — the prop name in JSX is what TypeScript is rejecting; the message tells you the expected type.${sourceHint}`;\n case 2339:\n return `This prop doesn't exist on this component — check the available props on the component's interface (visible at the top of the file, or in the design-system reference).${sourceHint}`;\n case 2305:\n return `This name is not defined. Check your imports and variable declarations.${sourceHint}`;\n case 2741:\n return `A required prop is missing. Check the component's Props interface.${sourceHint}`;\n case 18047:\n case 18048:\n return `This value might be null/undefined. Every dereference on the same nullable still needs \\`?.\\` — \\`x?.foo && x?.bar\\`, NOT \\`x?.foo && x.bar\\`. Or hoist: \\`const v = x; if (!v) return null;\\` then access \\`v.foo\\`/\\`v.bar\\` unguarded.${sourceHint}`;\n default:\n return `Review the TypeScript error and fix the type issue.${sourceHint}`;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Virtual Filesystem — lazy singleton\n// ---------------------------------------------------------------------------\n\ninterface VfsEntry {\n content: string;\n sourceFile: ts.SourceFile;\n}\n\nlet vfsCache: Map<string, VfsEntry> | null = null;\n\nfunction parseAndStore(\n vfs: Map<string, VfsEntry>,\n virtualPath: string,\n content: string,\n): void {\n const sourceFile = ts.createSourceFile(\n virtualPath,\n content,\n ts.ScriptTarget.ES2020,\n true,\n virtualPath.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.TS,\n );\n vfs.set(virtualPath, { content, sourceFile });\n}\n\n/**\n * Recursively walk a directory and return all file paths matching a filter.\n */\nfunction walkDir(dir: string, filter: (f: string) => boolean): string[] {\n const results: string[] = [];\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n results.push(...walkDir(full, filter));\n } else if (filter(full)) {\n results.push(full);\n }\n }\n return results;\n}\n\n/**\n * Resolve `@types/react`'s root directory through Node's module\n * resolution. Position-independent — walks the consumer's node_modules\n * chain. Works in monorepo (where the type package is hoisted under\n * `<root>/node_modules/.pnpm/@types+react@*`) and in standalone\n * `packages/`-only installs (e.g. the projected public OSS repo where\n * the `packages/` prefix is stripped, so a hardcoded `../../../..`\n * walk lands in the wrong place).\n */\nfunction findReactTypesDir(): string | null {\n try {\n // `@types/react` doesn't expose `package.json` as a public subpath,\n // so resolve via the always-present `index.d.ts`.\n const indexDts = createRequire(import.meta.url).resolve('@types/react/index.d.ts');\n return path.dirname(indexDts);\n } catch {\n return null;\n }\n}\n\n/**\n * Resolve a workspace package's `dist/` directory through Node's\n * module resolution. Like `findReactTypesDir`, this works in both\n * monorepo (where the package is hoisted/symlinked) and standalone\n * installs — no relative-path math required.\n */\nfunction findPackageDistDir(pkg: string): string | null {\n try {\n const pkgJson = createRequire(import.meta.url).resolve(`${pkg}/package.json`);\n const dist = path.join(path.dirname(pkgJson), 'dist');\n return fs.existsSync(dist) ? dist : null;\n } catch {\n return null;\n }\n}\n\nasync function loadVfs(): Promise<Map<string, VfsEntry>> {\n if (vfsCache) return vfsCache;\n\n const vfs = new Map<string, VfsEntry>();\n\n // ---- 1. TypeScript lib files ----\n // `createRequire(import.meta.url).resolve('typescript/lib/typescript.js')`\n // is position-independent — it walks the consumer's node_modules chain,\n // so this lookup works identically from core/, packages/ui-gen/, and any\n // future host package.\n const require_ = createRequire(import.meta.url);\n const tsDir = path.dirname(require_.resolve('typescript/lib/typescript.js'));\n const libFiles = fs.readdirSync(tsDir).filter((f) => /^lib\\..*\\.d\\.ts$/.test(f));\n for (const file of libFiles) {\n const content = fs.readFileSync(path.join(tsDir, file), 'utf-8');\n parseAndStore(vfs, file, content);\n }\n\n // ---- 2. React types ----\n // Resolved through `createRequire(import.meta.url).resolve('@types/\n // react/index.d.ts')` — position-independent. Works the same in\n // monorepo (hoisted to root) and in standalone `packages/`-only\n // installs (the projected public OSS repo strips the `packages/`\n // prefix, so a hardcoded `../../../..` walk would miss).\n const reactDir = findReactTypesDir();\n if (reactDir) {\n const reactDts = fs.readdirSync(reactDir).filter((f) => f.endsWith('.d.ts'));\n for (const file of reactDts) {\n const content = fs.readFileSync(path.join(reactDir, file), 'utf-8');\n parseAndStore(vfs, `node_modules/@types/react/${file}`, content);\n }\n }\n\n // ---- 3. Design system types ----\n const designDist = findPackageDistDir('@ggui-ai/design');\n if (designDist) {\n const dtsFiles = walkDir(designDist, (f) => f.endsWith('.d.ts') && !f.endsWith('.d.ts.map'));\n for (const file of dtsFiles) {\n const rel = path.relative(designDist, file);\n const content = fs.readFileSync(file, 'utf-8');\n parseAndStore(vfs, `node_modules/@ggui-ai/design/${rel}`, content);\n }\n }\n\n // ---- 4. Wire module types ----\n const wireDist = findPackageDistDir('@ggui-ai/wire');\n if (wireDist) {\n const dtsFiles = walkDir(wireDist, (f) => f.endsWith('.d.ts') && !f.endsWith('.d.ts.map'));\n for (const file of dtsFiles) {\n const rel = path.relative(wireDist, file);\n const content = fs.readFileSync(file, 'utf-8');\n parseAndStore(vfs, `node_modules/@ggui-ai/wire/${rel}`, content);\n }\n }\n\n // ---- 5. Gadgets types ----\n // Generated component code direct-imports standard-library gadget\n // hooks — `import { useGeolocation } from '@ggui-ai/gadgets'`.\n // Loading the `@ggui-ai/gadgets` package's shipped `.d.ts` into the\n // VFS makes those bare-named imports resolve against the real hook\n // declarations (named option/return types preserved). The standard\n // gadget hooks are plain named exports the resolver finds directly.\n const clientLibsDist = findPackageDistDir('@ggui-ai/gadgets');\n if (clientLibsDist) {\n const dtsFiles = walkDir(\n clientLibsDist,\n (f) => f.endsWith('.d.ts') && !f.endsWith('.d.ts.map'),\n );\n for (const file of dtsFiles) {\n const rel = path.relative(clientLibsDist, file);\n const content = fs.readFileSync(file, 'utf-8');\n parseAndStore(vfs, `node_modules/@ggui-ai/gadgets/${rel}`, content);\n }\n }\n\n vfsCache = vfs;\n return vfs;\n}\n\n// ---------------------------------------------------------------------------\n// Module resolution helper\n// ---------------------------------------------------------------------------\n\n/**\n * Try to resolve a relative import within the VFS.\n * Given `containingFile` (e.g. `node_modules/@ggui-ai/design/primitives/index.d.ts`)\n * and `importPath` (e.g. `./Card`), try:\n * - <resolved>.d.ts\n * - <resolved>/index.d.ts\n * - <resolved>.ts\n */\nfunction resolveRelativeInVfs(\n vfs: Map<string, VfsEntry>,\n containingFile: string,\n importPath: string,\n): string | undefined {\n // Strip leading '/' from containingFile — TS may prepend getCurrentDirectory()\n const normalized = containingFile.startsWith('/') ? containingFile.slice(1) : containingFile;\n const dir = path.posix.dirname(normalized);\n const resolved = path.posix.normalize(`${dir}/${importPath}`);\n\n // Try .d.ts extension\n const withDts = `${resolved}.d.ts`;\n if (vfs.has(withDts)) return withDts;\n\n // Try /index.d.ts\n const withIndexDts = `${resolved}/index.d.ts`;\n if (vfs.has(withIndexDts)) return withIndexDts;\n\n // Try .ts extension\n const withTs = `${resolved}.ts`;\n if (vfs.has(withTs)) return withTs;\n\n // Might already be the exact path\n if (vfs.has(resolved)) return resolved;\n\n return undefined;\n}\n\n// ---------------------------------------------------------------------------\n// CompilerHost factory\n// ---------------------------------------------------------------------------\n\nconst COMPILER_OPTIONS: ts.CompilerOptions = {\n target: ts.ScriptTarget.ES2020,\n module: ts.ModuleKind.ESNext,\n // Classic React JSX mode. The synthetic prefix (SYNTHETIC_PREFIX)\n // supplies `import React from 'react'` plus a `declare global` JSX\n // namespace shim: `@types/react` v19 removed the *global* `JSX`\n // namespace (it lives at `React.JSX` now), which classic mode needs\n // for `JSX.IntrinsicElements` and the `key`/`ref` carve-out. Without\n // the shim, `<div>` degrades to `any` and every typed component\n // falsely rejects the intrinsic `key` prop.\n jsx: ts.JsxEmit.React,\n moduleResolution: ts.ModuleResolutionKind.Bundler,\n strict: false,\n strictNullChecks: true, // Catch undefined.foo() errors that cause runtime crashes\n noEmit: true,\n skipLibCheck: true,\n noImplicitAny: true,\n types: [],\n esModuleInterop: true,\n};\n\n/**\n * Normalize a path that TS's internal module resolution might generate.\n * Since getCurrentDirectory() returns '/', TS will probe paths like\n * '/node_modules/@types/react/jsx-runtime.d.ts'. We strip the leading\n * '/' to match our VFS keys.\n */\nfunction normalizeVfsPath(f: string): string | null {\n if (f.startsWith('/')) {\n return f.slice(1);\n }\n return null;\n}\n\n/**\n * Core module resolution logic shared between resolveModuleNameLiterals\n * (for user imports) and resolveModuleNames (for TS-internal imports\n * like jsxImportSource).\n */\nfunction resolveModuleName(\n vfs: Map<string, VfsEntry>,\n name: string,\n containingFile: string,\n): ts.ResolvedModuleWithFailedLookupLocations {\n // React core\n if (name === 'react') {\n return resolved('node_modules/@types/react/index.d.ts');\n }\n if (name === 'react/jsx-runtime') {\n return resolved('node_modules/@types/react/jsx-runtime.d.ts');\n }\n if (name === 'react/jsx-dev-runtime') {\n const p = 'node_modules/@types/react/jsx-dev-runtime.d.ts';\n if (vfs.has(p)) return resolved(p);\n return resolved('node_modules/@types/react/jsx-runtime.d.ts');\n }\n\n // @ggui-ai/design sub-paths\n const designPrefix = '@ggui-ai/design/';\n if (name.startsWith(designPrefix)) {\n const subpath = name.slice(designPrefix.length);\n const indexPath = `node_modules/@ggui-ai/design/${subpath}/index.d.ts`;\n if (vfs.has(indexPath)) return resolved(indexPath);\n const directPath = `node_modules/@ggui-ai/design/${subpath}.d.ts`;\n if (vfs.has(directPath)) return resolved(directPath);\n }\n\n // @ggui-ai/design root\n if (name === '@ggui-ai/design') {\n return resolved('node_modules/@ggui-ai/design/index.d.ts');\n }\n\n // @ggui-ai/wire\n if (name === '@ggui-ai/wire') {\n return resolved('node_modules/@ggui-ai/wire/index.d.ts');\n }\n\n // @ggui-ai/gadgets — the STDLIB gadget package. Generated\n // code direct-imports the seven STDLIB hooks (`useGeolocation`, …)\n // as plain named exports from this specifier. The shipped `.d.ts`\n // (loaded into the VFS above) carries their real declarations.\n if (name === '@ggui-ai/gadgets') {\n return resolved('node_modules/@ggui-ai/gadgets/index.d.ts');\n }\n\n // Relative imports — resolve within VFS\n if (name.startsWith('./') || name.startsWith('../')) {\n const resolvedPath = resolveRelativeInVfs(vfs, containingFile, name);\n if (resolvedPath) return resolved(resolvedPath);\n }\n\n // Third-party gadget wrapper packages. The render handler fetches each\n // non-stdlib gadget's `.d.ts` and `typecheck` overlays it at\n // `node_modules/<package>/index.d.ts`. A generated direct import\n // `import { useX } from '<package>'` resolves through this\n // bare-specifier branch against the overlaid `.d.ts` — named\n // option/return types preserved instead of collapsing to `any`.\n // Generic VFS lookup — no per-package allow-list.\n const bareIndex = `node_modules/${name}/index.d.ts`;\n if (vfs.has(bareIndex)) return resolved(bareIndex);\n\n // Unknown module\n return { resolvedModule: undefined };\n}\n\nfunction createVfsHost(\n vfs: Map<string, VfsEntry>,\n componentCode: string,\n): ts.CompilerHost {\n // Create fresh source file for the user component\n const componentFile = ts.createSourceFile(\n 'Component.tsx',\n componentCode,\n ts.ScriptTarget.ES2020,\n true,\n ts.ScriptKind.TSX,\n );\n\n const host: ts.CompilerHost = {\n getSourceFile(fileName: string) {\n if (fileName === 'Component.tsx') return componentFile;\n const entry = vfs.get(fileName);\n if (entry) return entry.sourceFile;\n // Try normalized path (strip leading '/')\n const normalized = normalizeVfsPath(fileName);\n if (normalized) return vfs.get(normalized)?.sourceFile;\n return undefined;\n },\n getDefaultLibFileName() {\n return 'lib.es2020.full.d.ts';\n },\n writeFile() {\n // no-op\n },\n getCurrentDirectory() {\n return '/';\n },\n getCanonicalFileName(f: string) {\n return f;\n },\n useCaseSensitiveFileNames() {\n return true;\n },\n getNewLine() {\n return '\\n';\n },\n fileExists(f: string) {\n if (f === 'Component.tsx' || vfs.has(f)) return true;\n // Normalize bare-specifier lookups that TS's internal resolver\n // turns into relative-looking paths from getCurrentDirectory '/'.\n const normalized = normalizeVfsPath(f);\n if (normalized && vfs.has(normalized)) return true;\n return false;\n },\n readFile(f: string) {\n if (f === 'Component.tsx') return componentCode;\n const direct = vfs.get(f);\n if (direct) return direct.content;\n const normalized = normalizeVfsPath(f);\n if (normalized) return vfs.get(normalized)?.content;\n return undefined;\n },\n resolveModuleNameLiterals(\n moduleLiterals: readonly ts.StringLiteralLike[],\n containingFile: string,\n ): readonly ts.ResolvedModuleWithFailedLookupLocations[] {\n return moduleLiterals.map((literal) =>\n resolveModuleName(vfs, literal.text, containingFile),\n );\n },\n };\n\n return host;\n}\n\nfunction resolved(resolvedFileName: string): ts.ResolvedModuleWithFailedLookupLocations {\n return {\n resolvedModule: {\n resolvedFileName,\n isExternalLibraryImport: true,\n extension: ts.Extension.Dts,\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\nexport async function typecheck(\n code: string,\n /**\n * A `package -> .d.ts content` map for third-party gadget wrappers.\n * The render handler parallel-fetches each non-stdlib gadget's `.d.ts`\n * (via `GadgetDescriptor.typesUrl` + SRI) and threads the result\n * here. Each entry is overlaid into the per-call VFS at\n * `node_modules/<package>/index.d.ts`. A generated direct import\n * `import { useX } from '<package>'` resolves through the\n * bare-specifier branch in `resolveModuleName` directly against this\n * overlaid entry — named option/return types preserved, so a\n * wrong-typed hook call surfaces a blocking TS error instead of\n * collapsing to `any`.\n *\n * Stdlib gadgets (`package: '@ggui-ai/gadgets'`) need no entry —\n * `@ggui-ai/gadgets` ships its `.d.ts` into the VFS unconditionally.\n * Omit for standard-library-only callers.\n */\n dtsMap?: Readonly<Record<string, string>>,\n): Promise<TypeCheckResult> {\n const vfs = await loadVfs();\n\n // Overlay each third-party gadget wrapper's `.d.ts` per-call without\n // mutating the cached VFS. The bare-specifier branch in\n // `resolveModuleName` resolves a generated `import { useX } from\n // '<package>'` against the overlaid entry, so the LLM sandbox sees\n // the wrapper's strict hook/component types (named option/return\n // shapes) rather than `any`. A third-party wrapper with no `dtsMap`\n // entry collapses to `any` (TS2307 is non-blocking) — degraded UX,\n // not a generation blocker; the fix is to thread `gadgetTypes`.\n // Stdlib (`@ggui-ai/gadgets`) ships its `.d.ts` unconditionally.\n const effectiveVfs = (() => {\n const dtsEntries = dtsMap !== undefined ? Object.entries(dtsMap) : [];\n if (dtsEntries.length === 0) {\n return vfs;\n }\n const overlay = new Map(vfs);\n for (const [pkg, content] of dtsEntries) {\n parseAndStore(overlay, `node_modules/${pkg}/index.d.ts`, content);\n }\n return overlay;\n })();\n\n // Prepend a synthetic prefix for classic JSX mode:\n // - `import React from 'react'` — the classic-mode JSX factory\n // (`React.createElement`) needs the React namespace in scope.\n // - a `declare global` JSX shim — `@types/react` v19 dropped the\n // GLOBAL `JSX` namespace (it is `React.JSX` now); classic mode\n // resolves intrinsic elements + the `key`/`ref` carve-out through\n // the global `JSX`, so without this `<div>` degrades to `any` and\n // every typed component falsely rejects the intrinsic `key` prop.\n // Mirrors `react/jsx-runtime`'s own JSX namespace, hoisted global.\n // All prefix lines are skipped from diagnostics via `lineOffset`.\n // `@types/react` v19 removed the GLOBAL `JSX` namespace (it lives at\n // `React.JSX` now — un-exported, not reliably reachable through the\n // VFS). Classic JSX mode resolves intrinsic elements + the `key`\n // carve-out through the global `JSX`, so declare a self-contained\n // one. Intrinsic elements are permissive (`[elem]: any`): the\n // type-checker's value is on the design components — which keep\n // their real prop types regardless of this namespace — and on\n // runtime-crash detection, not on deep typing of raw HTML (which the\n // generation prompt discourages anyway).\n const globalJsxShim =\n 'declare global { namespace JSX {' +\n ' type ElementType = string | ((props: any) => any) | (new (props: any) => any);' +\n ' interface Element { type: any; props: any; key: string | number | null; }' +\n ' interface ElementClass { render(): any; }' +\n ' interface ElementAttributesProperty { props: object; }' +\n ' interface ElementChildrenAttribute { children: object; }' +\n ' interface IntrinsicAttributes { key?: string | number | bigint | null; }' +\n ' interface IntrinsicClassAttributes<T> { ref?: any; }' +\n ' interface IntrinsicElements { [elem: string]: any; }' +\n ' } }\\n';\n const SYNTHETIC_PREFIX = \"import React from 'react';\\n\" + globalJsxShim;\n const prefixedCode = SYNTHETIC_PREFIX + code;\n const lineOffset = SYNTHETIC_PREFIX.split('\\n').length - 1;\n\n const host = createVfsHost(effectiveVfs, prefixedCode);\n\n const program = ts.createProgram(['Component.tsx'], COMPILER_OPTIONS, host);\n const diagnostics = ts.getPreEmitDiagnostics(program);\n\n const errors: TypeCheckDiagnostic[] = [];\n const warnings: TypeCheckDiagnostic[] = [];\n\n for (const diag of diagnostics) {\n // Only report diagnostics from the user's component file\n if (diag.file && diag.file.fileName !== 'Component.tsx') continue;\n // Global diagnostics (no file) — skip as they come from config\n if (!diag.file) continue;\n // Skip diagnostics on the synthetic import prefix line\n const diagLine = ts.getLineAndCharacterOfPosition(diag.file, diag.start ?? 0).line;\n if (diagLine < lineOffset) continue;\n\n const rawLine = diag.file\n ? ts.getLineAndCharacterOfPosition(diag.file, diag.start ?? 0).line + 1\n : 0;\n // Subtract the synthetic import line to get the user's original line number\n const line = Math.max(1, rawLine - lineOffset);\n const message = ts.flattenDiagnosticMessageText(diag.messageText, '\\n');\n const code_ = diag.code;\n\n // Extract the offending source line for the fix hint — without it\n // \"Type 'string' is not assignable to type 'number'\" is unactionable\n // because the LLM doesn't know which prop on which primitive. With\n // the line (\"<Icon size=\\\"md\\\" ...>\"), the LLM can target precisely.\n const sourceLines = diag.file.text.split('\\n');\n const sourceLine =\n rawLine >= 1 && rawLine <= sourceLines.length\n ? sourceLines[rawLine - 1]?.trim() ?? ''\n : '';\n\n const entry: TypeCheckDiagnostic = {\n code: code_,\n line,\n message,\n fix: generateFix(code_, message, sourceLine),\n };\n\n if (BLOCKING_CODES.has(code_)) {\n errors.push(entry);\n } else {\n warnings.push(entry);\n }\n }\n\n return { errors, warnings };\n}\n","/**\n * Contract + rendering-context prompt rendering.\n *\n * Surfaces every contract dimension explicitly to the LLM. A compact\n * bullet list of action / stream names is not enough — without the\n * Props shape, generation fails tier-0 on most cells with \"Props\n * interface missing required field '<name>'\".\n *\n * Renders:\n *\n * - Props contract — required + optional field lists + a real\n * TypeScript interface (compiled via `propsSpecToTypeScript`)\n * - Action contract — id / label / example / nextStep hint\n * - Stream contract — channel descriptions + payload schemas + source\n * - AgentTools — catalog of tools the contract references (via\n * `actionSpec[*].nextStep` and `streamSpec[*].source.tool`)\n * - ClientCapabilities — browser-capability gadgets the UI mounts:\n * hooks it calls (e.g. `useGeolocation` from `@ggui-ai/gadgets`)\n * and components it renders (e.g. `<LeafletMap />`)\n * - Required UI Surfaces — derived list of \"every contract surface\n * must have visible UI\" so the LLM doesn't drop required props or\n * skip rendering data sources\n *\n * Rendering context (device + shell + viewport) is layered alongside —\n * shells (`chat` / `fullscreen` / `partial`) require very different\n * sizing strategies and including the hint inline cuts a class of\n * \"designed for fullscreen, rendered in a chat bubble\" bugs.\n */\nimport type {\n ActionEntry,\n BlueprintVariance,\n GadgetDescriptor,\n DataContract,\n StreamChannelEntry,\n} from '@ggui-ai/protocol';\nimport { HOOK_NAME_RE, listContractGadgets } from '@ggui-ai/protocol';\nimport { propsSpecToTypeScript } from './check/index.js';\n\n/**\n * Rendering context — how and where the component will be displayed.\n * Affects layout strategy, sizing, and interaction patterns. Mirrors the\n * shape cloud's harness passes through `dispatchGeneration`.\n */\nexport interface RenderingContext {\n /** Device category — affects touch targets, column count, density. */\n readonly device: 'mobile' | 'tablet' | 'desktop' | 'spatial';\n /** Shell type — the container the component renders in. */\n readonly shell: 'chat' | 'fullscreen' | 'partial';\n /** Viewport dimensions in CSS pixels (optional). */\n readonly viewport?: { readonly width: number; readonly height: number };\n}\n\nconst SHELL_HINTS: Record<string, string> = {\n chat: `**Chat Shell — Inline card in scrolling conversation**\n- Container: width varies (60-80% of viewport), height: auto (natural content height)\n- Chrome: parent provides card border + shadow + rounded corners — do NOT add your own box-shadow or outer border-radius\n- Sizing: target 300-600px natural height. Do NOT use min-height: 100vh. No full-viewport designs.\n- Padding: use var(--ggui-spacing-2) for inner elements, var(--ggui-spacing-4) for sections. Tight.\n- Scrolling: parent scrolls (chat feed) — component should NOT have internal scroll\n- Layout: single column, compact. No sidebar. Stack everything vertically.\n- Width: width: 100% (fills parent bubble). Do NOT set max-width.`,\n\n fullscreen: `**Fullscreen Shell — Takes over entire viewport**\n- Container: width: 100vw, height: 100vh (100dvh on mobile for safe areas)\n- Chrome: NONE — your component IS the entire UI. You own all visual chrome.\n- Sizing: fill the viewport. Use min-height: 100vh or height: 100%. Edge-to-edge.\n- Padding: component owns ALL padding. Use var(--ggui-spacing-6) or larger for breathing room.\n- Scrolling: component manages its own scroll if content exceeds viewport (overflow-y: auto)\n- Layout: can use multi-column, sidebars, headers/footers. Full creative control.\n- Navigation: swipe left/right between pages is handled by the shell — don't implement it.\n- DO NOT add outer margins or max-width containers. Fill the space.`,\n\n partial: `**Partial Shell — Embedded panel in a larger page**\n- Container: width constrained by parent (could be 400px sidebar or 800px main area)\n- Chrome: parent may or may not have border — add your own Card/shadow if needed\n- Sizing: width: 100% to fill parent. Use max-width if you want to center content.\n- Padding: use standard spacing (var(--ggui-spacing-4) to var(--ggui-spacing-6))\n- Scrolling: can scroll internally (overflow-y: auto) — parent does NOT scroll for you\n- Layout: responsive within the container. Flex-wrap for variable parent widths.\n- Context: other UI around you (nav, sidebar) — don't duplicate page-level chrome.`,\n};\n\nconst DEVICE_HINTS: Record<string, string> = {\n mobile: `**Mobile:** Single column. Touch targets 44px+. Compact padding. Stack all sections vertically.`,\n tablet: `**Tablet:** 1-2 columns adaptive. Medium touch targets. Side-by-side where space allows.`,\n desktop: `**Desktop:** Multi-column. Hover states. Dense layouts. Keyboard shortcuts. Pointer interactions.`,\n spatial: `**Spatial:** Fixed-size floating panel (~600x400). High contrast. Large text. No hover (gaze/hand input).`,\n};\n\n/** Build rendering-context block to inject into user prompt. */\nexport function buildRenderingContext(ctx: RenderingContext): string {\n const parts = [\n '## Rendering Context',\n `- Device: ${ctx.device}`,\n ctx.viewport ? `- Viewport: ${ctx.viewport.width}×${ctx.viewport.height}px` : '',\n `- Shell: ${ctx.shell}`,\n '',\n DEVICE_HINTS[ctx.device] || '',\n SHELL_HINTS[ctx.shell] || '',\n ].filter(Boolean);\n return parts.join('\\n');\n}\n\n/** Append rendering context to user prompt if present. */\nexport function injectRenderingContext(\n userPrompt: string,\n rendering?: RenderingContext,\n): string {\n if (!rendering) return userPrompt;\n return userPrompt + '\\n\\n' + buildRenderingContext(rendering);\n}\n\n/**\n * Build the contract-context block. Surfaces every dimension of the\n * contract so the LLM has a complete spec rather than the user-prompt\n * narrative alone — the eval scores against the contract regardless of\n * what prose the prompt carries, so contract-first rendering closes\n * the prompt-vs-contract drift class.\n */\nexport function buildContractsContext(\n contract: DataContract,\n /**\n * Operator-registered gadget catalog. When a\n * `clientCapabilities.gadgets[*]` ref is THIN (just `{hook}` —\n * no per-binding `package`), look the hook up here to surface the\n * correct package in user-prompt lines like `via useLeafletMap from\n * @ggui-samples/gadget-leaflet`. Mirrors the same plumb in\n * `harness/prompts.ts:buildContractsContext` — both surfaces are\n * separate copies used by different call sites (this one is for\n * `createUiGenerator`, the other for the benchmark / dispatch path).\n */\n appGadgets?: readonly GadgetDescriptor[],\n): string {\n const parts: string[] = [];\n\n // The LLM direct-imports each hook from its own package\n // (resolved from the per-entry `package` field). The parameter stays\n // on the signature so the TypeScript sandbox augmentation layer can\n // still consume it.\n void appGadgets;\n\n // `intent` is not a contract field; `userPrompt` already carries\n // the agent's narrative as the prompt body, so the contract-context\n // block focuses on the structural shape (props / actions / streams /\n // contexts). There is no categorical `interaction` mode label: the\n // four-spec surface (props / actions / streams / contexts) describes\n // the wire exhaustively — actions drive turns, contexts observe\n // state, and there is no third category.\n\n if (contract.propsSpec) {\n const propsSpec = contract.propsSpec;\n const requiredFields = Object.entries(propsSpec.properties || {})\n .filter(([, e]) => (e as { required?: boolean }).required)\n .map(([k]) => k);\n const optionalFields = Object.entries(propsSpec.properties || {})\n .filter(([, e]) => !(e as { required?: boolean }).required)\n .map(([k]) => k);\n\n const tsInterface = propsSpecToTypeScript(propsSpec);\n const fieldInfo: string[] = [];\n if (requiredFields.length > 0) fieldInfo.push(`Required: ${requiredFields.join(', ')}`);\n if (optionalFields.length > 0) fieldInfo.push(`Optional: ${optionalFields.join(', ')}`);\n\n parts.push(\n `## Props Contract (MUST use these exact prop names and types)\\n\\n` +\n (fieldInfo.length > 0 ? fieldInfo.join('\\n') + '\\n\\n' : '') +\n `\\`\\`\\`typescript\\ninterface Props ${tsInterface}\\n\\`\\`\\``,\n );\n }\n\n if (contract.actionSpec && Object.keys(contract.actionSpec).length > 0) {\n const actions = Object.entries(contract.actionSpec)\n .map(([id, entry]: [string, ActionEntry]) => {\n let line = ` - ${id}: \"${entry.label ?? id}\"${entry.description ? ` — ${entry.description}` : ''}`;\n if (entry.example !== undefined) {\n line += `\\n Example payload: \\`${JSON.stringify(entry.example)}\\``;\n }\n if (entry.nextStep) {\n line += `\\n Next-step hint: agent intends to call \\`${entry.nextStep}\\``;\n }\n return line;\n })\n .join('\\n');\n parts.push(`## Action Contract (wire these callbacks)\\n${actions}`);\n }\n\n if (contract.streamSpec && Object.keys(contract.streamSpec).length > 0) {\n const channels = Object.entries(contract.streamSpec)\n .map(([name, entry]: [string, StreamChannelEntry]) => {\n const bits = [` - ${name}`];\n if (entry.description) bits.push(`— ${entry.description}`);\n if (entry.source?.tool) {\n bits.push(`(source tool: \\`${entry.source.tool}\\`)`);\n }\n return bits.join(' ');\n })\n .join('\\n');\n parts.push(\n `## Stream Contract (real-time channels via \\`useStream(name)\\`)\\n${channels}\\n\\n` +\n `Payload schemas:\\n\\`\\`\\`json\\n${JSON.stringify(contract.streamSpec, null, 2)}\\n\\`\\`\\``,\n );\n }\n\n const agentTools = contract.agentCapabilities?.tools;\n if (agentTools && Object.keys(agentTools).length > 0) {\n const tools = Object.entries(agentTools)\n .map(([name, entry]) => {\n const bits = [` - **\\`${name}\\`**`];\n if (entry.toolInfo?.description) bits.push(`— ${entry.toolInfo.description}`);\n if (entry.usage) bits.push(`(usage: ${entry.usage})`);\n return bits.join(' ');\n })\n .join('\\n');\n parts.push(\n `## AgentTools Catalog (tools the contract references)\\n${tools}\\n\\n` +\n `These are referenced via \\`actionSpec[*].nextStep\\` (the tool the agent SHOULD call next ` +\n `after the action fires) and \\`streamSpec[*].source.tool\\` (the tool feeding a channel). ` +\n `The agent OWNS the call — your component does not invoke these directly.\\n\\n` +\n `Schemas:\\n\\`\\`\\`json\\n${JSON.stringify(agentTools, null, 2)}\\n\\`\\`\\``,\n );\n }\n\n const clientCapabilities = contract.clientCapabilities?.gadgets;\n const gadgetUses = listContractGadgets(contract);\n if (gadgetUses.length > 0) {\n // The wire `clientCapabilities.gadgets` is a package-keyed\n // two-level map; `listContractGadgets` flattens it to `GadgetUse[]`.\n // Kind is discriminated by the export-name grammar — `use`-prefixed\n // names are hooks (imported + CALLED), PascalCase names are\n // components (imported + RENDERED as JSX). Both are direct-imported.\n const caps = gadgetUses\n .map((use) => {\n const bits = [` - **\\`${use.name}\\`**`];\n bits.push(\n HOOK_NAME_RE.test(use.name)\n ? `via direct-imported hook \\`${use.name}\\` from \\`${use.package}\\` (call it)`\n : `via direct-imported component \\`<${use.name} />\\` from \\`${use.package}\\` (render as JSX)`,\n );\n if (use.description) bits.push(`— ${use.description}`);\n return bits.join(' ');\n })\n .join('\\n');\n parts.push(\n `## ClientCapabilities Contract (browser-capability gadgets the UI mounts)\\n${caps}\\n\\n` +\n `Hook entries are imported + called; component entries are imported + rendered as JSX. ` +\n `Capability values reach the agent only when the UI threads them into an actionSpec ` +\n `payload or contextSpec slot.\\n\\n` +\n `Declarations:\\n\\`\\`\\`json\\n${JSON.stringify(clientCapabilities, null, 2)}\\n\\`\\`\\``,\n );\n }\n\n // Required-UI derivation — the architectural fix at the heart of v15.\n // Every contract surface implies a visible UI element; eval scores\n // against the contract regardless of what the user-prompt narrative\n // says. Emitting the implied UI surfaces explicitly makes the contract\n // the single source of truth for \"what must render\".\n const uiRequirements: string[] = [];\n if (contract.propsSpec?.properties) {\n for (const [name, entry] of Object.entries(contract.propsSpec.properties)) {\n const required = (entry as { required?: boolean }).required;\n if (required !== false) {\n uiRequirements.push(\n `- **Required prop \\`${name}\\`** must appear somewhere in rendered DOM (display the value, or use it to drive a label/aria/key).`,\n );\n }\n }\n }\n if (contract.actionSpec) {\n for (const [name, entry] of Object.entries(contract.actionSpec)) {\n const label = (entry as ActionEntry).label ?? name;\n uiRequirements.push(\n `- **Action \\`${name}\\`** (\"${label}\") needs an interactive control that fires it (Button click, form submit, key press, etc.).`,\n );\n }\n }\n if (contract.streamSpec) {\n for (const name of Object.keys(contract.streamSpec)) {\n uiRequirements.push(\n `- **Stream \\`${name}\\`** must mirror into local state via \\`useEffect\\` and render the resulting state — \\`useStream\\` alone never re-renders the DOM.`,\n );\n }\n }\n // agentTools entries don't imply UI on their own — they're catalog\n // declarations the agent uses, not hooks the component invokes.\n // The implied UI lives on the referencing actionSpec entries\n // (already enumerated above) and streamSpec channels with `source`\n // declarations. No per-tool UI requirement is emitted here.\n for (const use of gadgetUses) {\n // Hook AND component exports are direct-imported; the\n // implied UI differs — a hook is called + its value threaded,\n // a component is rendered as a JSX element. Kind is discriminated\n // by the export-name grammar.\n const exportName = use.name;\n const pkg = use.package;\n uiRequirements.push(\n HOOK_NAME_RE.test(exportName)\n ? `- **ClientCapability \\`${exportName}\\`** — the boilerplate has direct-imported \\`${exportName}\\` from \\`${pkg}\\` above a \\`// DO NOT EDIT\\` banner; keep that import, call \\`${exportName}\\` inside the component, and thread the resulting value into a context slot or action payload if the agent needs to observe it.`\n : `- **ClientCapability \\`${exportName}\\`** — the boilerplate has direct-imported \\`${exportName}\\` from \\`${pkg}\\` above a \\`// DO NOT EDIT\\` banner; keep that import and RENDER \\`<${exportName} … />\\` as a JSX element in the tree you return.`,\n );\n }\n if (uiRequirements.length > 0) {\n parts.push(\n `## Required UI Surfaces (derived from contract — every surface below MUST have visible UI, eval will fail if any is missing)\\n` +\n uiRequirements.join('\\n'),\n );\n }\n\n return parts.join('\\n\\n');\n}\n\n/**\n * Append the contract-context block to a user prompt if a contract is present.\n *\n * `appGadgets` forwards into `buildContractsContext` so thin contract\n * refs (`{hook}` without per-binding `package`) resolve to the\n * registered descriptor's package in the prompt-emitted \"import X from\n * Y\" lines.\n */\nexport function injectContracts(\n userPrompt: string,\n contract?: DataContract,\n appGadgets?: readonly GadgetDescriptor[],\n): string {\n if (!contract) return userPrompt;\n const ctx = buildContractsContext(contract, appGadgets);\n if (!ctx) return userPrompt;\n return userPrompt + '\\n\\n' + ctx;\n}\n\n/**\n * Build the variance-context block. Surfaces the agent's declared\n * styling signals (persona, aesthetic, context, seedPrompt) so cold-gen\n * aligns the produced component with the requested variant. Each field\n * is optional; the block only renders when at least one is present.\n *\n * - `persona` names the USER mental model (e.g. \"data-analyst\",\n * \"mobile-first reader\") — drives copy register + density choices.\n * - `aesthetic` names the VISUAL treatment (e.g. \"glassmorphic\",\n * \"editorial\", \"brutalist\") — drives surface decoration, typographic\n * weight, color usage.\n * - `context` is structured key-value signal alongside the persona —\n * small JSON-safe shape, serialized verbatim into the prompt.\n * - `seedPrompt` is the operator's original natural-language steer that\n * produced this variant in cache storage — useful as a one-line\n * directive for cold-gen.\n *\n * Returns an empty string when no fields are populated so the inject\n * helper can no-op cleanly.\n */\nexport function buildVarianceContext(variance: BlueprintVariance): string {\n const lines: string[] = [];\n if (typeof variance.persona === 'string' && variance.persona.length > 0) {\n lines.push(`- **Persona**: ${variance.persona}`);\n }\n if (typeof variance.aesthetic === 'string' && variance.aesthetic.length > 0) {\n lines.push(`- **Aesthetic**: ${variance.aesthetic}`);\n }\n if (typeof variance.seedPrompt === 'string' && variance.seedPrompt.length > 0) {\n lines.push(`- **Seed prompt**: ${variance.seedPrompt}`);\n }\n if (\n variance.context !== undefined &&\n variance.context !== null &&\n typeof variance.context === 'object' &&\n !Array.isArray(variance.context) &&\n Object.keys(variance.context).length > 0\n ) {\n lines.push(`- **Context**: ${JSON.stringify(variance.context)}`);\n }\n if (lines.length === 0) return '';\n return [\n '## Variance (apply to styling, not contract shape)',\n '',\n 'The agent (or operator) declared these variant signals. Honor them in',\n 'the visual treatment + copy register — but the contract shape (props,',\n 'actions, contexts, streams) remains canonical. Variance changes HOW',\n 'the UI looks and reads, not WHAT it does.',\n '',\n ...lines,\n ].join('\\n');\n}\n\n/** Append the variance-context block to a user prompt if variance is present. */\nexport function injectVariance(\n userPrompt: string,\n variance?: BlueprintVariance,\n): string {\n if (!variance) return userPrompt;\n const block = buildVarianceContext(variance);\n if (!block) return userPrompt;\n return userPrompt + '\\n\\n' + block;\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../../src/boilerplate/json-schema-ts.ts","../../src/check/contract-validation.ts","../../src/check/type-checker.ts","../../src/contract-context.ts"],"names":["ts","listContractGadgets","HOOK_NAME_RE"],"mappings":"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","file":"prompts.js","sourcesContent":["// packages/ui-gen/src/boilerplate/json-schema-ts.ts\n//\n// JSON Schema → TypeScript type string converter.\n//\n// Pure, self-contained, no env reads, no framework deps. Exposed\n// publicly on `@ggui-ai/ui-gen/boilerplate` because it's the primitive\n// that turns a contract's JSON Schema into the `interface Props { ... }`\n// block the boilerplate generator writes.\n\nimport type { JsonSchema } from '@ggui-ai/protocol';\n\n/**\n * Convert a JsonSchema to a TypeScript type string — recursive.\n *\n * Handles: string, number, integer, boolean, null, array (+ tuples), object,\n * enum, oneOf/anyOf (unions), nullable, const, additionalProperties.\n */\nexport function jsonSchemaTypeToTs(schema: JsonSchema): string {\n // ── Union types (oneOf / anyOf) ──\n const unionMembers = schema.oneOf ?? schema.anyOf;\n if (unionMembers?.length) {\n const types = [...new Set(unionMembers.map(s => jsonSchemaTypeToTs(s)))];\n return types.length === 1 ? types[0] : types.join(' | ');\n }\n\n // ── Const (literal type) ──\n if (schema.const !== undefined) {\n return typeof schema.const === 'string' ? `'${schema.const}'` : String(schema.const);\n }\n\n // ── Enum (union of literals) ──\n if (schema.enum?.length) {\n return schema.enum\n .map(v => typeof v === 'string' ? `'${v}'` : String(v))\n .join(' | ');\n }\n\n // ── Draft-07 type array (`['string','null']` — the canonical\n // nullable form since draft-2026-08-19): members' TS union. Each\n // member re-enters with the single type so object/array members\n // keep their properties/items; `nullable` is folded in here (not in\n // the recursion) so it can't double-append. ──\n if (Array.isArray(schema.type)) {\n const { nullable: _nullable, ...rest } = schema;\n const members = [...new Set(schema.type)].map((member) =>\n jsonSchemaTypeToTs({ ...rest, type: member }),\n );\n const unique = [...new Set(members)];\n if (schema.nullable && !unique.includes('null')) unique.push('null');\n return unique.join(' | ');\n }\n\n let result: string;\n\n switch (schema.type) {\n case 'string':\n result = 'string';\n break;\n case 'number':\n case 'integer':\n result = 'number';\n break;\n case 'boolean':\n result = 'boolean';\n break;\n case 'null':\n return 'null';\n\n case 'array': {\n if (schema.items) {\n const itemType = jsonSchemaTypeToTs(schema.items);\n // Use Array<T> for complex object types, T[] for simple.\n // `items.type` may be a type SET — treat any set containing\n // 'object' like the single-string form.\n const itemsDeclareObject = Array.isArray(schema.items.type)\n ? schema.items.type.includes('object')\n : schema.items.type === 'object';\n result = (itemsDeclareObject && schema.items.properties)\n ? `Array<${itemType}>`\n : (itemType.includes('|') ? `(${itemType})[]` : `${itemType}[]`);\n } else {\n result = 'unknown[]';\n }\n break;\n }\n\n case 'object': {\n if (schema.properties) {\n const required = schema.required ?? [];\n const fields = Object.entries(schema.properties)\n .map(([key, prop]) => {\n const opt = !required.includes(key);\n return `${key}${opt ? '?' : ''}: ${jsonSchemaTypeToTs(prop)}`;\n })\n .join('; ');\n result = `{ ${fields} }`;\n } else if (schema.additionalProperties && typeof schema.additionalProperties === 'object') {\n result = `Record<string, ${jsonSchemaTypeToTs(schema.additionalProperties)}>`;\n } else {\n result = 'Record<string, unknown>';\n }\n break;\n }\n\n default:\n result = 'unknown';\n }\n\n // Apply nullable (OpenAPI 3.0 shorthand)\n if (schema.nullable && result !== 'unknown') {\n return `${result} | null`;\n }\n\n return result;\n}\n","// packages/ui-gen/src/check/contract-validation.ts\n//\n// Shared validation utilities for data-contract conformance —\n// deterministic tier-0 gate for `PropsSpec` / `StreamSpec` / `ActionSpec`\n// agreement between the LLM-emitted componentCode and the contract the\n// agent handed to `ggui_render`.\n//\n// Used by:\n// (1) Self-check soft gate in the coding agent\n// (`coding-agent/self-check.ts`) — surfaces mismatches as\n// structured violations for the LLM retry loop.\n// (2) Tier-0 hard gate in the evaluation pipeline — errors (severity\n// `error`) are blocking; warnings surface as advisory.\n// (3) Adapter tool-family wiring for multi-SDK generation runs.\n// (4) Benchmarks + LLM prompt construction — `jsonSchemaTypeToTs` +\n// `propsSpecToTypeScript` + `inferPropsSpecFromSampleData` are\n// pure helpers reused outside the check path. Re-exported here so\n// those callers don't need a separate import for the subpath.\n//\n// Deps are pure: `@ggui-ai/protocol` (public types) + `typescript`\n// (compiler API) + the in-package `jsonSchemaTypeToTs` primitive from\n// `../boilerplate/json-schema-ts.js`.\n\nimport type {\n PropsSpec,\n StreamSpec,\n ActionSpec,\n DataContract,\n JsonSchema,\n JsonValue,\n PropEntry,\n JsonObject,\n} from \"@ggui-ai/protocol\";\nimport ts from \"typescript\";\n\n// Re-export the pure JSON-Schema → TypeScript-type converter from the\n// in-package `boilerplate/` primitive, so consumers of the tier-0 check\n// surface (self-check, tiers, benchmarks, prompts, adapters) import a\n// single symbol from a single subpath.\nexport { jsonSchemaTypeToTs } from \"../boilerplate/json-schema-ts.js\";\nimport { jsonSchemaTypeToTs } from \"../boilerplate/json-schema-ts.js\";\n\n// =============================================================================\n// Contract Issue\n// =============================================================================\n\nexport interface ContractIssue {\n severity: \"error\" | \"warning\";\n field: string;\n message: string;\n fix: string;\n}\n\n// =============================================================================\n// Props Interface Extraction (AST-based)\n// =============================================================================\n\ninterface ExtractedProp {\n name: string;\n type: string;\n optional: boolean;\n}\n\n/**\n * Extract field names and types from a TypeScript Props interface in source code.\n * Uses the TypeScript compiler API — handles nested types, generics, unions correctly.\n */\nexport function extractPropsInterface(code: string): ExtractedProp[] | null {\n const sf = ts.createSourceFile(\"component.tsx\", code, ts.ScriptTarget.Latest, true, ts.ScriptKind.TSX);\n const props: ExtractedProp[] = [];\n\n function visitMembers(members: ts.NodeArray<ts.TypeElement>): void {\n for (const member of members) {\n if (ts.isPropertySignature(member) && member.name) {\n const name = member.name.getText(sf);\n const type = member.type ? member.type.getText(sf) : \"unknown\";\n const optional = !!member.questionToken;\n props.push({ name, type, optional });\n }\n }\n }\n\n function visit(node: ts.Node): void {\n // interface Props { ... }\n if (ts.isInterfaceDeclaration(node) && node.name.text === \"Props\") {\n visitMembers(node.members);\n return;\n }\n // type Props = { ... }\n if (ts.isTypeAliasDeclaration(node) && node.name.text === \"Props\") {\n if (ts.isTypeLiteralNode(node.type)) {\n visitMembers(node.type.members);\n }\n return;\n }\n ts.forEachChild(node, visit);\n }\n\n visit(sf);\n return props.length > 0 ? props : null;\n}\n\n// =============================================================================\n// Contract Validation\n// =============================================================================\n\n/**\n * Compare extracted Props interface against a PropsSpec contract.\n * Returns issues: errors for missing required fields, warnings for type mismatches.\n */\nexport function validatePropsAgainstSchema(\n code: string,\n spec: PropsSpec,\n): ContractIssue[] {\n const issues: ContractIssue[] = [];\n const extracted = extractPropsInterface(code);\n\n if (!extracted) {\n // Can't extract — not necessarily an error (might use inline types)\n return [];\n }\n\n const extractedMap = new Map(extracted.map((p) => [p.name, p]));\n\n for (const [propName, entry] of Object.entries(spec.properties)) {\n const extractedProp = extractedMap.get(propName);\n\n if (!extractedProp) {\n if (entry.required) {\n issues.push({\n severity: \"error\",\n field: propName,\n message: `Props interface is missing required field '${propName}' from the data contract`,\n fix: `Add \\`${propName}${entry.required ? \"\" : \"?\"}: ${jsonSchemaTypeToTs(entry.schema)}\\` to your Props interface`,\n });\n } else {\n issues.push({\n severity: \"warning\",\n field: propName,\n message: `Props interface is missing optional field '${propName}' from the data contract`,\n fix: `Consider adding \\`${propName}?: ${jsonSchemaTypeToTs(entry.schema)}\\` to your Props interface`,\n });\n }\n continue;\n }\n\n // Basic type compatibility check (only if we have type info)\n if (extractedProp.type !== \"unknown\") {\n const expectedTsType = jsonSchemaTypeToTs(entry.schema);\n if (!isTypeCompatible(extractedProp.type, expectedTsType, entry.schema)) {\n issues.push({\n severity: \"warning\",\n field: propName,\n message: `Field '${propName}' has type '${extractedProp.type}' but contract expects '${expectedTsType}'`,\n fix: `Change \\`${propName}\\` type to \\`${expectedTsType}\\``,\n });\n }\n }\n }\n\n return issues;\n}\n\n/**\n * Check if a TypeScript type string is compatible with a JSON Schema type.\n * Loose comparison — we only check the base type, not full structural equality.\n */\nfunction isTypeCompatible(tsType: string, expectedTsType: string, schema: JsonSchema): boolean {\n const normalized = tsType.replace(/\\s/g, \"\").toLowerCase();\n\n switch (schema.type) {\n case \"string\":\n return normalized.includes(\"string\");\n case \"number\":\n case \"integer\":\n return normalized.includes(\"number\");\n case \"boolean\":\n return normalized.includes(\"boolean\");\n case \"array\":\n return normalized.includes(\"[]\") || normalized.includes(\"array\");\n case \"object\":\n return ![\"string\", \"number\", \"boolean\"].some((t) => normalized === t);\n default:\n return true;\n }\n}\n\n// =============================================================================\n// JSON Schema → TypeScript Conversion (for LLM prompts)\n// =============================================================================\n\n// `jsonSchemaTypeToTs` lives in `@ggui-ai/ui-gen/boilerplate` and is\n// re-exported at the top of this file.\n\n/**\n * Convert a full PropsSpec to a TypeScript interface string for LLM prompt injection.\n * Includes required/optional markers and descriptions as comments.\n */\nexport function propsSpecToTypeScript(spec: PropsSpec, indent = 2): string {\n const pad = \" \".repeat(indent);\n const lines: string[] = [];\n\n for (const [propName, entry] of Object.entries(spec.properties)) {\n if (entry.description) {\n lines.push(`${pad}/** ${entry.description} */`);\n }\n const optional = !entry.required;\n const tsType = jsonSchemaTypeToTs(entry.schema);\n const defaultStr = entry.default !== undefined ? ` // default: ${JSON.stringify(entry.default)}` : \"\";\n lines.push(`${pad}${propName}${optional ? \"?\" : \"\"}: ${tsType};${defaultStr}`);\n // Include example as a comment so the LLM sees the exact data shape\n if (entry.example !== undefined) {\n const exampleStr = JSON.stringify(entry.example, null, 2)\n .split(\"\\n\")\n .map((l, i) => (i === 0 ? `${pad}// Example: ${l}` : `${pad}// ${l}`))\n .join(\"\\n\");\n lines.push(exampleStr);\n }\n }\n\n return `{\\n${lines.join(\"\\n\")}\\n}`;\n}\n\n// =============================================================================\n// StreamSpec Validation\n// =============================================================================\n\n/**\n * Validate that the generated source code properly handles stream events.\n * Requires useStream() wire hooks — legacy DOM event patterns are no longer accepted.\n */\nexport function validateStreamSpecConformance(\n code: string,\n spec: StreamSpec,\n): ContractIssue[] {\n const issues: ContractIssue[] = [];\n\n const usesWireStream = code.includes(\"useStream\");\n\n if (!usesWireStream) {\n issues.push({\n severity: \"error\",\n field: \"__streamSpec__\",\n message: \"Component does not handle stream events — use useStream() from the boilerplate wire hooks\",\n fix: \"Use the useStream('eventName') hook from the boilerplate to receive real-time data from the agent\",\n });\n return issues;\n }\n\n // Check each declared channel is referenced in the code. `spec` is a\n // flat `Record<channelName, StreamChannelEntry>` — iterate directly.\n for (const [channelName, entry] of Object.entries(spec)) {\n if (!code.includes(`'${channelName}'`) && !code.includes(`\"${channelName}\"`)) {\n issues.push({\n severity: \"warning\",\n field: channelName,\n message: `StreamSpec declares channel '${channelName}' but component doesn't reference it`,\n fix: `Add useStream('${channelName}') to receive ${entry.description || channelName} events`,\n });\n }\n }\n\n return issues;\n}\n\n// =============================================================================\n// ActionSpec Validation\n// =============================================================================\n\n/**\n * Validate that the generated source code wires the declared actions.\n * Checks that action IDs are referenced via useAction() wire hooks, string literals, or prop callbacks.\n */\nexport function validateActionSpecConformance(\n code: string,\n spec: ActionSpec,\n): ContractIssue[] {\n const issues: ContractIssue[] = [];\n\n // `spec` is a flat `Record<actionId, ActionEntry>` — iterate directly.\n for (const [actionId, entry] of Object.entries(spec)) {\n // Check the action ID or its label is referenced in the code\n const idReferenced = code.includes(`'${actionId}'`) || code.includes(`\"${actionId}\"`);\n const labelReferenced = code.includes(entry.label);\n const callbackName = `on${actionId.charAt(0).toUpperCase()}${actionId.slice(1)}`;\n const callbackReferenced = code.includes(callbackName);\n\n if (!idReferenced && !labelReferenced && !callbackReferenced) {\n issues.push({\n severity: \"error\",\n field: actionId,\n message: `ActionSpec declares action '${actionId}' (\"${entry.label}\") but component doesn't wire it`,\n fix: `Use the useAction('${actionId}') hook from the boilerplate and wire it to a button or form submit`,\n });\n }\n }\n\n return issues;\n}\n\n// =============================================================================\n// Unified Contract Validation\n// =============================================================================\n\n/**\n * Validate all contract (props, stream, actions) against the source code.\n * Returns combined issues from all three validations.\n */\nexport function validateAllContracts(\n code: string,\n contract: DataContract,\n): ContractIssue[] {\n const issues: ContractIssue[] = [];\n\n if (contract.propsSpec) {\n issues.push(...validatePropsAgainstSchema(code, contract.propsSpec));\n }\n\n if (contract.streamSpec) {\n issues.push(...validateStreamSpecConformance(code, contract.streamSpec));\n }\n\n if (contract.actionSpec) {\n issues.push(...validateActionSpecConformance(code, contract.actionSpec));\n }\n\n return issues;\n}\n\n// =============================================================================\n// Sample Data → PropsSpec Inference (backward compat)\n// =============================================================================\n\n/**\n * Infer a PropsSpec from sample data.\n * Convenience utility for migration and for agents that pass sample data instead of schemas.\n * All fields are marked as required (can't distinguish required/optional from data alone).\n */\nexport function inferPropsSpecFromSampleData(data: JsonObject): PropsSpec {\n const properties: Record<string, PropEntry> = {};\n\n for (const [key, value] of Object.entries(data)) {\n properties[key] = {\n schema: inferJsonSchema(value),\n required: true,\n example: value as JsonValue,\n };\n }\n\n return { properties };\n}\n\nfunction inferJsonSchema(value: unknown): JsonSchema {\n if (value === null || value === undefined) return { type: \"null\" };\n if (typeof value === \"string\") return { type: \"string\" };\n if (typeof value === \"number\") return { type: \"number\" };\n if (typeof value === \"boolean\") return { type: \"boolean\" };\n\n if (Array.isArray(value)) {\n if (value.length === 0) return { type: \"array\" };\n return {\n type: \"array\",\n items: inferJsonSchema(value[0]),\n };\n }\n\n if (typeof value === \"object\") {\n const properties: Record<string, JsonSchema> = {};\n const required: string[] = [];\n for (const [k, v] of Object.entries(value as JsonObject)) {\n properties[k] = inferJsonSchema(v);\n required.push(k);\n }\n return { type: \"object\", properties, required };\n }\n\n return { type: \"string\" };\n}\n","// packages/ui-gen/src/check/type-checker.ts\n//\n// TypeScript type-checker for LLM-generated UI components. Uses the\n// TypeScript compiler API with a virtual filesystem to type-check TSX\n// code against React types and `@ggui-ai/design` primitives, giving\n// the LLM familiar TS error feedback without writing anything to disk.\n//\n// The checker builds a virtual filesystem seeded with `typescript/lib`,\n// `@types/react`, `@ggui-ai/design/dist`, and `@ggui-ai/wire/dist`\n// `.d.ts` content, then drives `ts.createProgram` against a synthetic\n// `Component.tsx`. Exposed on `@ggui-ai/ui-gen/check` so\n// `createUiGenerator` runs the same tier-0 gate as the hosted\n// generation path.\n//\n// Path-resolution note: the three workspace-rooted lookups —\n// `.pnpm/@types+react@*`, `packages/design/dist`, and\n// `packages/wire/dist` — are anchored relative to this file. tsup\n// bundles `src/check/index.ts` (splitting: false) into a flat\n// `dist/check/index.js`, so the src and dist locations sit at the same\n// depth and both resolve correctly. `typescript/lib/typescript.js`\n// keeps using `createRequire(import.meta.url).resolve` —\n// position-independent, so no change.\n//\n// Used by:\n// (1) Tier-0 `type_check` — blocking errors (see BLOCKING_CODES)\n// map to PRODUCTION_FAILED; non-blocking TS diagnostics surface\n// as warnings so the LLM can iterate on non-crash-risk findings\n// without stalling the harness.\n// (2) The `self_check` tool — called from generator SDKs (Anthropic /\n// OpenAI / Google) to give the LLM TS feedback mid-generation.\n// (3) The tier-0 orchestrator `runTier0Checks` — runs in parallel\n// with the wire-preservation + lint checks under the same tier\n// budget.\n//\n// Design notes that motivate non-obvious choices:\n// - Classic React JSX mode (`ts.JsxEmit.React`). The synthetic\n// prefix (see SYNTHETIC_PREFIX) supplies `import React from\n// 'react'` for the classic JSX factory PLUS a self-contained\n// global `JSX` namespace. `@types/react` v19 removed the global\n// `JSX` namespace that classic mode resolves intrinsic elements +\n// the `key`/`ref` carve-out through; without the shim, `<div>`\n// degrades to `any` and every typed component falsely rejects the\n// intrinsic `key` prop. Automatic mode was tried but its\n// `react/jsx-runtime` resolution does not survive the VFS.\n// - `strict: false` + `strictNullChecks: true`: we want the runtime\n// crash classes (`undefined.foo()`, `null.bar`) to surface as\n// blocking, but not the optional-chaining / exhaustive-check\n// noise that full `strict` would emit on LLM code.\n// - `types: []`: prevents the VFS from auto-pulling every\n// `@types/*` package that happens to be hoisted — only react and\n// the design/wire dist types should be reachable, matching the\n// forbidden-import policy enforced by `react-linter` elsewhere.\n// - TS2307 (\"Cannot find module\") is deliberately NOT blocking —\n// Lambda bundles code without type declarations, so the VFS can't\n// see every package that may exist at runtime. Forbidden imports\n// are caught by `runSelfChecks` regex instead.\n\nimport ts from 'typescript';\nimport fs from 'fs';\nimport path from 'path';\nimport { createRequire } from 'module';\nimport { describeAllowedImports } from '../validation/allowed-imports.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface TypeCheckDiagnostic {\n code: number;\n line: number;\n message: string;\n fix: string;\n}\n\nexport interface TypeCheckResult {\n errors: TypeCheckDiagnostic[]; // Blocking\n warnings: TypeCheckDiagnostic[]; // Non-blocking\n}\n\n// ---------------------------------------------------------------------------\n// Blocking error codes — these would break at runtime\n// ---------------------------------------------------------------------------\n\nconst BLOCKING_CODES = new Set([\n 2304, // Cannot find name\n // 2307 (Cannot find module) is NOT blocking — Lambda bundles code without\n // type declarations, so the VFS can't resolve react/@ggui-ai/design.\n // Forbidden imports are caught by runSelfChecks regex instead.\n 2305, // Module has no exported member\n 2322, // Type not assignable\n 2339, // Property does not exist on type\n 2741, // Missing required property\n 2769, // No overload matches this call\n 17004, // Cannot use JSX unless '--jsx' flag\n 18047, // 'X' is possibly 'null' — causes runtime crash\n 18048, // 'X' is possibly 'undefined' — causes runtime crash\n]);\n\n// ---------------------------------------------------------------------------\n// Fix suggestions per error code\n// ---------------------------------------------------------------------------\n\nfunction generateFix(code: number, message?: string, sourceLine?: string): string {\n // Source-line hint: when the failing line is short and self-contained,\n // appending it gives the LLM a precise target. Skip for very long lines\n // (>140 chars — usually multi-prop JSX where the line itself is the\n // diagnostic) since they bloat the violation envelope without helping.\n const sourceHint =\n sourceLine && sourceLine.length > 0 && sourceLine.length <= 140\n ? ` Offending line: \\`${sourceLine}\\``\n : '';\n\n // Detect event handler on non-interactive primitive\n if ((code === 2322 || code === 2339) && message && /onClick|onDoubleClick|onMouseEnter|onMouseLeave|onPress/.test(message)) {\n return `This structural primitive has no event handlers of its own — add the trait as a PROP: as={Clickable} (then onClick works), imported from @ggui-ai/design. Do NOT wrap it in <Clickable>…</Clickable>; as is a prop, not a wrapper element.${sourceHint}`;\n }\n\n // Detect underscore-prefix-on-prop trap: `Property '_X' does not exist`.\n // LLM saw an unused-var warning on `X` and tried to silence it by renaming\n // the destructure to `_X`, breaking prop access. Steer back.\n if (code === 2339 && message && /Property '_[a-zA-Z]/.test(message)) {\n return `You renamed a prop with a leading underscore to silence \\`no-unused-vars\\`, but the prop on \\`Props\\` doesn't have that prefix. Restore the original name; better, don't destructure props you won't render — access them via \\`props.fieldName\\` only when needed.${sourceHint}`;\n }\n\n // Detect \"Cannot find name\" for destructured props\n if (code === 2304 && message && !message.includes('module') && !message.includes('import')) {\n return `This name is not defined in scope. Either you destructured props (use \\`props.fieldName\\` directly instead) OR you removed a helper declaration in this patch but kept a JSX/expression reference to it. Read your full patch and either restore the declaration or remove the reference.${sourceHint}`;\n }\n\n // Detect \"unknown is not assignable to ReactNode\"\n if ((code === 2322 || code === 2769) && message && /unknown.*ReactNode|ReactNode.*unknown/.test(message)) {\n return `This value has type 'unknown' and cannot be rendered in JSX. Cast it: String(value) or add a type annotation.${sourceHint}`;\n }\n\n switch (code) {\n case 2307:\n return `Only these imports are allowed: ${describeAllowedImports()}${sourceHint}`;\n case 2322:\n case 2769:\n return `Type mismatch on this expression. Check the offending line below — the prop name in JSX is what TypeScript is rejecting; the message tells you the expected type.${sourceHint}`;\n case 2339:\n return `This prop doesn't exist on this component — check the available props on the component's interface (visible at the top of the file, or in the design-system reference).${sourceHint}`;\n case 2305:\n return `This name is not defined. Check your imports and variable declarations.${sourceHint}`;\n case 2741:\n return `A required prop is missing. Check the component's Props interface.${sourceHint}`;\n case 18047:\n case 18048:\n return `This value might be null/undefined. Every dereference on the same nullable still needs \\`?.\\` — \\`x?.foo && x?.bar\\`, NOT \\`x?.foo && x.bar\\`. Or hoist: \\`const v = x; if (!v) return null;\\` then access \\`v.foo\\`/\\`v.bar\\` unguarded.${sourceHint}`;\n default:\n return `Review the TypeScript error and fix the type issue.${sourceHint}`;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Virtual Filesystem — lazy singleton\n// ---------------------------------------------------------------------------\n\ninterface VfsEntry {\n content: string;\n sourceFile: ts.SourceFile;\n}\n\nlet vfsCache: Map<string, VfsEntry> | null = null;\n\nfunction parseAndStore(\n vfs: Map<string, VfsEntry>,\n virtualPath: string,\n content: string,\n): void {\n const sourceFile = ts.createSourceFile(\n virtualPath,\n content,\n ts.ScriptTarget.ES2020,\n true,\n virtualPath.endsWith('.tsx') ? ts.ScriptKind.TSX : ts.ScriptKind.TS,\n );\n vfs.set(virtualPath, { content, sourceFile });\n}\n\n/**\n * Recursively walk a directory and return all file paths matching a filter.\n */\nfunction walkDir(dir: string, filter: (f: string) => boolean): string[] {\n const results: string[] = [];\n for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {\n const full = path.join(dir, entry.name);\n if (entry.isDirectory()) {\n results.push(...walkDir(full, filter));\n } else if (filter(full)) {\n results.push(full);\n }\n }\n return results;\n}\n\n/**\n * Resolve `@types/react`'s root directory through Node's module\n * resolution. Position-independent — walks the consumer's node_modules\n * chain. Works in monorepo (where the type package is hoisted under\n * `<root>/node_modules/.pnpm/@types+react@*`) and in standalone\n * `packages/`-only installs (e.g. the projected public OSS repo where\n * the `packages/` prefix is stripped, so a hardcoded `../../../..`\n * walk lands in the wrong place).\n */\nfunction findReactTypesDir(): string | null {\n try {\n // `@types/react` doesn't expose `package.json` as a public subpath,\n // so resolve via the always-present `index.d.ts`.\n const indexDts = createRequire(import.meta.url).resolve('@types/react/index.d.ts');\n return path.dirname(indexDts);\n } catch {\n return null;\n }\n}\n\n/**\n * Resolve a workspace package's `dist/` directory through Node's\n * module resolution. Like `findReactTypesDir`, this works in both\n * monorepo (where the package is hoisted/symlinked) and standalone\n * installs — no relative-path math required.\n */\nfunction findPackageDistDir(pkg: string): string | null {\n try {\n const pkgJson = createRequire(import.meta.url).resolve(`${pkg}/package.json`);\n const dist = path.join(path.dirname(pkgJson), 'dist');\n return fs.existsSync(dist) ? dist : null;\n } catch {\n return null;\n }\n}\n\nasync function loadVfs(): Promise<Map<string, VfsEntry>> {\n if (vfsCache) return vfsCache;\n\n const vfs = new Map<string, VfsEntry>();\n\n // ---- 1. TypeScript lib files ----\n // `createRequire(import.meta.url).resolve('typescript/lib/typescript.js')`\n // is position-independent — it walks the consumer's node_modules chain,\n // so this lookup works identically from core/, packages/ui-gen/, and any\n // future host package.\n const require_ = createRequire(import.meta.url);\n const tsDir = path.dirname(require_.resolve('typescript/lib/typescript.js'));\n const libFiles = fs.readdirSync(tsDir).filter((f) => /^lib\\..*\\.d\\.ts$/.test(f));\n for (const file of libFiles) {\n const content = fs.readFileSync(path.join(tsDir, file), 'utf-8');\n parseAndStore(vfs, file, content);\n }\n\n // ---- 2. React types ----\n // Resolved through `createRequire(import.meta.url).resolve('@types/\n // react/index.d.ts')` — position-independent. Works the same in\n // monorepo (hoisted to root) and in standalone `packages/`-only\n // installs (the projected public OSS repo strips the `packages/`\n // prefix, so a hardcoded `../../../..` walk would miss).\n const reactDir = findReactTypesDir();\n if (reactDir) {\n const reactDts = fs.readdirSync(reactDir).filter((f) => f.endsWith('.d.ts'));\n for (const file of reactDts) {\n const content = fs.readFileSync(path.join(reactDir, file), 'utf-8');\n parseAndStore(vfs, `node_modules/@types/react/${file}`, content);\n }\n }\n\n // ---- 3. Design system types ----\n const designDist = findPackageDistDir('@ggui-ai/design');\n if (designDist) {\n const dtsFiles = walkDir(designDist, (f) => f.endsWith('.d.ts') && !f.endsWith('.d.ts.map'));\n for (const file of dtsFiles) {\n const rel = path.relative(designDist, file);\n const content = fs.readFileSync(file, 'utf-8');\n parseAndStore(vfs, `node_modules/@ggui-ai/design/${rel}`, content);\n }\n }\n\n // ---- 4. Wire module types ----\n const wireDist = findPackageDistDir('@ggui-ai/wire');\n if (wireDist) {\n const dtsFiles = walkDir(wireDist, (f) => f.endsWith('.d.ts') && !f.endsWith('.d.ts.map'));\n for (const file of dtsFiles) {\n const rel = path.relative(wireDist, file);\n const content = fs.readFileSync(file, 'utf-8');\n parseAndStore(vfs, `node_modules/@ggui-ai/wire/${rel}`, content);\n }\n }\n\n // ---- 5. Gadgets types ----\n // Generated component code direct-imports standard-library gadget\n // hooks — `import { useGeolocation } from '@ggui-ai/gadgets'`.\n // Loading the `@ggui-ai/gadgets` package's shipped `.d.ts` into the\n // VFS makes those bare-named imports resolve against the real hook\n // declarations (named option/return types preserved). The standard\n // gadget hooks are plain named exports the resolver finds directly.\n const clientLibsDist = findPackageDistDir('@ggui-ai/gadgets');\n if (clientLibsDist) {\n const dtsFiles = walkDir(\n clientLibsDist,\n (f) => f.endsWith('.d.ts') && !f.endsWith('.d.ts.map'),\n );\n for (const file of dtsFiles) {\n const rel = path.relative(clientLibsDist, file);\n const content = fs.readFileSync(file, 'utf-8');\n parseAndStore(vfs, `node_modules/@ggui-ai/gadgets/${rel}`, content);\n }\n }\n\n vfsCache = vfs;\n return vfs;\n}\n\n// ---------------------------------------------------------------------------\n// Module resolution helper\n// ---------------------------------------------------------------------------\n\n/**\n * Try to resolve a relative import within the VFS.\n * Given `containingFile` (e.g. `node_modules/@ggui-ai/design/primitives/index.d.ts`)\n * and `importPath` (e.g. `./Card`), try:\n * - <resolved>.d.ts\n * - <resolved>/index.d.ts\n * - <resolved>.ts\n */\nfunction resolveRelativeInVfs(\n vfs: Map<string, VfsEntry>,\n containingFile: string,\n importPath: string,\n): string | undefined {\n // Strip leading '/' from containingFile — TS may prepend getCurrentDirectory()\n const normalized = containingFile.startsWith('/') ? containingFile.slice(1) : containingFile;\n const dir = path.posix.dirname(normalized);\n const resolved = path.posix.normalize(`${dir}/${importPath}`);\n\n // Try .d.ts extension\n const withDts = `${resolved}.d.ts`;\n if (vfs.has(withDts)) return withDts;\n\n // Try /index.d.ts\n const withIndexDts = `${resolved}/index.d.ts`;\n if (vfs.has(withIndexDts)) return withIndexDts;\n\n // Try .ts extension\n const withTs = `${resolved}.ts`;\n if (vfs.has(withTs)) return withTs;\n\n // Might already be the exact path\n if (vfs.has(resolved)) return resolved;\n\n return undefined;\n}\n\n// ---------------------------------------------------------------------------\n// CompilerHost factory\n// ---------------------------------------------------------------------------\n\nconst COMPILER_OPTIONS: ts.CompilerOptions = {\n target: ts.ScriptTarget.ES2020,\n module: ts.ModuleKind.ESNext,\n // Classic React JSX mode. The synthetic prefix (SYNTHETIC_PREFIX)\n // supplies `import React from 'react'` plus a `declare global` JSX\n // namespace shim: `@types/react` v19 removed the *global* `JSX`\n // namespace (it lives at `React.JSX` now), which classic mode needs\n // for `JSX.IntrinsicElements` and the `key`/`ref` carve-out. Without\n // the shim, `<div>` degrades to `any` and every typed component\n // falsely rejects the intrinsic `key` prop.\n jsx: ts.JsxEmit.React,\n moduleResolution: ts.ModuleResolutionKind.Bundler,\n strict: false,\n strictNullChecks: true, // Catch undefined.foo() errors that cause runtime crashes\n noEmit: true,\n skipLibCheck: true,\n noImplicitAny: true,\n types: [],\n esModuleInterop: true,\n};\n\n/**\n * Normalize a path that TS's internal module resolution might generate.\n * Since getCurrentDirectory() returns '/', TS will probe paths like\n * '/node_modules/@types/react/jsx-runtime.d.ts'. We strip the leading\n * '/' to match our VFS keys.\n */\nfunction normalizeVfsPath(f: string): string | null {\n if (f.startsWith('/')) {\n return f.slice(1);\n }\n return null;\n}\n\n/**\n * Core module resolution logic shared between resolveModuleNameLiterals\n * (for user imports) and resolveModuleNames (for TS-internal imports\n * like jsxImportSource).\n */\nfunction resolveModuleName(\n vfs: Map<string, VfsEntry>,\n name: string,\n containingFile: string,\n): ts.ResolvedModuleWithFailedLookupLocations {\n // React core\n if (name === 'react') {\n return resolved('node_modules/@types/react/index.d.ts');\n }\n if (name === 'react/jsx-runtime') {\n return resolved('node_modules/@types/react/jsx-runtime.d.ts');\n }\n if (name === 'react/jsx-dev-runtime') {\n const p = 'node_modules/@types/react/jsx-dev-runtime.d.ts';\n if (vfs.has(p)) return resolved(p);\n return resolved('node_modules/@types/react/jsx-runtime.d.ts');\n }\n\n // @ggui-ai/design sub-paths\n const designPrefix = '@ggui-ai/design/';\n if (name.startsWith(designPrefix)) {\n const subpath = name.slice(designPrefix.length);\n const indexPath = `node_modules/@ggui-ai/design/${subpath}/index.d.ts`;\n if (vfs.has(indexPath)) return resolved(indexPath);\n const directPath = `node_modules/@ggui-ai/design/${subpath}.d.ts`;\n if (vfs.has(directPath)) return resolved(directPath);\n }\n\n // @ggui-ai/design root\n if (name === '@ggui-ai/design') {\n return resolved('node_modules/@ggui-ai/design/index.d.ts');\n }\n\n // @ggui-ai/wire\n if (name === '@ggui-ai/wire') {\n return resolved('node_modules/@ggui-ai/wire/index.d.ts');\n }\n\n // @ggui-ai/gadgets — the STDLIB gadget package. Generated\n // code direct-imports the seven STDLIB hooks (`useGeolocation`, …)\n // as plain named exports from this specifier. The shipped `.d.ts`\n // (loaded into the VFS above) carries their real declarations.\n if (name === '@ggui-ai/gadgets') {\n return resolved('node_modules/@ggui-ai/gadgets/index.d.ts');\n }\n\n // Relative imports — resolve within VFS\n if (name.startsWith('./') || name.startsWith('../')) {\n const resolvedPath = resolveRelativeInVfs(vfs, containingFile, name);\n if (resolvedPath) return resolved(resolvedPath);\n }\n\n // Third-party gadget wrapper packages. The render handler fetches each\n // non-stdlib gadget's `.d.ts` and `typecheck` overlays it at\n // `node_modules/<package>/index.d.ts`. A generated direct import\n // `import { useX } from '<package>'` resolves through this\n // bare-specifier branch against the overlaid `.d.ts` — named\n // option/return types preserved instead of collapsing to `any`.\n // Generic VFS lookup — no per-package allow-list.\n const bareIndex = `node_modules/${name}/index.d.ts`;\n if (vfs.has(bareIndex)) return resolved(bareIndex);\n\n // Unknown module\n return { resolvedModule: undefined };\n}\n\nfunction createVfsHost(\n vfs: Map<string, VfsEntry>,\n componentCode: string,\n): ts.CompilerHost {\n // Create fresh source file for the user component\n const componentFile = ts.createSourceFile(\n 'Component.tsx',\n componentCode,\n ts.ScriptTarget.ES2020,\n true,\n ts.ScriptKind.TSX,\n );\n\n const host: ts.CompilerHost = {\n getSourceFile(fileName: string) {\n if (fileName === 'Component.tsx') return componentFile;\n const entry = vfs.get(fileName);\n if (entry) return entry.sourceFile;\n // Try normalized path (strip leading '/')\n const normalized = normalizeVfsPath(fileName);\n if (normalized) return vfs.get(normalized)?.sourceFile;\n return undefined;\n },\n getDefaultLibFileName() {\n return 'lib.es2020.full.d.ts';\n },\n writeFile() {\n // no-op\n },\n getCurrentDirectory() {\n return '/';\n },\n getCanonicalFileName(f: string) {\n return f;\n },\n useCaseSensitiveFileNames() {\n return true;\n },\n getNewLine() {\n return '\\n';\n },\n fileExists(f: string) {\n if (f === 'Component.tsx' || vfs.has(f)) return true;\n // Normalize bare-specifier lookups that TS's internal resolver\n // turns into relative-looking paths from getCurrentDirectory '/'.\n const normalized = normalizeVfsPath(f);\n if (normalized && vfs.has(normalized)) return true;\n return false;\n },\n readFile(f: string) {\n if (f === 'Component.tsx') return componentCode;\n const direct = vfs.get(f);\n if (direct) return direct.content;\n const normalized = normalizeVfsPath(f);\n if (normalized) return vfs.get(normalized)?.content;\n return undefined;\n },\n resolveModuleNameLiterals(\n moduleLiterals: readonly ts.StringLiteralLike[],\n containingFile: string,\n ): readonly ts.ResolvedModuleWithFailedLookupLocations[] {\n return moduleLiterals.map((literal) =>\n resolveModuleName(vfs, literal.text, containingFile),\n );\n },\n };\n\n return host;\n}\n\nfunction resolved(resolvedFileName: string): ts.ResolvedModuleWithFailedLookupLocations {\n return {\n resolvedModule: {\n resolvedFileName,\n isExternalLibraryImport: true,\n extension: ts.Extension.Dts,\n },\n };\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\nexport async function typecheck(\n code: string,\n /**\n * A `package -> .d.ts content` map for third-party gadget wrappers.\n * The render handler parallel-fetches each non-stdlib gadget's `.d.ts`\n * (via `GadgetDescriptor.typesUrl` + SRI) and threads the result\n * here. Each entry is overlaid into the per-call VFS at\n * `node_modules/<package>/index.d.ts`. A generated direct import\n * `import { useX } from '<package>'` resolves through the\n * bare-specifier branch in `resolveModuleName` directly against this\n * overlaid entry — named option/return types preserved, so a\n * wrong-typed hook call surfaces a blocking TS error instead of\n * collapsing to `any`.\n *\n * Stdlib gadgets (`package: '@ggui-ai/gadgets'`) need no entry —\n * `@ggui-ai/gadgets` ships its `.d.ts` into the VFS unconditionally.\n * Omit for standard-library-only callers.\n */\n dtsMap?: Readonly<Record<string, string>>,\n): Promise<TypeCheckResult> {\n const vfs = await loadVfs();\n\n // Overlay each third-party gadget wrapper's `.d.ts` per-call without\n // mutating the cached VFS. The bare-specifier branch in\n // `resolveModuleName` resolves a generated `import { useX } from\n // '<package>'` against the overlaid entry, so the LLM sandbox sees\n // the wrapper's strict hook/component types (named option/return\n // shapes) rather than `any`. A third-party wrapper with no `dtsMap`\n // entry collapses to `any` (TS2307 is non-blocking) — degraded UX,\n // not a generation blocker; the fix is to thread `gadgetTypes`.\n // Stdlib (`@ggui-ai/gadgets`) ships its `.d.ts` unconditionally.\n const effectiveVfs = (() => {\n const dtsEntries = dtsMap !== undefined ? Object.entries(dtsMap) : [];\n if (dtsEntries.length === 0) {\n return vfs;\n }\n const overlay = new Map(vfs);\n for (const [pkg, content] of dtsEntries) {\n parseAndStore(overlay, `node_modules/${pkg}/index.d.ts`, content);\n }\n return overlay;\n })();\n\n // Prepend a synthetic prefix for classic JSX mode:\n // - `import React from 'react'` — the classic-mode JSX factory\n // (`React.createElement`) needs the React namespace in scope.\n // - a `declare global` JSX shim — `@types/react` v19 dropped the\n // GLOBAL `JSX` namespace (it is `React.JSX` now); classic mode\n // resolves intrinsic elements + the `key`/`ref` carve-out through\n // the global `JSX`, so without this `<div>` degrades to `any` and\n // every typed component falsely rejects the intrinsic `key` prop.\n // Mirrors `react/jsx-runtime`'s own JSX namespace, hoisted global.\n // All prefix lines are skipped from diagnostics via `lineOffset`.\n // `@types/react` v19 removed the GLOBAL `JSX` namespace (it lives at\n // `React.JSX` now — un-exported, not reliably reachable through the\n // VFS). Classic JSX mode resolves intrinsic elements + the `key`\n // carve-out through the global `JSX`, so declare a self-contained\n // one. Intrinsic elements are permissive (`[elem]: any`): the\n // type-checker's value is on the design components — which keep\n // their real prop types regardless of this namespace — and on\n // runtime-crash detection, not on deep typing of raw HTML (which the\n // generation prompt discourages anyway).\n const globalJsxShim =\n 'declare global { namespace JSX {' +\n ' type ElementType = string | ((props: any) => any) | (new (props: any) => any);' +\n ' interface Element { type: any; props: any; key: string | number | null; }' +\n ' interface ElementClass { render(): any; }' +\n ' interface ElementAttributesProperty { props: object; }' +\n ' interface ElementChildrenAttribute { children: object; }' +\n ' interface IntrinsicAttributes { key?: string | number | bigint | null; }' +\n ' interface IntrinsicClassAttributes<T> { ref?: any; }' +\n ' interface IntrinsicElements { [elem: string]: any; }' +\n ' } }\\n';\n const SYNTHETIC_PREFIX = \"import React from 'react';\\n\" + globalJsxShim;\n const prefixedCode = SYNTHETIC_PREFIX + code;\n const lineOffset = SYNTHETIC_PREFIX.split('\\n').length - 1;\n\n const host = createVfsHost(effectiveVfs, prefixedCode);\n\n const program = ts.createProgram(['Component.tsx'], COMPILER_OPTIONS, host);\n const diagnostics = ts.getPreEmitDiagnostics(program);\n\n const errors: TypeCheckDiagnostic[] = [];\n const warnings: TypeCheckDiagnostic[] = [];\n\n for (const diag of diagnostics) {\n // Only report diagnostics from the user's component file\n if (diag.file && diag.file.fileName !== 'Component.tsx') continue;\n // Global diagnostics (no file) — skip as they come from config\n if (!diag.file) continue;\n // Skip diagnostics on the synthetic import prefix line\n const diagLine = ts.getLineAndCharacterOfPosition(diag.file, diag.start ?? 0).line;\n if (diagLine < lineOffset) continue;\n\n const rawLine = diag.file\n ? ts.getLineAndCharacterOfPosition(diag.file, diag.start ?? 0).line + 1\n : 0;\n // Subtract the synthetic import line to get the user's original line number\n const line = Math.max(1, rawLine - lineOffset);\n const message = ts.flattenDiagnosticMessageText(diag.messageText, '\\n');\n const code_ = diag.code;\n\n // Extract the offending source line for the fix hint — without it\n // \"Type 'string' is not assignable to type 'number'\" is unactionable\n // because the LLM doesn't know which prop on which primitive. With\n // the line (\"<Icon size=\\\"md\\\" ...>\"), the LLM can target precisely.\n const sourceLines = diag.file.text.split('\\n');\n const sourceLine =\n rawLine >= 1 && rawLine <= sourceLines.length\n ? sourceLines[rawLine - 1]?.trim() ?? ''\n : '';\n\n const entry: TypeCheckDiagnostic = {\n code: code_,\n line,\n message,\n fix: generateFix(code_, message, sourceLine),\n };\n\n if (BLOCKING_CODES.has(code_)) {\n errors.push(entry);\n } else {\n warnings.push(entry);\n }\n }\n\n return { errors, warnings };\n}\n","/**\n * Contract + rendering-context prompt rendering.\n *\n * Surfaces every contract dimension explicitly to the LLM. A compact\n * bullet list of action / stream names is not enough — without the\n * Props shape, generation fails tier-0 on most cells with \"Props\n * interface missing required field '<name>'\".\n *\n * Renders:\n *\n * - Props contract — required + optional field lists + a real\n * TypeScript interface (compiled via `propsSpecToTypeScript`)\n * - Action contract — id / label / example / nextStep hint\n * - Stream contract — channel descriptions + payload schemas + source\n * - AgentTools — catalog of tools the contract references (via\n * `actionSpec[*].nextStep` and `streamSpec[*].source.tool`)\n * - ClientCapabilities — browser-capability gadgets the UI mounts:\n * hooks it calls (e.g. `useGeolocation` from `@ggui-ai/gadgets`)\n * and components it renders (e.g. `<LeafletMap />`)\n * - Required UI Surfaces — derived list of \"every contract surface\n * must have visible UI\" so the LLM doesn't drop required props or\n * skip rendering data sources\n *\n * Rendering context (device + shell + viewport) is layered alongside —\n * shells (`chat` / `fullscreen` / `partial`) require very different\n * sizing strategies and including the hint inline cuts a class of\n * \"designed for fullscreen, rendered in a chat bubble\" bugs.\n */\nimport type {\n ActionEntry,\n BlueprintVariance,\n GadgetDescriptor,\n DataContract,\n StreamChannelEntry,\n} from '@ggui-ai/protocol';\nimport { HOOK_NAME_RE, listContractGadgets } from '@ggui-ai/protocol';\nimport { propsSpecToTypeScript } from './check/index.js';\n\n/**\n * Rendering context — how and where the component will be displayed.\n * Affects layout strategy, sizing, and interaction patterns. Mirrors the\n * shape cloud's harness passes through `dispatchGeneration`.\n */\nexport interface RenderingContext {\n /** Device category — affects touch targets, column count, density. */\n readonly device: 'mobile' | 'tablet' | 'desktop' | 'spatial';\n /** Shell type — the container the component renders in. */\n readonly shell: 'chat' | 'fullscreen' | 'partial';\n /** Viewport dimensions in CSS pixels (optional). */\n readonly viewport?: { readonly width: number; readonly height: number };\n}\n\nconst SHELL_HINTS: Record<string, string> = {\n chat: `**Chat Shell — Inline card in scrolling conversation**\n- Container: width varies (60-80% of viewport), height: auto (natural content height)\n- Chrome: parent provides card border + shadow + rounded corners — do NOT add your own box-shadow or outer border-radius\n- Sizing: target 300-600px natural height. Do NOT use min-height: 100vh. No full-viewport designs.\n- Padding: use var(--ggui-spacing-2) for inner elements, var(--ggui-spacing-4) for sections. Tight.\n- Scrolling: parent scrolls (chat feed) — component should NOT have internal scroll\n- Layout: single column, compact. No sidebar. Stack everything vertically.\n- Width: width: 100% (fills parent bubble). Do NOT set max-width.`,\n\n fullscreen: `**Fullscreen Shell — Takes over entire viewport**\n- Container: width: 100vw, height: 100vh (100dvh on mobile for safe areas)\n- Chrome: NONE — your component IS the entire UI. You own all visual chrome.\n- Sizing: fill the viewport. Use min-height: 100vh or height: 100%. Edge-to-edge.\n- Padding: component owns ALL padding. Use var(--ggui-spacing-6) or larger for breathing room.\n- Scrolling: component manages its own scroll if content exceeds viewport (overflow-y: auto)\n- Layout: can use multi-column, sidebars, headers/footers. Full creative control.\n- Navigation: swipe left/right between pages is handled by the shell — don't implement it.\n- DO NOT add outer margins or max-width containers. Fill the space.`,\n\n partial: `**Partial Shell — Embedded panel in a larger page**\n- Container: width constrained by parent (could be 400px sidebar or 800px main area)\n- Chrome: parent may or may not have border — add your own Card/shadow if needed\n- Sizing: width: 100% to fill parent. Use max-width if you want to center content.\n- Padding: use standard spacing (var(--ggui-spacing-4) to var(--ggui-spacing-6))\n- Scrolling: can scroll internally (overflow-y: auto) — parent does NOT scroll for you\n- Layout: responsive within the container. Flex-wrap for variable parent widths.\n- Context: other UI around you (nav, sidebar) — don't duplicate page-level chrome.`,\n};\n\nconst DEVICE_HINTS: Record<string, string> = {\n mobile: `**Mobile:** Single column. Touch targets 44px+. Compact padding. Stack all sections vertically.`,\n tablet: `**Tablet:** 1-2 columns adaptive. Medium touch targets. Side-by-side where space allows.`,\n desktop: `**Desktop:** Multi-column. Hover states. Dense layouts. Keyboard shortcuts. Pointer interactions.`,\n spatial: `**Spatial:** Fixed-size floating panel (~600x400). High contrast. Large text. No hover (gaze/hand input).`,\n};\n\n/** Build rendering-context block to inject into user prompt. */\nexport function buildRenderingContext(ctx: RenderingContext): string {\n const parts = [\n '## Rendering Context',\n `- Device: ${ctx.device}`,\n ctx.viewport ? `- Viewport: ${ctx.viewport.width}×${ctx.viewport.height}px` : '',\n `- Shell: ${ctx.shell}`,\n '',\n DEVICE_HINTS[ctx.device] || '',\n SHELL_HINTS[ctx.shell] || '',\n ].filter(Boolean);\n return parts.join('\\n');\n}\n\n/** Append rendering context to user prompt if present. */\nexport function injectRenderingContext(\n userPrompt: string,\n rendering?: RenderingContext,\n): string {\n if (!rendering) return userPrompt;\n return userPrompt + '\\n\\n' + buildRenderingContext(rendering);\n}\n\n/**\n * Build the contract-context block. Surfaces every dimension of the\n * contract so the LLM has a complete spec rather than the user-prompt\n * narrative alone — the eval scores against the contract regardless of\n * what prose the prompt carries, so contract-first rendering closes\n * the prompt-vs-contract drift class.\n */\nexport function buildContractsContext(\n contract: DataContract,\n /**\n * Operator-registered gadget catalog. When a\n * `clientCapabilities.gadgets[*]` ref is THIN (just `{hook}` —\n * no per-binding `package`), look the hook up here to surface the\n * correct package in user-prompt lines like `via useLeafletMap from\n * @ggui-samples/gadget-leaflet`. Mirrors the same plumb in\n * `harness/prompts.ts:buildContractsContext` — both surfaces are\n * separate copies used by different call sites (this one is for\n * `createUiGenerator`, the other for the benchmark / dispatch path).\n */\n appGadgets?: readonly GadgetDescriptor[],\n): string {\n const parts: string[] = [];\n\n // The LLM direct-imports each hook from its own package\n // (resolved from the per-entry `package` field). The parameter stays\n // on the signature so the TypeScript sandbox augmentation layer can\n // still consume it.\n void appGadgets;\n\n // `intent` is not a contract field; `userPrompt` already carries\n // the agent's narrative as the prompt body, so the contract-context\n // block focuses on the structural shape (props / actions / streams /\n // contexts). There is no categorical `interaction` mode label: the\n // four-spec surface (props / actions / streams / contexts) describes\n // the wire exhaustively — actions drive turns, contexts observe\n // state, and there is no third category.\n\n if (contract.propsSpec) {\n const propsSpec = contract.propsSpec;\n const requiredFields = Object.entries(propsSpec.properties || {})\n .filter(([, e]) => (e as { required?: boolean }).required)\n .map(([k]) => k);\n const optionalFields = Object.entries(propsSpec.properties || {})\n .filter(([, e]) => !(e as { required?: boolean }).required)\n .map(([k]) => k);\n\n const tsInterface = propsSpecToTypeScript(propsSpec);\n const fieldInfo: string[] = [];\n if (requiredFields.length > 0) fieldInfo.push(`Required: ${requiredFields.join(', ')}`);\n if (optionalFields.length > 0) fieldInfo.push(`Optional: ${optionalFields.join(', ')}`);\n\n parts.push(\n `## Props Contract (MUST use these exact prop names and types)\\n\\n` +\n (fieldInfo.length > 0 ? fieldInfo.join('\\n') + '\\n\\n' : '') +\n `\\`\\`\\`typescript\\ninterface Props ${tsInterface}\\n\\`\\`\\``,\n );\n }\n\n if (contract.actionSpec && Object.keys(contract.actionSpec).length > 0) {\n const actions = Object.entries(contract.actionSpec)\n .map(([id, entry]: [string, ActionEntry]) => {\n let line = ` - ${id}: \"${entry.label ?? id}\"${entry.description ? ` — ${entry.description}` : ''}`;\n if (entry.example !== undefined) {\n line += `\\n Example payload: \\`${JSON.stringify(entry.example)}\\``;\n }\n if (entry.nextStep) {\n line += `\\n Next-step hint: agent intends to call \\`${entry.nextStep}\\``;\n }\n return line;\n })\n .join('\\n');\n parts.push(`## Action Contract (wire these callbacks)\\n${actions}`);\n }\n\n if (contract.streamSpec && Object.keys(contract.streamSpec).length > 0) {\n const channels = Object.entries(contract.streamSpec)\n .map(([name, entry]: [string, StreamChannelEntry]) => {\n const bits = [` - ${name}`];\n if (entry.description) bits.push(`— ${entry.description}`);\n if (entry.source?.tool) {\n bits.push(`(source tool: \\`${entry.source.tool}\\`)`);\n }\n return bits.join(' ');\n })\n .join('\\n');\n parts.push(\n `## Stream Contract (real-time channels via \\`useStream(name)\\`)\\n${channels}\\n\\n` +\n `Payload schemas:\\n\\`\\`\\`json\\n${JSON.stringify(contract.streamSpec, null, 2)}\\n\\`\\`\\``,\n );\n }\n\n const agentTools = contract.agentCapabilities?.tools;\n if (agentTools && Object.keys(agentTools).length > 0) {\n const tools = Object.entries(agentTools)\n .map(([name, entry]) => {\n const bits = [` - **\\`${name}\\`**`];\n if (entry.toolInfo?.description) bits.push(`— ${entry.toolInfo.description}`);\n if (entry.usage) bits.push(`(usage: ${entry.usage})`);\n return bits.join(' ');\n })\n .join('\\n');\n parts.push(\n `## AgentTools Catalog (tools the contract references)\\n${tools}\\n\\n` +\n `These are referenced via \\`actionSpec[*].nextStep\\` (the tool the agent SHOULD call next ` +\n `after the action fires) and \\`streamSpec[*].source.tool\\` (the tool feeding a channel). ` +\n `The agent OWNS the call — your component does not invoke these directly.\\n\\n` +\n `Schemas:\\n\\`\\`\\`json\\n${JSON.stringify(agentTools, null, 2)}\\n\\`\\`\\``,\n );\n }\n\n const clientCapabilities = contract.clientCapabilities?.gadgets;\n const gadgetUses = listContractGadgets(contract);\n if (gadgetUses.length > 0) {\n // The wire `clientCapabilities.gadgets` is a package-keyed\n // two-level map; `listContractGadgets` flattens it to `GadgetUse[]`.\n // Kind is discriminated by the export-name grammar — `use`-prefixed\n // names are hooks (imported + CALLED), PascalCase names are\n // components (imported + RENDERED as JSX). Both are direct-imported.\n const caps = gadgetUses\n .map((use) => {\n const bits = [` - **\\`${use.name}\\`**`];\n bits.push(\n HOOK_NAME_RE.test(use.name)\n ? `via direct-imported hook \\`${use.name}\\` from \\`${use.package}\\` (call it)`\n : `via direct-imported component \\`<${use.name} />\\` from \\`${use.package}\\` (render as JSX)`,\n );\n if (use.description) bits.push(`— ${use.description}`);\n return bits.join(' ');\n })\n .join('\\n');\n parts.push(\n `## ClientCapabilities Contract (browser-capability gadgets the UI mounts)\\n${caps}\\n\\n` +\n `Hook entries are imported + called; component entries are imported + rendered as JSX. ` +\n `Capability values reach the agent only when the UI threads them into an actionSpec ` +\n `payload or contextSpec slot.\\n\\n` +\n `Declarations:\\n\\`\\`\\`json\\n${JSON.stringify(clientCapabilities, null, 2)}\\n\\`\\`\\``,\n );\n }\n\n // Required-UI derivation — the architectural fix at the heart of v15.\n // Every contract surface implies a visible UI element; eval scores\n // against the contract regardless of what the user-prompt narrative\n // says. Emitting the implied UI surfaces explicitly makes the contract\n // the single source of truth for \"what must render\".\n const uiRequirements: string[] = [];\n if (contract.propsSpec?.properties) {\n for (const [name, entry] of Object.entries(contract.propsSpec.properties)) {\n // Opt-in required (`=== true`) — the wire gate's reading, ggui#528.\n // An omitted-`required` prop is OPTIONAL: the agent may leave it\n // out of a legitimate render, so the derived instruction must\n // demand a presence-safe component, not unconditional display.\n const required = (entry as { required?: boolean }).required === true;\n if (required) {\n uiRequirements.push(\n `- **Required prop \\`${name}\\`** must appear somewhere in rendered DOM (display the value, or use it to drive a label/aria/key).`,\n );\n } else {\n uiRequirements.push(\n `- **Optional prop \\`${name}\\`** may be ABSENT at runtime — the component must render without it (guard the access or apply its default) and must display it when it IS provided.`,\n );\n }\n }\n }\n if (contract.actionSpec) {\n for (const [name, entry] of Object.entries(contract.actionSpec)) {\n const label = (entry as ActionEntry).label ?? name;\n uiRequirements.push(\n `- **Action \\`${name}\\`** (\"${label}\") needs an interactive control that fires it (Button click, form submit, key press, etc.).`,\n );\n }\n }\n if (contract.streamSpec) {\n for (const name of Object.keys(contract.streamSpec)) {\n uiRequirements.push(\n `- **Stream \\`${name}\\`** must mirror into local state via \\`useEffect\\` and render the resulting state — \\`useStream\\` alone never re-renders the DOM.`,\n );\n }\n }\n // agentTools entries don't imply UI on their own — they're catalog\n // declarations the agent uses, not hooks the component invokes.\n // The implied UI lives on the referencing actionSpec entries\n // (already enumerated above) and streamSpec channels with `source`\n // declarations. No per-tool UI requirement is emitted here.\n for (const use of gadgetUses) {\n // Hook AND component exports are direct-imported; the\n // implied UI differs — a hook is called + its value threaded,\n // a component is rendered as a JSX element. Kind is discriminated\n // by the export-name grammar.\n const exportName = use.name;\n const pkg = use.package;\n uiRequirements.push(\n HOOK_NAME_RE.test(exportName)\n ? `- **ClientCapability \\`${exportName}\\`** — the boilerplate has direct-imported \\`${exportName}\\` from \\`${pkg}\\` above a \\`// DO NOT EDIT\\` banner; keep that import, call \\`${exportName}\\` inside the component, and thread the resulting value into a context slot or action payload if the agent needs to observe it.`\n : `- **ClientCapability \\`${exportName}\\`** — the boilerplate has direct-imported \\`${exportName}\\` from \\`${pkg}\\` above a \\`// DO NOT EDIT\\` banner; keep that import and RENDER \\`<${exportName} … />\\` as a JSX element in the tree you return.`,\n );\n }\n if (uiRequirements.length > 0) {\n parts.push(\n `## Required UI Surfaces (derived from contract — every surface below MUST have visible UI, eval will fail if any is missing)\\n` +\n uiRequirements.join('\\n'),\n );\n }\n\n return parts.join('\\n\\n');\n}\n\n/**\n * Append the contract-context block to a user prompt if a contract is present.\n *\n * `appGadgets` forwards into `buildContractsContext` so thin contract\n * refs (`{hook}` without per-binding `package`) resolve to the\n * registered descriptor's package in the prompt-emitted \"import X from\n * Y\" lines.\n */\nexport function injectContracts(\n userPrompt: string,\n contract?: DataContract,\n appGadgets?: readonly GadgetDescriptor[],\n): string {\n if (!contract) return userPrompt;\n const ctx = buildContractsContext(contract, appGadgets);\n if (!ctx) return userPrompt;\n return userPrompt + '\\n\\n' + ctx;\n}\n\n/**\n * Build the variance-context block. Surfaces the agent's declared\n * styling signals (persona, aesthetic, context, seedPrompt) so cold-gen\n * aligns the produced component with the requested variant. Each field\n * is optional; the block only renders when at least one is present.\n *\n * - `persona` names the USER mental model (e.g. \"data-analyst\",\n * \"mobile-first reader\") — drives copy register + density choices.\n * - `aesthetic` names the VISUAL treatment (e.g. \"glassmorphic\",\n * \"editorial\", \"brutalist\") — drives surface decoration, typographic\n * weight, color usage.\n * - `context` is structured key-value signal alongside the persona —\n * small JSON-safe shape, serialized verbatim into the prompt.\n * - `seedPrompt` is the operator's original natural-language steer that\n * produced this variant in cache storage — useful as a one-line\n * directive for cold-gen.\n *\n * Returns an empty string when no fields are populated so the inject\n * helper can no-op cleanly.\n */\nexport function buildVarianceContext(variance: BlueprintVariance): string {\n const lines: string[] = [];\n if (typeof variance.persona === 'string' && variance.persona.length > 0) {\n lines.push(`- **Persona**: ${variance.persona}`);\n }\n if (typeof variance.aesthetic === 'string' && variance.aesthetic.length > 0) {\n lines.push(`- **Aesthetic**: ${variance.aesthetic}`);\n }\n if (typeof variance.seedPrompt === 'string' && variance.seedPrompt.length > 0) {\n lines.push(`- **Seed prompt**: ${variance.seedPrompt}`);\n }\n if (\n variance.context !== undefined &&\n variance.context !== null &&\n typeof variance.context === 'object' &&\n !Array.isArray(variance.context) &&\n Object.keys(variance.context).length > 0\n ) {\n lines.push(`- **Context**: ${JSON.stringify(variance.context)}`);\n }\n if (lines.length === 0) return '';\n return [\n '## Variance (apply to styling, not contract shape)',\n '',\n 'The agent (or operator) declared these variant signals. Honor them in',\n 'the visual treatment + copy register — but the contract shape (props,',\n 'actions, contexts, streams) remains canonical. Variance changes HOW',\n 'the UI looks and reads, not WHAT it does.',\n '',\n ...lines,\n ].join('\\n');\n}\n\n/** Append the variance-context block to a user prompt if variance is present. */\nexport function injectVariance(\n userPrompt: string,\n variance?: BlueprintVariance,\n): string {\n if (!variance) return userPrompt;\n const block = buildVarianceContext(variance);\n if (!block) return userPrompt;\n return userPrompt + '\\n\\n' + block;\n}\n"]}
|
|
@@ -3,7 +3,7 @@ export { ActionSpec, DataContract, JsonSchema, PropsSpec, StreamSpec } from '@gg
|
|
|
3
3
|
import { AdapterResult } from '../adapters/types.js';
|
|
4
4
|
import { R as RenderingContext } from '../prompts-BVhtH3M1.js';
|
|
5
5
|
import { EvaluationResult } from '../evaluation/types.js';
|
|
6
|
-
import { b as EvalResult } from '../types-public-
|
|
6
|
+
import { b as EvalResult } from '../types-public-DIpjC-OA.js';
|
|
7
7
|
import 'zod';
|
|
8
8
|
import '../axes-CzLEMDeB.js';
|
|
9
9
|
import '../types-BOvHNG7K.js';
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { Q as QualityConfig } from '../types-public-
|
|
1
|
+
import { Q as QualityConfig } from '../types-public-DIpjC-OA.js';
|
|
2
2
|
import { DataContract, JsonObject, GadgetDescriptor } from '@ggui-ai/protocol';
|
|
3
|
-
import { A as AgentConfig } from '../llm-router-
|
|
3
|
+
import { A as AgentConfig } from '../llm-router-BEVtbAb9.js';
|
|
4
4
|
export { g as generateBoilerplate } from '../generate-BTfvELgl.js';
|
|
5
5
|
import '../axes-CzLEMDeB.js';
|
|
6
6
|
import '../types-BOvHNG7K.js';
|
|
@@ -18,6 +18,13 @@ interface AgentSpec {
|
|
|
18
18
|
* it. Absent for every pre-existing caller.
|
|
19
19
|
*/
|
|
20
20
|
routeOverride?: AgentConfig["routeOverride"];
|
|
21
|
+
/**
|
|
22
|
+
* Retry observer (#489) — see `AgentConfig.onRetry`. Threaded
|
|
23
|
+
* through unchanged so `createAgent()` can notify the caller when
|
|
24
|
+
* `apiCall()` retries a rate-limited request. Absent for every
|
|
25
|
+
* pre-existing caller (no-op).
|
|
26
|
+
*/
|
|
27
|
+
onRetry?: AgentConfig["onRetry"];
|
|
21
28
|
}
|
|
22
29
|
interface SingleComponentParams {
|
|
23
30
|
userPrompt: string;
|
package/dist/harness/runtime.js
CHANGED
|
@@ -48,10 +48,12 @@ These tokens adapt automatically to any theme (light, dark, branded). **ALWAYS u
|
|
|
48
48
|
- Borders/dividers \u2192 \`var(--ggui-color-outline)\` or \`var(--ggui-color-outlineVariant)\`
|
|
49
49
|
|
|
50
50
|
### State Colors
|
|
51
|
-
|
|
52
|
-
- var(--ggui-color-
|
|
53
|
-
- var(--ggui-color-
|
|
54
|
-
- var(--ggui-color-
|
|
51
|
+
Semantic families ship as SCALES; the \`500\` stop is the role color (per-mode brightness), \`50\` the tint, \`800\` the on-tint text:
|
|
52
|
+
- var(--ggui-color-success-500) \u2014 success states (tint bg: success-50, text on tint: success-800)
|
|
53
|
+
- var(--ggui-color-warning-500) \u2014 warning states (warning-50 / warning-800)
|
|
54
|
+
- var(--ggui-color-error-500) \u2014 error states, destructive actions (error-50 / error-800)
|
|
55
|
+
- var(--ggui-color-info-500) \u2014 informational (info-50 / info-800)
|
|
56
|
+
Prefer the primitives' \`tone="success|warning|error|info"\` prop over hand-written vars \u2014 it resolves to the same stops.
|
|
55
57
|
|
|
56
58
|
### IMPORTANT: Color Rules
|
|
57
59
|
- **NEVER** use hardcoded hex colors. ONLY use var(--ggui-color-*) tokens.
|
|
@@ -487,6 +489,15 @@ function jsonSchemaTypeToTs(schema) {
|
|
|
487
489
|
if (schema.enum?.length) {
|
|
488
490
|
return schema.enum.map((v) => typeof v === "string" ? `'${v}'` : String(v)).join(" | ");
|
|
489
491
|
}
|
|
492
|
+
if (Array.isArray(schema.type)) {
|
|
493
|
+
const { nullable: _nullable, ...rest } = schema;
|
|
494
|
+
const members = [...new Set(schema.type)].map(
|
|
495
|
+
(member) => jsonSchemaTypeToTs({ ...rest, type: member })
|
|
496
|
+
);
|
|
497
|
+
const unique = [...new Set(members)];
|
|
498
|
+
if (schema.nullable && !unique.includes("null")) unique.push("null");
|
|
499
|
+
return unique.join(" | ");
|
|
500
|
+
}
|
|
490
501
|
let result;
|
|
491
502
|
switch (schema.type) {
|
|
492
503
|
case "string":
|
|
@@ -504,7 +515,8 @@ function jsonSchemaTypeToTs(schema) {
|
|
|
504
515
|
case "array": {
|
|
505
516
|
if (schema.items) {
|
|
506
517
|
const itemType = jsonSchemaTypeToTs(schema.items);
|
|
507
|
-
|
|
518
|
+
const itemsDeclareObject = Array.isArray(schema.items.type) ? schema.items.type.includes("object") : schema.items.type === "object";
|
|
519
|
+
result = itemsDeclareObject && schema.items.properties ? `Array<${itemType}>` : itemType.includes("|") ? `(${itemType})[]` : `${itemType}[]`;
|
|
508
520
|
} else {
|
|
509
521
|
result = "unknown[]";
|
|
510
522
|
}
|
|
@@ -637,13 +649,14 @@ function generateBoilerplate(_userPrompt, contract, shellType, screen, composedS
|
|
|
637
649
|
const propsFields = [];
|
|
638
650
|
const propsProperties = contract?.propsSpec?.properties ?? {};
|
|
639
651
|
for (const [key, entry] of Object.entries(propsProperties)) {
|
|
640
|
-
const required = entry.required
|
|
652
|
+
const required = entry.required === true;
|
|
641
653
|
const nullable = entry.schema.nullable === true;
|
|
642
654
|
const tsType = jsonSchemaTypeToTs(entry.schema);
|
|
643
655
|
const fullType = nullable ? `${tsType} | null` : tsType;
|
|
644
656
|
const parts = [];
|
|
645
657
|
if (entry.description) parts.push(entry.description);
|
|
646
658
|
if (entry.default !== void 0) parts.push(`(default: ${JSON.stringify(entry.default)})`);
|
|
659
|
+
if (!required) parts.push("(optional \u2014 the agent may omit it; guard or fall back)");
|
|
647
660
|
const desc = parts.length > 0 ? ` // ${parts.join(" ")}` : "";
|
|
648
661
|
propsFields.push(` ${key}${required ? "" : "?"}: ${fullType};${desc}`);
|
|
649
662
|
}
|
|
@@ -1391,6 +1404,7 @@ A \`DataContract\` declares everything a GguiSession exchanges with the outside
|
|
|
1391
1404
|
|
|
1392
1405
|
Props arrive at mount and may be partial on first render; later prop changes reach the SAME mounted component via \`ggui_amend\` (in-place repaint) on the live channel. Stream channels start empty and fill over time. Context slots start at their declared default (often \`null\`). **Never assume a field exists before you read it.**
|
|
1393
1406
|
|
|
1407
|
+
- **Optional contract props**: a \`Props\` field marked \`?\` in the boilerplate may be omitted by the agent ENTIRELY \u2014 the wire gate only enforces \`required: true\` entries, and the harness verifies your component with those props stripped. Guard them (\`?? default\`, \`?.\`, or an empty-state branch) from the FIRST render.
|
|
1394
1408
|
- **Array iteration**: always default to \`[]\` before \`.map\`/\`.filter\`/\`.length\`. Use \`(props.items ?? []).map(...)\` not \`props.items.map(...)\`. Same for stream.all, stream.latest, etc.
|
|
1395
1409
|
- **Object access**: optional-chain through nested fields. \`props.user?.name ?? 'Anonymous'\` not \`props.user.name\`.
|
|
1396
1410
|
- **Number ops**: default before arithmetic. \`(props.count ?? 0) + 1\` not \`props.count + 1\`.
|