@ggui-ai/ui-gen 0.22.0 → 0.24.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.
Files changed (90) hide show
  1. package/dist/adapter-Du7P_5Fx.d.ts +201 -0
  2. package/dist/adapters/generation-dispatch.d.ts +33 -12
  3. package/dist/adapters/generation-dispatch.js +549 -129
  4. package/dist/adapters/generation-dispatch.js.map +1 -1
  5. package/dist/adapters/index.js +1 -1
  6. package/dist/adapters/index.js.map +1 -1
  7. package/dist/advanced/index.d.ts +5 -3
  8. package/dist/advanced/index.js +601 -144
  9. package/dist/advanced/index.js.map +1 -1
  10. package/dist/blueprint-validator.js +178 -54
  11. package/dist/blueprint-validator.js.map +1 -1
  12. package/dist/boilerplate.js +1 -0
  13. package/dist/boilerplate.js.map +1 -1
  14. package/dist/check/index.d.ts +2 -1
  15. package/dist/check/index.js +47 -1
  16. package/dist/check/index.js.map +1 -1
  17. package/dist/{create-ui-generator-BEkzEtNz.d.ts → create-ui-generator-GIhzx08x.d.ts} +34 -10
  18. package/dist/{dispatch-DJNpQ7DI.d.ts → dispatch-BnAH56p4.d.ts} +1 -1
  19. package/dist/evaluation/axis-checks/index.d.ts +4 -3
  20. package/dist/evaluation/axis-checks/index.js +19 -4
  21. package/dist/evaluation/axis-checks/index.js.map +1 -1
  22. package/dist/evaluation/axis-checks/registry.d.ts +2 -1
  23. package/dist/evaluation/axis-checks/registry.js +19 -4
  24. package/dist/evaluation/axis-checks/registry.js.map +1 -1
  25. package/dist/evaluation/index.d.ts +4 -3
  26. package/dist/evaluation/index.js +21 -6
  27. package/dist/evaluation/index.js.map +1 -1
  28. package/dist/harness/check/runtime-render/index.d.ts +27 -8
  29. package/dist/harness/check/runtime-render/index.js +217 -54
  30. package/dist/harness/check/runtime-render/index.js.map +1 -1
  31. package/dist/harness/check/runtime-render/render-check-worker.d.ts +8 -1
  32. package/dist/harness/check/runtime-render/render-check-worker.js +72 -32
  33. package/dist/harness/check/runtime-render/render-check-worker.js.map +1 -1
  34. package/dist/harness/index.d.ts +2 -1
  35. package/dist/harness/index.js +3 -2
  36. package/dist/harness/index.js.map +1 -1
  37. package/dist/harness/prompts.js.map +1 -1
  38. package/dist/harness/result-types.d.ts +2 -1
  39. package/dist/harness/runtime.d.ts +2 -1
  40. package/dist/harness/runtime.js +2 -1
  41. package/dist/harness/runtime.js.map +1 -1
  42. package/dist/harness/types-public.d.ts +18 -5
  43. package/dist/harness/types-public.js +1 -0
  44. package/dist/harness/types-public.js.map +1 -1
  45. package/dist/harness/validator-trace-sink.d.ts +2 -1
  46. package/dist/index.d.ts +38 -3
  47. package/dist/index.js +592 -144
  48. package/dist/index.js.map +1 -1
  49. package/dist/patch.d.ts +2 -1
  50. package/dist/policy.d.ts +7 -4
  51. package/dist/policy.js.map +1 -1
  52. package/dist/tools/get-primitives-ts.d.ts +1 -1
  53. package/dist/tools/get-primitives-ts.js +1 -1
  54. package/dist/tools/get-primitives-ts.js.map +1 -1
  55. package/dist/tools/render-check-worker.d.ts +7 -1
  56. package/dist/tools/render-check-worker.js +55 -23
  57. package/dist/tools/render-check-worker.js.map +1 -1
  58. package/dist/{types-public-mM_-1Yk3.d.ts → types-public-Bk6d1WLN.d.ts} +98 -9
  59. package/dist/validation/index.d.ts +1 -1
  60. package/dist/validation/index.js +1 -1
  61. package/dist/validation/index.js.map +1 -1
  62. package/dist/workflows.d.ts +2 -1
  63. package/package.json +12 -12
  64. package/src/adapters/generation-dispatch.ts +137 -96
  65. package/src/advanced/generator.ts +16 -1
  66. package/src/blueprint-validator.ts +21 -6
  67. package/src/boilerplate/hard-sections.ts +1 -0
  68. package/src/check/run-tier0.ts +80 -1
  69. package/src/coding-agent/tools.ts +6 -1
  70. package/src/create-ui-generator.ts +185 -95
  71. package/src/evaluation/axis-checks/checks/state-payload.ts +29 -4
  72. package/src/evaluation/types-public.ts +94 -8
  73. package/src/harness/check/runtime-render/adapter.ts +223 -58
  74. package/src/harness/check/runtime-render/index.ts +75 -2
  75. package/src/harness/check/runtime-render/render-check-host.ts +111 -26
  76. package/src/harness/check/runtime-render/render-check-worker.ts +83 -29
  77. package/src/harness/check/runtime-render/render-check.ts +173 -117
  78. package/src/harness/coding/generate-task-runner.ts +57 -4
  79. package/src/harness/coding/init-session.ts +13 -0
  80. package/src/harness/coding/run-eval-round.ts +238 -13
  81. package/src/harness/llm-router.ts +3 -3
  82. package/src/harness/policy.ts +11 -0
  83. package/src/harness/primitive-slice.ts +104 -0
  84. package/src/harness/types-public.ts +17 -5
  85. package/src/index.ts +4 -0
  86. package/src/policy.ts +7 -4
  87. package/src/tools/get-primitives-ts.ts +1 -1
  88. package/src/tools/render-check-worker.ts +74 -23
  89. package/src/validation/primitives.ts +1 -1
  90. package/dist/render-check-C0CqVceF.d.ts +0 -87
