@ggui-ai/ui-gen 0.23.0 → 0.25.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 (123) hide show
  1. package/dist/adapter-Cg_rkpGq.d.ts +201 -0
  2. package/dist/adapters/generation-dispatch.d.ts +58 -15
  3. package/dist/adapters/generation-dispatch.js +1418 -180
  4. package/dist/adapters/generation-dispatch.js.map +1 -1
  5. package/dist/adapters/index.js +3 -1
  6. package/dist/adapters/index.js.map +1 -1
  7. package/dist/advanced/index.d.ts +12 -8
  8. package/dist/advanced/index.js +1467 -239
  9. package/dist/advanced/index.js.map +1 -1
  10. package/dist/{axes-CzLEMDeB.d.ts → axes-CKVCzR_x.d.ts} +1 -1
  11. package/dist/bank-CT4eEkta.d.ts +118 -0
  12. package/dist/blueprint-validator.js +153 -59
  13. package/dist/blueprint-validator.js.map +1 -1
  14. package/dist/boilerplate.js +24 -12
  15. package/dist/boilerplate.js.map +1 -1
  16. package/dist/check/index.d.ts +12 -4
  17. package/dist/check/index.js +94 -2
  18. package/dist/check/index.js.map +1 -1
  19. package/dist/classifier/index.d.ts +2 -2
  20. package/dist/compose.d.ts +2 -2
  21. package/dist/{create-ui-generator-D0kbWwln.d.ts → create-ui-generator-DFnnArrv.d.ts} +43 -11
  22. package/dist/{dispatch-wxaIMwZt.d.ts → dispatch-CStl9eAw.d.ts} +5 -3
  23. package/dist/evaluation/axis-checks/index.d.ts +6 -5
  24. package/dist/evaluation/axis-checks/index.js +90 -2
  25. package/dist/evaluation/axis-checks/index.js.map +1 -1
  26. package/dist/evaluation/axis-checks/registry.d.ts +4 -3
  27. package/dist/evaluation/axis-checks/registry.js +88 -1
  28. package/dist/evaluation/axis-checks/registry.js.map +1 -1
  29. package/dist/evaluation/index.d.ts +301 -23
  30. package/dist/evaluation/index.js +746 -30
  31. package/dist/evaluation/index.js.map +1 -1
  32. package/dist/evaluation/types.d.ts +9 -1
  33. package/dist/evaluation/types.js.map +1 -1
  34. package/dist/fragments/index.d.ts +3 -3
  35. package/dist/harness/check/runtime-render/index.d.ts +30 -11
  36. package/dist/harness/check/runtime-render/index.js +203 -60
  37. package/dist/harness/check/runtime-render/index.js.map +1 -1
  38. package/dist/harness/check/runtime-render/render-check-worker.d.ts +8 -1
  39. package/dist/harness/check/runtime-render/render-check-worker.js +72 -32
  40. package/dist/harness/check/runtime-render/render-check-worker.js.map +1 -1
  41. package/dist/harness/index.d.ts +5 -4
  42. package/dist/harness/index.js +33 -15
  43. package/dist/harness/index.js.map +1 -1
  44. package/dist/harness/prompts.js.map +1 -1
  45. package/dist/harness/result-types.d.ts +4 -3
  46. package/dist/harness/runtime.d.ts +11 -4
  47. package/dist/harness/runtime.js +26 -14
  48. package/dist/harness/runtime.js.map +1 -1
  49. package/dist/harness/types-public.d.ts +13 -4
  50. package/dist/harness/types-public.js +33 -15
  51. package/dist/harness/types-public.js.map +1 -1
  52. package/dist/harness/validator-trace-sink.d.ts +4 -3
  53. package/dist/hash.d.ts +1 -1
  54. package/dist/index.d.ts +59 -8
  55. package/dist/index.js +1476 -241
  56. package/dist/index.js.map +1 -1
  57. package/dist/{llm-router-DwwUK1bz.d.ts → llm-router-nfO7wPec.d.ts} +3 -1
  58. package/dist/patch.d.ts +4 -3
  59. package/dist/policy.d.ts +8 -5
  60. package/dist/policy.js.map +1 -1
  61. package/dist/tools/get-primitives-ts.d.ts +1 -1
  62. package/dist/tools/get-primitives-ts.js +1 -1
  63. package/dist/tools/get-primitives-ts.js.map +1 -1
  64. package/dist/tools/render-check-worker.d.ts +7 -1
  65. package/dist/tools/render-check-worker.js +55 -23
  66. package/dist/tools/render-check-worker.js.map +1 -1
  67. package/dist/{types-BOvHNG7K.d.ts → types-Dh1s4K9j.d.ts} +1 -1
  68. package/dist/{types-public-Ow-tNtFE.d.ts → types-public-BJTLpzWn.d.ts} +208 -15
  69. package/dist/validation/index.d.ts +1 -1
  70. package/dist/validation/index.js +1 -1
  71. package/dist/validation/index.js.map +1 -1
  72. package/dist/workflows.d.ts +4 -3
  73. package/package.json +12 -12
  74. package/src/adapters/generation-dispatch.ts +173 -98
  75. package/src/adapters/tools.ts +2 -1
  76. package/src/boilerplate/free-design-prompt.ts +1 -1
  77. package/src/boilerplate/generate.ts +26 -1
  78. package/src/boilerplate/hard-sections.ts +13 -13
  79. package/src/boilerplate/system-prompt.ts +1 -1
  80. package/src/check/index.ts +1 -0
  81. package/src/check/run-tier0.ts +80 -1
  82. package/src/check/sample-props.ts +63 -0
  83. package/src/coding-agent/tools.ts +4 -1
  84. package/src/create-harness.ts +3 -3
  85. package/src/create-ui-generator.ts +204 -95
  86. package/src/evaluation/__fixtures__/png.ts +100 -0
  87. package/src/evaluation/axis-checks/checks/state-merge.ts +1 -1
  88. package/src/evaluation/axis-checks/checks/universal.ts +111 -0
  89. package/src/evaluation/axis-checks/dispatch.ts +4 -1
  90. package/src/evaluation/criteria/bank.ts +85 -0
  91. package/src/evaluation/criteria/context.ts +52 -0
  92. package/src/evaluation/criteria/resolve.ts +159 -0
  93. package/src/evaluation/criteria/select.ts +79 -0
  94. package/src/evaluation/index.ts +23 -0
  95. package/src/evaluation/ink-extent.ts +200 -0
  96. package/src/evaluation/theme-css.ts +12 -5
  97. package/src/evaluation/types-public.ts +200 -11
  98. package/src/evaluation/types.ts +9 -0
  99. package/src/evaluation/visual-evaluator.ts +636 -30
  100. package/src/harness/check/runtime-render/adapter.ts +221 -75
  101. package/src/harness/check/runtime-render/index.ts +75 -2
  102. package/src/harness/check/runtime-render/render-check-host.ts +82 -19
  103. package/src/harness/check/runtime-render/render-check-worker.ts +83 -29
  104. package/src/harness/check/runtime-render/render-check.ts +154 -117
  105. package/src/harness/coding/generate-task-runner.ts +60 -4
  106. package/src/harness/coding/init-session.ts +13 -0
  107. package/src/harness/coding/run-coding-turn.ts +7 -3
  108. package/src/harness/coding/run-eval-round.ts +257 -15
  109. package/src/harness/policy.ts +11 -0
  110. package/src/harness/primitive-slice.ts +104 -0
  111. package/src/harness/runtime.ts +5 -0
  112. package/src/harness/types-public.ts +9 -1
  113. package/src/harness-tool-names.ts +38 -0
  114. package/src/index.ts +16 -0
  115. package/src/policy.ts +7 -4
  116. package/src/run-check.ts +2 -0
  117. package/src/tools/get-primitives-ts.ts +1 -1
  118. package/src/tools/get-wire.ts +1 -1
  119. package/src/tools/render-check-worker.ts +74 -23
  120. package/src/tools/render-check.ts +0 -60
  121. package/src/tools.ts +11 -1
  122. package/src/validation/primitives.ts +1 -1
  123. package/dist/render-check-Blwj8gB6.d.ts +0 -105