@@ -416,7 +416,12 @@ export async function executeTool(
416
416
  changes: normalizedChanges,
417
417
  });
418
418
  if (!patchResult.ok) {
419
- return { result: `FAILED: ${patchResult.error}`, error: true };
419
+ // ggui#1261: the engine refused the patch (overlap, line bounds, missing range) before
420
+ // anything applied. Say so in the log, and call it what it is — PATCH_INVALID, like every
421
+ // other structural refusal in this tool — so the turn is never counted as a self-check
422
+ // failure that did not run.
423
+ console.log(`[coding-agent] apply_changes: REJECTED | ${patchResult.error}`);
424
+ return { result: `PATCH_INVALID: ${patchResult.error}`, error: true };
420
425
  }
421
426
  const candidate = patchResult.sourceAfter ?? currentFile;
422
427
 
@@ -31,66 +31,96 @@
31
31
  * Callers who want a lightweight single-shot path can build it
32
32
  * themselves.
33
33
  */
34
- import type { SingleComponentParams } from './harness/runtime.js';
34
+ import type { SingleComponentParams } from "./harness/runtime.js";
35
35
  import type {
36
36
  GadgetDescriptor,
37
37
  GenerationError,
38
38
  JsonObject,
39
39
  GeneratorId,
40
40
  ModelRef,
41
- } from '@ggui-ai/protocol';
42
- import { isGeneratorId, modelRefOfRoute } from '@ggui-ai/protocol';
43
- import type { GadgetCatalogAdapter } from '@ggui-ai/gadgets';
41
+ } from "@ggui-ai/protocol";
42
+ import { isGeneratorId, modelRefOfRoute } from "@ggui-ai/protocol";
43
+ import type { GadgetCatalogAdapter } from "@ggui-ai/gadgets";
44
44
  import type {
45
45
  GenerationMetadata,
46
+ GenerationRuntimeProbe,
47
+ GenerationRuntimeProbeOutcome,
48
+ GenerationRuntimeProbeRepair,
46
49
  GeneratorTier,
47
50
  LlmProvider,
48
51
  UiGenerateInput,
49
52
  UiGenerateResult,
50
53
  UiGenerator,
51
- } from '@ggui-ai/mcp-server-core';
54
+ } from "@ggui-ai/mcp-server-core";
52
55
  import {
53
56
  formatGeneratorSlug,
54
57
  isValidGeneratorSlug,
55
58
  parseGeneratorSlug,
56
- } from '@ggui-ai/mcp-server-core';
57
- import { createGeneratorTools } from './adapters/index.js';
58
- import { dispatchGeneration } from './adapters/generation-dispatch.js';
59
- import { generatorBuild } from './generator-build.js';
60
- import { hasProfile } from './boilerplate/styling-profile.js';
61
- import { effortDials } from './effort.js';
62
- import type { ProviderName } from './adapters/types.js';
63
- import type { GenerationResult } from './harness/result-types.js';
59
+ } from "@ggui-ai/mcp-server-core";
60
+ import { createGeneratorTools } from "./adapters/index.js";
61
+ import { dispatchGeneration } from "./adapters/generation-dispatch.js";
62
+ import {
63
+ createRuntimeRenderCheck,
64
+ type RuntimeRenderProbeConfig,
65
+ } from "./harness/check/runtime-render/adapter.js";
66
+ import { generatorBuild } from "./generator-build.js";
67
+ import { hasProfile } from "./boilerplate/styling-profile.js";
68
+ import { effortDials } from "./effort.js";
69
+ import type { ProviderName } from "./adapters/types.js";
70
+ import type { GenerationResult } from "./harness/result-types.js";
64
71
  import {
65
72
  canvasForRenderingContext,
66
73
  injectContracts,
67
74
  injectRenderingContext,
68
75
  injectVariance,
69
- } from './contract-context.js';
70
- import type { RenderingContext } from './contract-context.js';
71
- import type { DesignMode } from './design-mode.js';
72
- import { DEFAULT_DESIGN_MODE } from './design-mode.js';
73
- import { resolveRoute, applyRouteToEnv } from './adapters/provider-router.js';
74
- import type { QualityConfig } from './evaluation/types-public.js';
75
- import type { EvaluationConfig } from './evaluation/types.js';
76
- import type { AgentConfig } from './harness/llm-router.js';
76
+ } from "./contract-context.js";
77
+ import type { RenderingContext } from "./contract-context.js";
78
+ import type { DesignMode } from "./design-mode.js";
79
+ import { DEFAULT_DESIGN_MODE } from "./design-mode.js";
80
+ import { resolveRoute, applyRouteToEnv } from "./adapters/provider-router.js";
81
+ import type { QualityConfig, RuntimeProbeMeta, RuntimeProbeRepair } from "./evaluation/types-public.js";
82
+ import type { EvaluationConfig } from "./evaluation/types.js";
83
+ import type { AgentConfig } from "./harness/llm-router.js";
77
84
 