@@ -469,7 +469,7 @@ The design-system primitives are accessible by construction — they emit their
469
469
  3. **Icons are decorative by default** — \`<Icon name="check" />\` is hidden from screen readers, which is correct for an icon sitting next to text. Add \`aria-label\` ONLY for a standalone, meaning-bearing icon with no adjacent text. Icon-only \`Button\`s still need \`aria-label\` on the **Button** itself.
470
470
  4. **Live & streaming data** — wrap any region whose content updates on its own (a \`useStream\` \`.latest\` value, a live clock, an "N new" counter, a flashing price) in an element with \`aria-live="polite"\` so screen readers announce the change.
471
471
  5. **Headings nest** — one \`<Heading level={1}>\` per screen, \`level={2}\` for sections, \`level={3}\` for subsections. Never skip or invert levels.
472
- 6. **Buttons** — descriptive text content; icon-only buttons need \`aria-label\`. Announce busy state: \`<Button disabled={isLoading} aria-busy={isLoading}>{isLoading ? 'Submitting…' : 'Submit'}</Button>\`.
472
+ 6. **Buttons** — descriptive text content; icon-only buttons need \`aria-label\`. Announce busy state. EVERY control that dispatches an action — a Button, a Select whose change dispatches, an Input that sends on Enter — stays pending from that action's \`useActionPending\` until the agent's answer repaints the card: with \`const sendPending = useActionPending('send');\`, write \`<Button disabled={sendPending} aria-busy={sendPending} onClick={() => send({ text })}>{sendPending ? 'Sending…' : 'Send'}</Button>\`, and give a dispatching Select or Input the same \`disabled={sendPending} aria-busy={sendPending}\`. Only a control that dispatches the action carries its \`aria-busy\` and a working label. While it is pending, other controls of the SAME record may be \`disabled\` — nothing more; controls of other records, creation controls and the action's own cancel stay enabled. Busy state the card owns (a local \`isLoading\`) is announced the same way.
473
473
  7. **Stateful controls announce their state.** Toggleable / selectable / expandable things use the primitive that carries the semantics (\`Checkbox\`, \`Toggle\`, \`Tabs\`, \`Accordion\`) — never a styled \`Box\`/\`Icon\` that only *looks* checked. When you hand-roll one with \`as={Clickable}\`, \`Clickable\` adds button + keyboard semantics but NOT state — add it yourself: \`role="checkbox"\` + \`aria-checked={done}\` on a toggleable row, \`aria-pressed\` on a toggle button, \`aria-selected\` on the chosen item, \`aria-expanded\` on an open/close affordance. Styling (strikethrough, color, a check icon) must never be the only carrier of state.
474
474
 
475
475
  ## Design System Usage (CRITICAL)
@@ -51,3 +51,4 @@ export {
51
51
 
52
52
  // runTier0 orchestrator
53
53
  export { runTier0, runTier0Checks } from "./run-tier0.js";
54
+ export { generateSampleProps } from "./sample-props.js";
@@ -295,6 +295,85 @@ function detectCertainDoubleWiredActions(
295
295
  * fallback ban it paints nothing.
296
296
  */
297
297
  const MANIFEST_TOKENS: ReadonlySet<string> = new Set(consumedTokenManifest);
298
+
299
+ /**
300
+ * Role names retired by ggui#987 that generated code still invents, each
301
+ * with the manifest name that replaced it (ggui#1325). The self-check names
302
+ * the successor, so the model does not spend a turn guessing another name.
303
+ */
304
+ const RETIRED_TOKEN_SUCCESSORS: ReadonlyMap<string, string> = new Map([
305
+ ['--ggui-color-surface', '--ggui-color-container'],
306
+ ['--ggui-color-onSurface', '--ggui-color-onContainer'],
307
+ ['--ggui-color-surfaceVariant', '--ggui-color-sunken'],
308
+ ['--ggui-color-onSurfaceVariant', '--ggui-color-onSunken'],
309
+ ]);
310
+
311
+ /**
312
+ * The manifest names nearest an invented one (ggui#1325): a retired role's
313
+ * successor when there is one; otherwise the names sharing the invented
314
+ * name's longest family prefix at a segment boundary
315
+ * (`--ggui-motion-duration-normal` → fast, base, slow); otherwise the names
316
+ * sharing any of its segments of four letters or more
317
+ * (`--ggui-duration-slow` → the motion durations). Empty when nothing is
318
+ * near, so the hint falls back to the manifest's general shape.
319
+ */
320
+ export function suggestManifestTokens(name: string): readonly string[] {
321
+ const successor = RETIRED_TOKEN_SUCCESSORS.get(name);
322
+ if (successor !== undefined) return [successor];
323
+ // Family prefixes at the name's own segment boundaries, longest first: for
324
+ // `--ggui-motion-duration-normal` that is `--ggui-motion-duration-`, then
325
+ // `--ggui-motion-`; an empty-suffix name such as `--ggui-color-` (the model
326
+ // stopped mid-name) is its own longest prefix, so it gets the colour family.
327
+ const segments = name.split('-').filter((segment) => segment.length > 0);
328
+ const own = name.endsWith('-') ? segments.length : segments.length - 1;
329
+ for (let n = own; n >= 2; n--) {
330
+ const prefix = `--${segments.slice(0, n).join('-')}-`;
331
+ const family = consumedTokenManifest.filter((token) => token.startsWith(prefix));
332
+ if (family.length > 0) return wordHitsFirst(name, rolesFirst(family));
333
+ }
334
+ const words = new Set(
335
+ segments
336
+ .slice(1)
337
+ .filter((segment) => segment.length >= 4)
338
+ .map((segment) => segment.toLowerCase()),
339
+ );
340
+ return rolesFirst(
341
+ consumedTokenManifest.filter((token) =>
342
+ token.split('-').some((segment) => words.has(segment.toLowerCase())),
343
+ ),
344
+ );
345
+ }
346
+
347
+ /**
348
+ * Names that carry the invented name's last word lead the list: `--ggui-color-inverse` names `inverseLink` and
349
+ * `inverseOutline` before the rest of the colour family, since those are what the model was reaching for. The word is
350
+ * the last segment when it has four letters or more; shorter or empty suffixes leave the order alone.
351
+ */
352
+ function wordHitsFirst(name: string, family: readonly string[]): readonly string[] {
353
+ const word = name.slice(name.lastIndexOf('-') + 1).toLowerCase();
354
+ if (word.length < 4) return family;
355
+ const hit = (token: string): boolean => token.slice(token.lastIndexOf('-') + 1).toLowerCase().includes(word);
356
+ return [...family.filter(hit), ...family.filter((token) => !hit(token))];
357
+ }
358
+
359
+ /** Role names (a non-numeric last segment) before ramp steps, so the taught vocabulary leads a long family. */
360
+ function rolesFirst(tokens: readonly string[]): readonly string[] {
361
+ const isRole = (token: string): boolean => !/^\d+$/.test(token.slice(token.lastIndexOf('-') + 1));
362
+ return [...tokens.filter(isRole), ...tokens.filter((token) => !isRole(token))];
363
+ }
364
+
365
+ /** The sentence that names the real token first (ggui#1325); empty when nothing in the manifest is near. */
366
+ function offManifestHint(name: string): string {
367
+ const successor = RETIRED_TOKEN_SUCCESSORS.get(name);
368
+ if (successor !== undefined) {
369
+ return `"${name}" was retired (ggui#987); the current name is var(${successor}). `;
370
+ }
371
+ const near = suggestManifestTokens(name);
372
+ if (near.length === 0) return '';
373
+ const shown = near.slice(0, 8).map((token) => `var(${token})`).join(', ');
374
+ const more = near.length > 8 ? ` (and ${near.length - 8} more in that family)` : '';
375
+ return `Nearest names in the manifest: ${shown}${more}. `;
376
+ }
298
377
  const TOKEN_REF_RE = /var\(\s*(--ggui-[a-zA-Z0-9-]+)/g;
299
378
 
300
379
  /** Map wire kind to the matching `@ggui-ai/wire` hook name. */
@@ -790,7 +869,7 @@ export async function runTier0Checks(
790
869
  subcategory: 'off-manifest-token',
791
870
  severity: 'critical',
792
871
  description: `Token "${name}" is not in the design system's token manifest — no theme defines it, so it renders unset.`,
793
- fix: `Choose a token that exists: the closed vocabulary is the manifest the design-system docs list (colors: var(--ggui-color-primary-500), var(--ggui-color-onContainer), …; spacing: var(--ggui-spacing-4); type: var(--ggui-font-size-sm)). Never invent a name and never add a literal fallback.`,
872
+ fix: `${offManifestHint(name)}Choose a token that exists: the closed vocabulary is the manifest the design-system docs list (colors: var(--ggui-color-primary-500), var(--ggui-color-onContainer), …; spacing: var(--ggui-spacing-4); type: var(--ggui-font-size-sm)). Never invent a name and never add a literal fallback.`,
794
873
  line: lineNum,
795
874
  });
796
875
  }
@@ -0,0 +1,63 @@
1
+ // Sample props from a PropsSpec — `example` first, then `default`, then a type-driven fill from the JSON
2
+ // Schema. Pure: protocol types only, no sandbox, no node modules, so `@ggui-ai/ui-gen/check` can export it and a
3
+ // caller outside the harness (an experiment's judge input, ggui#1386) synthesizes the SAME props the coding
4
+ // agent's own render check falls back to. Moved verbatim out of `tools/render-check.ts` (2026-09-26).
5
+ import type { JsonObject, JsonSchema, JsonValue, PropsSpec } from '@ggui-ai/protocol';
6
+
7
+ /**
8
+ * Generate sample props from a PropsSpec contract.
9
+ * Uses `example` values from each PropEntry when available,
10
+ * otherwise synthesizes plausible defaults from the JSON Schema.
11
+ */
12
+ export function generateSampleProps(spec: PropsSpec): JsonObject {
13
+ const props: JsonObject = {};
14
+
15
+ for (const [name, entry] of Object.entries(spec.properties)) {
16
+ // Prefer explicit example value
17
+ if (entry.example !== undefined) {
18
+ props[name] = entry.example;
19
+ continue;
20
+ }
21
+
22
+ // Prefer explicit default value
23
+ if (entry.default !== undefined) {
24
+ props[name] = entry.default;
25
+ continue;
26
+ }
27
+
28
+ // Synthesize from JSON Schema
29
+ props[name] = synthesizeFromSchema(entry.schema);
30
+ }
31
+
32
+ return props;
33
+ }
34
+
35
+ function synthesizeFromSchema(schema: JsonSchema): JsonValue | undefined {
36
+ // Draft-07 type arrays (`['string','null']`) synthesize from the
37
+ // first non-null member — the canonical nullability form the
38
+ // enforced-props-schema emission produces.
39
+ const declared = Array.isArray(schema.type)
40
+ ? schema.type.find((t) => t !== 'null')
41
+ : schema.type;
42
+ switch (declared) {
43
+ case 'string': return 'sample';
44
+ case 'number':
45
+ case 'integer': return 0;
46
+ case 'boolean': return false;
47
+ case 'null': return null;
48
+ case 'array': {
49
+ if (schema.items) return [synthesizeFromSchema(schema.items) ?? null];
50
+ return [];
51
+ }
52
+ case 'object': {
53
+ const obj: JsonObject = {};
54
+ if (schema.properties) {
55
+ for (const [k, v] of Object.entries(schema.properties)) {
56
+ obj[k] = synthesizeFromSchema(v);
57
+ }
58
+ }
59
+ return obj;
60
+ }
61
+ default: return undefined;
62
+ }
63
+ }
@@ -17,7 +17,7 @@ import type { AxisCheck, AxisCheckInput } from '../evaluation/types-public.js';
17
17
  import type { Classification } from '../classifier/axes.js';