78
85
  /** The slug for the OSS default seed generator. */
79
- const DEFAULT_TIER: GeneratorTier = 'default';
80
- const DEFAULT_MODEL: ModelRef = 'anthropic/claude-haiku-4-5';
86
+ const DEFAULT_TIER: GeneratorTier = "default";
87
+ const DEFAULT_MODEL: ModelRef = "anthropic/claude-haiku-4-5";
81
88
 
82
89
  export interface CreateUiGeneratorOptions {
83
90
  /**
84
- * Wire `DEFAULT_RUNTIME_RENDER_CHECK` into the harness's check leg.
85
- * When `true`, every coding turn ends with a happy-dom runtime-render
86
- * probe that catches contract-wiring bugs (missing `useAction()`,
87
- * wrong stream channel name, etc.).
91
+ * Wire the runtime-render check into the harness's check leg — one
92
+ * instance per generator, built from `runtimeRenderProbe` (the default
93
+ * bounds when it is absent). When `true`, the runtime-render probe runs
94
+ * ONCE at the exit decision,
95
+ * after the coding turns, in an isolated subprocess: it mounts the
96
+ * compiled component against the contract and catches crash-class and
97
+ * contract-wiring bugs (a render that throws, a missing `useAction()`,
98
+ * a wrong stream channel name). It is never on the per-turn hot path.
99
+ *
100
+ * Without an `evaluation` config the round is observe + one repair turn
101
+ * (ggui#1380): a recoverable render crash buys exactly one repair turn
102
+ * and one re-probe, then the generation serves what it has; a probe that
103
+ * timed out, could not run, or had nothing to probe never blocks and
104
+ * never repairs. The probe's status and timing ride the result's
105
+ * metadata (`runtimeProbe`, `evalMs`). With an `evaluation` config the
106
+ * probe is the evaluation round's exit gate instead.
88
107
  *
89
- * Default: `false` for OSS — keeps cold-start light. The probe pulls
90
- * happy-dom + @testing-library on first use (~700-1500ms cold). Bench
91
- * enables it via dispatch's own default for stricter quality gating.
108
+ * Default: `false` — keeps cold-start light. The probe pulls happy-dom
109
+ * + @testing-library on first use (~700-1500ms cold). A serving
110
+ * deployment that wants the probe's verdict on every card sets it.
92
111
  */
93
112
  readonly enableRuntimeRender?: boolean;
113
+ /**
114
+ * How the runtime-render probe runs its isolated worker (ggui#1380). A
115
+ * serving deployment sets the probe's wall-clock bound, worker heap and
116
+ * concurrency here; the defaults are the evaluation lane's (30 s, 512 MB,
117
+ * no concurrency cap). The generator builds ONE check from it and hands
118
+ * that instance to every generation it dispatches — the concurrency cap
119
+ * lives on the instance, so it bounds the workers this generator has
120
+ * live at once. Only read when `enableRuntimeRender` is on. Never part
121
+ * of the prompt or the identity.
122
+ */
123
+ readonly runtimeRenderProbe?: RuntimeRenderProbeConfig;
94
124
  /** Maximum coding attempts per generation pass. */
95
125
  readonly maxAttempts?: number;
96
126
  /** Maximum evaluation rounds. */
@@ -108,7 +138,7 @@ export interface CreateUiGeneratorOptions {
108
138
  * stylesheet to render under). Forwarded verbatim; never part of the
109
139
  * prompt or the identity. Default: none — the round runs as today.
110
140
  */
111
- readonly visualEvaluation?: SingleComponentParams['visualEvaluation'];
141
+ readonly visualEvaluation?: SingleComponentParams["visualEvaluation"];
112
142
  /** Quality config controlling eval tiers + improvement behavior. */
113
143
  readonly qualityConfig?: QualityConfig;
114
144
  /**
@@ -189,7 +219,7 @@ export interface CreateUiGeneratorOptions {
189
219
  * request. Absent (default) is a no-op — retries still happen and
190
220
  * still log via `console.warn`, just without a caller-side hook.
191
221
  */
192
- readonly onRetry?: AgentConfig['onRetry'];
222
+ readonly onRetry?: AgentConfig["onRetry"];
193
223
  /**
194
224
  * Generation observer. Invoked once per successful `generate()` with
195
225
  * the FULL harness result — source, compiled code, token counters,
@@ -221,11 +251,10 @@ export interface CreateUiGeneratorOptions {
221
251
  readonly designMode?: DesignMode;
222
252
  }
223
253
 
224
- export function createUiGenerator(
225
- options: CreateUiGeneratorOptions = {},
226
- ): UiGenerator {
254
+ export function createUiGenerator(options: CreateUiGeneratorOptions = {}): UiGenerator {
227
255
  const {
228
256
  enableRuntimeRender = false,
257
+ runtimeRenderProbe,
229
258
  maxAttempts,
230
259
  maxEvalRounds,
231
260
  evaluation,
@@ -237,6 +266,11 @@ export function createUiGenerator(
237
266
  onGenerated,
238
267
  designMode,
239
268
  } = options;
269
+ // ggui#1380 — ONE runtime-render check per generator: the instance owns
270
+ // the probe's concurrency cap, so it must outlive a single generation.
271
+ const runtimeRender = enableRuntimeRender
272
+ ? createRuntimeRenderCheck(runtimeRenderProbe)
273
+ : undefined;
240
274
 
241
275
  const identity = resolveIdentity(options);
242
276
 
@@ -273,9 +307,9 @@ export function createUiGenerator(
273
307
  });
274
308
  } catch (err) {
275
309
  return failWithoutMetadata(input, identity.slug, startedAt, {
276
- code: 'PRODUCTION_FAILED',
310
+ code: "PRODUCTION_FAILED",
277
311
  message: err instanceof Error ? err.message : String(err),
278
- details: { kind: 'route-resolution-failed' },
312
+ details: { kind: "route-resolution-failed" },
279
313
  });
280
314
  }
281
315
 
@@ -307,16 +341,16 @@ export function createUiGenerator(
307
341
  // flag is the only way the decision reaches `createAgent` /
308
342
  // `createVisionAgent` (which select the login client on it).
309
343
  const routeOverride =
310
- route.auth === 'claude-code-login'
344
+ route.auth === "claude-code-login"
311
345
  ? { claudeCodeLogin: true }
312
346
  : disableEnvMutation
313
347
  ? {
314
348
  apiKey:
315
- route.env['ANTHROPIC_API_KEY'] ??
316
- route.env['OPENAI_API_KEY'] ??
317
- route.env['GEMINI_API_KEY'] ??
318
- route.env['OPENROUTER_API_KEY'],
319
- useBedrock: route.env['CLAUDE_CODE_USE_BEDROCK'] === '1',
349
+ route.env["ANTHROPIC_API_KEY"] ??
350
+ route.env["OPENAI_API_KEY"] ??
351
+ route.env["GEMINI_API_KEY"] ??
352
+ route.env["OPENROUTER_API_KEY"],
353
+ useBedrock: route.env["CLAUDE_CODE_USE_BEDROCK"] === "1",
320
354
  }
321
355
  : undefined;
322
356
 
@@ -342,11 +376,9 @@ export function createUiGenerator(
342
376
  // descriptors stay byte-identical).
343
377
  const canvas = rendering ? canvasForRenderingContext(rendering) : undefined;
344
378
  const shellType = harnessShellForRendering(rendering);
345
- const promptWithVariance = injectVariance(
346
- promptWithRendering,
347
- input.variance,
348
- { profileDeclared: hasProfile(input.profile) },
349
- );
379
+ const promptWithVariance = injectVariance(promptWithRendering, input.variance, {
380
+ profileDeclared: hasProfile(input.profile),
381
+ });
350
382
 
351
383
  // Resolve appGadgets by precedence:
352
384
  // 1. input.appGadgets (caller pre-fetched, handler path)
@@ -359,14 +391,10 @@ export function createUiGenerator(
359
391
  input.appGadgets !== undefined
360
392
  ? input.appGadgets
361
393
  : gadgetCatalog !== undefined && input.appId !== undefined
362
- ? await gadgetCatalog.list(input.appId)
363
- : undefined;
394
+ ? await gadgetCatalog.list(input.appId)
395
+ : undefined;
364
396
 
365
- const userPrompt = injectContracts(
366
- promptWithVariance,
367
- input.contract,
368
- resolvedAppGadgets,
369
- );
397
+ const userPrompt = injectContracts(promptWithVariance, input.contract, resolvedAppGadgets);
370
398
 
371
399
  // `effort` (ggui#1059): a named level's dials override the
372
400
  // deployment's options for THIS generation; absent ⇒ untouched.
@@ -375,7 +403,9 @@ export function createUiGenerator(
375
403
  const effectiveMaxAttempts = dials?.maxAttempts ?? maxAttempts;
376
404
  const effectiveMaxEvalRounds = dials?.maxEvalRounds ?? maxEvalRounds;
377
405
  const effectiveEvaluation =
378
- dials !== undefined && evaluation !== undefined ? { ...evaluation, maxRounds: dials.maxEvalRounds } : evaluation;
406
+ dials !== undefined && evaluation !== undefined
407
+ ? { ...evaluation, maxRounds: dials.maxEvalRounds }
408
+ : evaluation;
379
409
  const effectiveVisualEvaluation =
380
410
  dials !== undefined && visualEvaluation !== undefined
381
411
  ? { ...visualEvaluation, passThreshold: dials.selfEvalPassThreshold }
@@ -389,21 +419,22 @@ export function createUiGenerator(
389
419
  ...(input.contract ? { contract: input.contract } : {}),
390
420
  originalPrompt: input.request.prompt,
391
421
  ...(effectiveMaxAttempts !== undefined ? { maxAttempts: effectiveMaxAttempts } : {}),
392
- ...(effectiveMaxEvalRounds !== undefined ? { maxEvalRounds: effectiveMaxEvalRounds } : {}),
422
+ ...(effectiveMaxEvalRounds !== undefined
423
+ ? { maxEvalRounds: effectiveMaxEvalRounds }
424
+ : {}),
393
425
  ...(effectiveEvaluation !== undefined ? { evaluation: effectiveEvaluation } : {}),
394
- ...(effectiveVisualEvaluation !== undefined ? { visualEvaluation: effectiveVisualEvaluation } : {}),
395
- ...(qualityConfig !== undefined ? { qualityConfig } : {}),
396
- ...(resolvedAppGadgets !== undefined
397
- ? { appGadgets: resolvedAppGadgets }
426
+ ...(effectiveVisualEvaluation !== undefined
427
+ ? { visualEvaluation: effectiveVisualEvaluation }
398
428
  : {}),
429
+ ...(qualityConfig !== undefined ? { qualityConfig } : {}),
430
+ ...(resolvedAppGadgets !== undefined ? { appGadgets: resolvedAppGadgets } : {}),
399
431
  // Forward the third-party wrapper `.d.ts` map the render
400
432
  // handler pre-fetched. Reaches both the code-gen prompt's
401
433
  // `Type:` lines and the coding-agent typecheck overlay via
402
434
  // `dispatchGeneration`.
403
- ...(input.gadgetTypes !== undefined
404
- ? { gadgetTypes: input.gadgetTypes }
405
- : {}),
435
+ ...(input.gadgetTypes !== undefined ? { gadgetTypes: input.gadgetTypes } : {}),
406
436
  enableRuntimeRender,
437
+ ...(runtimeRender !== undefined ? { runtimeRender } : {}),
407
438
  ...(routeOverride !== undefined ? { routeOverride } : {}),
408
439
  ...(onRetry !== undefined ? { onRetry } : {}),
409
440
  ...(designMode !== undefined ? { designMode } : {}),
@@ -417,6 +448,8 @@ export function createUiGenerator(
417
448
 
418
449
  onGenerated?.(result);
419
450
 
451
+ const runtimeProbe = metadataRuntimeProbe(result);
452
+ const evalMs = metadataEvalMs(result);
420
453
  const metadata: GenerationMetadata = {
421
454
  provider: input.llm.provider,
422
455
  generator: identity.slug,
@@ -437,6 +470,10 @@ export function createUiGenerator(
437
470
  ...(result.cacheCreationTokens !== undefined
438
471
  ? { cacheCreationTokens: result.cacheCreationTokens }
439
472
  : {}),
473
+ // ggui#1380 — the runtime probe's record and the eval rounds' wall,
474
+ // when the harness ran a round; absent stays absent.
475
+ ...(runtimeProbe !== undefined ? { runtimeProbe } : {}),
476
+ ...(evalMs !== undefined ? { evalMs } : {}),
440
477
  };
441
478
 
442
479
  return {
@@ -451,12 +488,12 @@ export function createUiGenerator(
451
488
  };
452
489
  } catch (err) {
453
490
  const message = err instanceof Error ? err.message : String(err);
454
- const details: JsonObject = { kind: 'harness-failed' };
455
- if (err instanceof Error && err.stack) details['stack'] = err.stack;
491
+ const details: JsonObject = { kind: "harness-failed" };
492
+ if (err instanceof Error && err.stack) details["stack"] = err.stack;
456
493
  return {
457
494
  ok: false,
458
495
  error: {
459
- code: 'PRODUCTION_FAILED',
496
+ code: "PRODUCTION_FAILED",
460
497
  message,
461
498
  details,
462
499
  },
@@ -488,11 +525,9 @@ function resolveIdentity(opts: CreateUiGeneratorOptions): {
488
525
  tier: GeneratorTier;
489
526
  model: ModelRef;
490
527
  } {
491
- const hasSlug = typeof opts.slug === 'string' && opts.slug.length > 0;
528
+ const hasSlug = typeof opts.slug === "string" && opts.slug.length > 0;
492
529
  if (hasSlug && opts.tier !== undefined) {
493
- throw new Error(
494
- 'createUiGenerator: pass either { slug } or { tier } — not both.',
495
- );
530
+ throw new Error("createUiGenerator: pass either { slug } or { tier } — not both.");
496
531
  }
497
532
  const model = opts.model ?? DEFAULT_MODEL;
498
533
  if (hasSlug) {
@@ -500,7 +535,7 @@ function resolveIdentity(opts: CreateUiGeneratorOptions): {
500
535
  const parsed = parseGeneratorSlug(slug);
501
536
  if (!parsed || !isGeneratorId(slug)) {
502
537
  throw new Error(
503
- `createUiGenerator: slug ${JSON.stringify(slug)} is not a valid ui-gen-<tier> identifier.`,
538
+ `createUiGenerator: slug ${JSON.stringify(slug)} is not a valid ui-gen-<tier> identifier.`
504
539
  );
505
540
  }
506
541
  return { slug, tier: parsed.tier, model };
@@ -512,23 +547,80 @@ function resolveIdentity(opts: CreateUiGeneratorOptions): {
512
547
  // so a slug it returns must parse. Defensive check kept to surface
513
548
  // any future drift between the two helpers.
514
549
  throw new Error(
515
- `createUiGenerator: formatted slug ${JSON.stringify(slug)} round-trips as invalid — formatGeneratorSlug/isValidGeneratorSlug drift.`,
550
+ `createUiGenerator: formatted slug ${JSON.stringify(slug)} round-trips as invalid — formatGeneratorSlug/isValidGeneratorSlug drift.`
516
551
  );
517
552
  }
518
553
  return { slug, tier, model };
519
554
  }
520
555
 
556
+ /**
557
+ * ggui#1380 — one probe's meta, projected onto the metadata's outcome ARM BY
558
+ * ARM: the outcome fields (`status`, `verdict`, `failChecks`, `elapsedMs`)
559
+ * are copied; the engine's own detail (`reason`, `renderMs`, `hostLoad`)
560
+ * stays on the harness result. No cast — each arm is rebuilt as a literal of
561
+ * the target arm, so a field the metadata does not carry cannot ride along
562
+ * and a field the arm requires cannot be dropped. Absent stays absent.
563
+ */
564
+ function metadataProbeOutcome(meta: RuntimeProbeMeta): GenerationRuntimeProbeOutcome {
565
+ const elapsed = {
566
+ ...(meta.elapsedMs !== undefined ? { elapsedMs: meta.elapsedMs } : {}),
567
+ ...(meta.queuedMs !== undefined ? { queuedMs: meta.queuedMs } : {}),
568
+ };
569
+ if (meta.status !== "ran") return { status: meta.status, ...elapsed };
570
+ if (meta.verdict === "pass") return { status: "ran", verdict: "pass", ...elapsed };
571
+ return { status: "ran", verdict: "fail", failChecks: meta.failChecks, ...elapsed };
572
+ }
573
+
574
+ /**
575
+ * ggui#1380 — the repair record, arm by arm: `compiled: false` is carried as
576
+ * is (no re-probe, so no after); `compiled: true` carries the re-probe's meta
577
+ * projected with the same function as the record's own outcome.
578
+ */
579
+ function metadataProbeRepair(repair: RuntimeProbeRepair): GenerationRuntimeProbeRepair {
580
+ return repair.compiled
581
+ ? { attempted: true, compiled: true, trigger: metadataProbeOutcome(repair.trigger) }
582
+ : { attempted: true, compiled: false };
583
+ }
584
+
585
+ /**
586
+ * ggui#1380 — the harness result's probe record, projected onto the metadata
587
+ * a host reads. The outcome fields are the LAST probe's (the re-probe's when
588
+ * the repair compiled, else the pre-repair probe's — exactly what the harness
589
+ * stamps on `evalResult.runtimeProbe`), and `repair` is present only when a
590
+ * repair turn was bought. Absent when no probe ran — never a default status.
591
+ */
592
+ function metadataRuntimeProbe(result: GenerationResult): GenerationRuntimeProbe | undefined {
593
+ const probe = result.evalResult?.runtimeProbe;
594
+ if (probe === undefined) return undefined;
595
+ const repair = result.evalResult?.runtimeProbeRepair;
596
+ return {
597
+ ...metadataProbeOutcome(probe),
598
+ ...(repair !== undefined ? { repair: metadataProbeRepair(repair) } : {}),
599
+ };
600
+ }
601
+
602
+ /**
603
+ * ggui#1380 — the eval rounds' wall-clock, present only when a round ran:
604
+ * a generation with no round has nothing to time, and the field stays
605
+ * absent rather than reporting a structural 0.
606
+ */
607
+ function metadataEvalMs(result: GenerationResult): number | undefined {
608
+ const breakdown = result.breakdown;
609
+ if (breakdown === undefined || breakdown.evalRounds === 0) return undefined;
610
+ return breakdown.evalMs;
611
+ }
612
+
521
613
  function mapLlmProviderToDispatchProvider(provider: LlmProvider): ProviderName {
522
614
  switch (provider) {
523
- case 'anthropic':
524
- case 'bedrock':
525
- return 'claude';
526
- case 'openai':
527
- return 'openai';
528
- case 'google':
529
- return 'google';
530
- case 'openrouter':
531
- return 'openrouter';
615
+ case "anthropic":
616
+ case "bedrock":
617
+ return "claude";
618
+ case "openai":
619
+ return "openai";
620
+ case "google":
621
+ return "google";
622
+ case "openrouter":
623
+ return "openrouter";
532
624
  }
533
625
  }
534
626
 
@@ -542,20 +634,18 @@ function mapLlmProviderToDispatchProvider(provider: LlmProvider): ProviderName {
542
634
  * yet, so both pass no shell and those generations stay byte-identical — named
543
635
  * here rather than guessed.
544
636
  */
545
- function harnessShellForRendering(rendering: RenderingContext | undefined): 'chat' | undefined {
637
+ function harnessShellForRendering(rendering: RenderingContext | undefined): "chat" | undefined {
546
638
  if (rendering === undefined) return undefined;
547
639
  switch (rendering.shell) {
548
- case 'chat':
549
- return 'chat';
550
- case 'fullscreen':
551
- case 'partial':
640
+ case "chat":
641
+ return "chat";
642
+ case "fullscreen":
643
+ case "partial":
552
644
  return undefined;
553
645
  }
554
646
  }
555
647
 
556
- function mapRendering(
557
- rendering: UiGenerateInput['rendering'],
558
- ): RenderingContext | undefined {
648
+ function mapRendering(rendering: UiGenerateInput["rendering"]): RenderingContext | undefined {
559
649
  if (!rendering) return undefined;
560
650
  return {
561
651
  device: rendering.device,
@@ -568,7 +658,7 @@ function failWithoutMetadata(
568
658
  input: UiGenerateInput,
569
659
  generatorSlug: GeneratorId,
570
660
  startedAt: number,
571
- error: GenerationError,
661
+ error: GenerationError
572
662
  ): UiGenerateResult {
573
663
  return {
574
664
  ok: false,
@@ -600,18 +690,18 @@ export function extractComponentCode(raw: string): string {
600
690
  const fences: Array<{ lang: string; body: string }> = [];
601
691
  let match: RegExpExecArray | null;
602
692
  while ((match = fencePattern.exec(raw)) !== null) {
603
- const lang = (match[1] ?? '').toLowerCase();
604
- const body = match[2] ?? '';
693
+ const lang = (match[1] ?? "").toLowerCase();
694
+ const body = match[2] ?? "";
605
695
  fences.push({ lang, body });
606
696
  }
607
697
 
608
698
  if (fences.length > 0) {
609
- const priority = ['tsx', 'jsx', 'typescript', 'ts', 'javascript', 'js', ''];
699
+ const priority = ["tsx", "jsx", "typescript", "ts", "javascript", "js", ""];
610
700
  for (const preferred of priority) {
611
701
  const hit = fences.find((f) => f.lang === preferred);
612
702
  if (hit) return hit.body.trim();
613
703
  }
614
- const firstBody = fences[0]?.body ?? '';
704
+ const firstBody = fences[0]?.body ?? "";
615
705
  return firstBody.trim();
616
706
  }
617
707
 
@@ -79,16 +79,41 @@ function runStateCoversPayload(input: AxisCheckInput): EvalIssue[] {
79
79
  return issues;
80
80
  }
81
81
 
82
+ /**
83
+ * Whether a `useState` initializer reads `props.<name>`: directly, or through a local bound from
84
+ * it (`const initial = normalize(props.initialProfile)` then `useState(initial.name)`, or a
85
+ * destructure `const { name } = props.initialProfile ?? {}` then `useState(name)`). The property
86
+ * this check guards is "form state is seeded from the prop", not a spelling: ggui#1261's Exp 011
87
+ * cell re-seeded correctly through a normalized local, was refused, and spent a turn restoring
88
+ * the literal form.
89
+ */
90
+ function seedsFromProp(src: string, name: string): boolean {
91
+ const propRef = `props\\??\\.${name}\\b`;
92
+ const initializerReads = (ref: string): boolean =>
93
+ new RegExp(`useState(?:<[^>]*>)?\\s*\\([\\s\\S]{0,400}?${ref}`).test(src);
94
+ if (initializerReads(propRef)) return true;
95
+ const locals = new Set<string>();
96
+ for (const m of src.matchAll(new RegExp(`(?:const|let|var)\\s+(\\w+)\\s*(?::[^=]{1,80})?=\\s*[^;]{0,300}?${propRef}`, "g"))) {
97
+ const id = m[1];
98
+ if (id !== undefined) locals.add(id);
99
+ }
100
+ for (const m of src.matchAll(new RegExp(`(?:const|let|var)\\s*\\{([^}]{1,400})\\}\\s*=\\s*[^;]{0,300}?${propRef}`, "g"))) {
101
+ for (const part of (m[1] ?? "").split(",")) {
102
+ const id = part.split(":").pop()?.split("=")[0]?.trim();
103
+ if (id !== undefined && /^\w+$/.test(id)) locals.add(id);
104
+ }
105
+ }
106
+ for (const id of locals) if (initializerReads(`\\b${id}\\b`)) return true;
107
+ return false;
108
+ }
109
+
82
110
  function runInitialValuesSeeded(input: AxisCheckInput): EvalIssue[] {
83
111
  if (input.compiledCode === null) return [];
84
112
  const src = input.sourceCode;
85
113
  const initialProps = getInitialValuePropNames(input.contract);
86
114
  const issues: EvalIssue[] = [];
87
115
  for (const name of initialProps) {
88
- const re = new RegExp(
89
- `useState(?:<[^>]*>)?\\s*\\([\\s\\S]{0,400}?props\\.${name}\\b`,
90
- );
91
- if (re.test(src)) continue;
116
+ if (seedsFromProp(src, name)) continue;
92
117
  issues.push(
93
118
  mkIssue(
94
119
  "state.payload.initial_values_seeded",