18
18
  import type { CanvasClass } from '../design-mode.js';
19
19
  import { PRIMITIVES_DOCUMENTATION } from '../validation/index.js';
20
- import type { DataContract } from '@ggui-ai/protocol';
20
+ import type { DataContract, JsonObject } from '@ggui-ai/protocol';
21
21
  import type {
22
22
  ToolResult,
23
23
  CommitMetadata,
@@ -64,6 +64,8 @@ export interface AutoCommitAxisChecks {
64
64
  readonly originalPrompt: string;
65
65
  /** The canvas the harness was built for (ggui#1117); absent = no rendering context, never a default. */
66
66
  readonly canvas?: CanvasClass;
67
+ /** The props the probe and judge render with (ggui#1285); absent = the fixture-echo check stands down. */
68
+ readonly fixtureProps?: JsonObject;
67
69
  }
68
70
 
69
71
  async function autoCommit(
@@ -161,6 +163,7 @@ async function autoCommit(
161
163
  classification: axis.classification,
162
164
  designMode,
163
165
  ...(axis.canvas !== undefined ? { canvas: axis.canvas } : {}),
166
+ ...(axis.fixtureProps !== undefined ? { fixtureProps: axis.fixtureProps } : {}),
164
167
  }
165
168
  : undefined;
166
169
  if (axisFacts !== undefined && !buildSuccess) traceAxisChecksSkipped(axisFacts, 'build failed — no check ran');
@@ -16,7 +16,7 @@ import { hasProfile } from "./boilerplate/styling-profile.js";
16
16
  import type { CacheTier } from "./fragments/index.js";
17
17
  import { compose } from "./compose.js";
18
18
  import { buildSystemPrompt, generateBoilerplate } from "./boilerplate.js";
19
- import { APPLY_CHANGES_TOOL, APPLY_CHANGES_TOOL_SCOPED } from "./tools.js";
19
+ import { HARNESS_CODING_TOOLS, HARNESS_SCOPED_TOOLS } from "./harness-tool-names.js";
20
20
  import { defaultApplyPatch } from "./patch.js";
21
21
  import { computeHarnessId, computeHarnessName, hashClassification } from "./hash.js";
22
22
  import { pickWorkflow } from "./workflows.js";
@@ -128,8 +128,8 @@ export function createHarness(input: CreateHarnessInput): Harness {
128
128
  const baseWhat: WhatLeg = {
129
129
  boilerplate,
130
130
  fragments: whatFragments,
131
- codingTools: [APPLY_CHANGES_TOOL],
132
- scopedTools: [APPLY_CHANGES_TOOL_SCOPED],
131
+ codingTools: [...HARNESS_CODING_TOOLS],
132
+ scopedTools: [...HARNESS_SCOPED_TOOLS],
133
133
  applyPatch: defaultPatchFn,
134
134
  // Registered gadget catalog — drives the system prompt's gadget
135
135
  // table + the boilerplate's direct-import emission. Omitted when no