@ggui-ai/ui-gen 0.15.0 → 0.17.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/adapters/generation-dispatch.d.ts +24 -4
  2. package/dist/adapters/generation-dispatch.js +5188 -3920
  3. package/dist/adapters/generation-dispatch.js.map +1 -1
  4. package/dist/adapters/index.js +33 -26
  5. package/dist/adapters/index.js.map +1 -1
  6. package/dist/advanced/index.d.ts +22 -13
  7. package/dist/advanced/index.js +4889 -3561
  8. package/dist/advanced/index.js.map +1 -1
  9. package/dist/blueprint-validator.js +8 -12
  10. package/dist/blueprint-validator.js.map +1 -1
  11. package/dist/boilerplate.d.ts +19 -1
  12. package/dist/boilerplate.js +671 -254
  13. package/dist/boilerplate.js.map +1 -1
  14. package/dist/check/index.d.ts +17 -3
  15. package/dist/check/index.js +34 -25
  16. package/dist/check/index.js.map +1 -1
  17. package/dist/compose.js +1 -1
  18. package/dist/compose.js.map +1 -1
  19. package/dist/create-ui-generator-C8Ki9Wz1.d.ts +189 -0
  20. package/dist/design-mode-DLufhrNp.d.ts +61 -0
  21. package/dist/{dispatch-BuZfTh9r.d.ts → dispatch-D2htzi9h.d.ts} +4 -1
  22. package/dist/evaluation/axis-checks/index.d.ts +4 -3
  23. package/dist/evaluation/axis-checks/index.js +300 -14
  24. package/dist/evaluation/axis-checks/index.js.map +1 -1
  25. package/dist/evaluation/axis-checks/registry.d.ts +2 -1
  26. package/dist/evaluation/axis-checks/registry.js +253 -1
  27. package/dist/evaluation/axis-checks/registry.js.map +1 -1
  28. package/dist/evaluation/index.d.ts +297 -5
  29. package/dist/evaluation/index.js +4515 -120
  30. package/dist/evaluation/index.js.map +1 -1
  31. package/dist/fragments/index.js +1 -1
  32. package/dist/fragments/index.js.map +1 -1
  33. package/dist/{generate-BTfvELgl.d.ts → generate-9IeNT4ZY.d.ts} +10 -1
  34. package/dist/harness/check/runtime-render/index.d.ts +3 -1
  35. package/dist/harness/check/runtime-render/index.js +8 -12
  36. package/dist/harness/check/runtime-render/index.js.map +1 -1
  37. package/dist/harness/index.d.ts +3 -1
  38. package/dist/harness/index.js +913 -441
  39. package/dist/harness/index.js.map +1 -1
  40. package/dist/harness/llm-trace-sink.d.ts +3 -3
  41. package/dist/harness/llm-trace-sink.js.map +1 -1
  42. package/dist/harness/prompts.d.ts +2 -1
  43. package/dist/harness/prompts.js +6 -3
  44. package/dist/harness/prompts.js.map +1 -1
  45. package/dist/harness/result-types.d.ts +17 -5
  46. package/dist/harness/result-types.js.map +1 -1
  47. package/dist/harness/runtime.d.ts +28 -3
  48. package/dist/harness/runtime.js +702 -274
  49. package/dist/harness/runtime.js.map +1 -1
  50. package/dist/harness/types-public.d.ts +39 -1
  51. package/dist/harness/types-public.js +913 -441
  52. package/dist/harness/types-public.js.map +1 -1
  53. package/dist/harness/validator-trace-sink.d.ts +2 -1
  54. package/dist/index.d.ts +32 -6
  55. package/dist/index.js +4899 -3560
  56. package/dist/index.js.map +1 -1
  57. package/dist/patch.d.ts +3 -1
  58. package/dist/{prompts-BVhtH3M1.d.ts → prompts-YU9piOgs.d.ts} +21 -45
  59. package/dist/providers/index.d.ts +2 -2
  60. package/dist/providers/index.js.map +1 -1
  61. package/dist/styling-profile-CxfvV48g.d.ts +35 -0
  62. package/dist/tools/get-primitives-ts.d.ts +1 -1
  63. package/dist/tools/get-primitives-ts.js +1 -1
  64. package/dist/tools/get-primitives-ts.js.map +1 -1
  65. package/dist/{types-public-CkNWEov9.d.ts → types-public-DVSssdc-.d.ts} +85 -5
  66. package/dist/validation/index.d.ts +1 -1
  67. package/dist/validation/index.js +4 -4
  68. package/dist/validation/index.js.map +1 -1
  69. package/dist/workflows.d.ts +3 -1
  70. package/package.json +12 -12
  71. package/src/adapters/generation-dispatch.ts +62 -4
  72. package/src/adapters/provider-router.ts +7 -6
  73. package/src/adapters/tools.ts +5 -5
  74. package/src/advanced/generator.ts +11 -10
  75. package/src/advanced/index.ts +5 -4
  76. package/src/boilerplate/free-design-prompt.ts +336 -0
  77. package/src/boilerplate/generate.ts +10 -1
  78. package/src/boilerplate/hard-sections.ts +257 -0
  79. package/src/boilerplate/render.ts +32 -8
  80. package/src/boilerplate/styling-profile.ts +166 -0
  81. package/src/boilerplate/system-prompt.ts +101 -237
  82. package/src/boilerplate/templates/base-free.tsx.tmpl +24 -0
  83. package/src/check/run-tier0.ts +82 -20
  84. package/src/coding-agent/tools.ts +8 -2
  85. package/src/contract-context.ts +43 -14
  86. package/src/create-harness.ts +15 -0
  87. package/src/create-ui-generator.ts +112 -27
  88. package/src/design-mode.ts +192 -0
  89. package/src/design-system-docs.ts +24 -17
  90. package/src/effort.ts +48 -0
  91. package/src/evaluation/axis-checks/checks/grid.ts +148 -0
  92. package/src/evaluation/axis-checks/checks/universal.ts +244 -0
  93. package/src/evaluation/axis-checks/dispatch.ts +82 -13
  94. package/src/evaluation/axis-checks/extras.ts +5 -0
  95. package/src/evaluation/axis-checks/helpers.ts +12 -0
  96. package/src/evaluation/axis-checks/registry.ts +2 -0
  97. package/src/evaluation/cost-tracker.ts +2 -3
  98. package/src/evaluation/design-identity.ts +96 -0
  99. package/src/evaluation/index.ts +63 -2
  100. package/src/evaluation/llm-evaluator.ts +89 -21
  101. package/src/evaluation/theme-css.ts +14 -0
  102. package/src/evaluation/types-public.ts +137 -8
  103. package/src/evaluation/visual-evaluator.ts +759 -54
  104. package/src/fragments/render.ts +1 -1
  105. package/src/harness/check/runtime-render/prepare-mockup.ts +18 -11
  106. package/src/harness/coding/dupe-break.ts +0 -0
  107. package/src/harness/coding/init-session.ts +2 -0
  108. package/src/harness/coding/run-coding-turn.ts +9 -14
  109. package/src/harness/coding/run-eval-round.ts +46 -5
  110. package/src/harness/llm-trace-sink.ts +3 -3
  111. package/src/harness/result-types.ts +15 -3
  112. package/src/harness/runtime.ts +28 -0
  113. package/src/harness/types-public.ts +38 -0
  114. package/src/index.ts +28 -0
  115. package/src/internal/extract-call-signatures.ts +2 -2
  116. package/src/pin-fixtures.ts +98 -0
  117. package/src/providers/bedrock.ts +4 -4
  118. package/src/run-check.ts +11 -10
  119. package/src/tools/get-primitives-ts.ts +1 -1
  120. package/src/tools.ts +2 -1
  121. package/src/validation/allowed-imports.ts +8 -2
  122. package/src/validation/primitives.ts +1 -1
  123. package/dist/create-ui-generator-D6TKuv9V.d.ts +0 -106
@@ -14,6 +14,8 @@
14
14
  // string — the skeleton still renders cleanly for OSS callers who don't
15
15
  // pass doc blocks.
16
16
 
17
+ import type { GenerationProfileInput } from "./styling-profile.js";
18
+ import { buildStylingProfileSection, withStylingProfile } from "./styling-profile.js";
17
19
  import { resolveAppGadgets } from "@ggui-ai/protocol";
18
20
  import type {
19
21
  GadgetDescriptor,
@@ -22,6 +24,27 @@ import type {
22
24
  GadgetComponentExport,
23
25
  } from "@ggui-ai/protocol";
24
26
  import { buildCodingCriteriaSummary } from "../evaluation/types-public.js";
27
+ import {
28
+ DEFAULT_DESIGN_MODE,
29
+ canvasForRendering,
30
+ type DesignMode,
31
+ type CanvasClass,
32
+ } from "../design-mode.js";
33
+ import { buildFreeDesignPrompt } from "./free-design-prompt.js";
34
+ import {
35
+ ANTI_PATTERNS,
36
+ COMPONENT_STRUCTURE,
37
+ CONTEXT_SPEC_SECTION,
38
+ CONTRACT_SURFACE,
39
+ CROSS_REFERENCE_RULES,
40
+ DATA_PARAMETERIZATION,
41
+ DEFENSIVE_CODING,
42
+ FRAME_SIZING,
43
+ GESTURE_ROUTING,
44
+ INTERACTIVE_TRAITS,
45
+ PROTOCOL_NOTES,
46
+ renderGadgetCatalogSection,
47
+ } from "./hard-sections.js";
25
48
  import {
26
49
  extractCallSignaturesFromDts,
27
50
  extractComponentPropsFromDts,
@@ -92,11 +115,28 @@ export interface SystemPromptInputs {
92
115
  * callers (the section stays byte-identical).
93
116
  */
94
117
  gadgetTypes?: Readonly<Record<string, string>>;
118
+ /**
119
+ * Which triad the prompt teaches. `constrained` (default) is today's
120
+ * design-system prompt, byte-identical to the pre-`designMode`
121
+ * output; `free` relaxes only the design vocabulary — see
122
+ * `./free-design-prompt.ts` and `../design-mode.ts`.
123
+ */
124
+ designMode?: DesignMode;
125
+ /**
126
+ * Rendering canvas class — `free` mode only. Rendered as an explicit
127
+ * width-range sentence so the agent is TOLD its canvas. When omitted
128
+ * it is derived from `shellType` × `screen`. Ignored in `constrained`
129
+ * mode (that prompt keeps its shell/screen descriptors verbatim).
130
+ */
131
+ canvas?: CanvasClass;
132
+ /** The app's generation profile (#991) — one bounded section, both arms; absent = byte-identical. */
133
+ profile?: GenerationProfileInput;
95
134
  }
96
135
 
97
136
  const SHELL_DESCRIPTIONS: Record<string, string> = {
98
137
  chat: "inline component inside ChatShell message bubble (~400px wide, compact)",
99
- fullscreen: "full viewport, responsive layout",
138
+ fullscreen:
139
+ "the full host viewport, responsive layout — the frame stretches your root to its height; size to content, never to the viewport",
100
140
  spatial: "floating AR/VR panel (~600px, touch-friendly)",
101
141
  };
102
142
 
@@ -283,7 +323,9 @@ export function buildSystemPrompt(inputs: SystemPromptInputs): string {
283
323
  const shellDesc = SHELL_DESCRIPTIONS[shell] ?? SHELL_DESCRIPTIONS.fullscreen;
284
324
  const screenDesc = SCREEN_DESCRIPTIONS[scr] ?? SCREEN_DESCRIPTIONS.universal;
285
325
 
286
- const criteriaBlock = inputs.criteriaBlock ?? buildCodingCriteriaSummary();
326
+ const designMode = inputs.designMode ?? DEFAULT_DESIGN_MODE;
327
+ const criteriaBlock =
328
+ inputs.criteriaBlock ?? buildCodingCriteriaSummary(designMode);
287
329
  const pitfallsBlock = inputs.pitfallsBlock ?? "";
288
330
  const designSystemDocs = inputs.designSystemDocs ?? "";
289
331
  const primitivesDoc = inputs.primitivesDoc ?? "";
@@ -299,11 +341,26 @@ export function buildSystemPrompt(inputs: SystemPromptInputs): string {
299
341
  inputs.gadgetTypes,
300
342
  );
301
343
 
344
+ const profileSection = buildStylingProfileSection(inputs.profile);
345
+
302
346
  const axisSection =
303
347
  inputs.axisDelta && inputs.axisDelta.trim().length > 0
304
348
  ? `\n## Shape Guidance\n${inputs.axisDelta}\n`
305
349
  : "";
306
350
 
351
+ if (designMode === "free") {
352
+ return buildFreeDesignPrompt({
353
+ userRequest: inputs.userRequest,
354
+ canvas: inputs.canvas ?? canvasForRendering(inputs.shellType, inputs.screen),
355
+ axisSection,
356
+ criteriaBlock,
357
+ wireDoc,
358
+ primitivesDoc,
359
+ gadgetsSection,
360
+ profileSection,
361
+ });
362
+ }
363
+
307
364
  return `You are ggui's UI builder. You receive a typed boilerplate and fill it in using apply_changes.
308
365
 
309
366
  ## Your Task
@@ -320,179 +377,26 @@ ${inputs.userRequest}
320
377
 
321
378
  ${criteriaBlock}
322
379
  ${axisSection}
323
- ## Protocol Notes
324
- The boilerplate pre-declares every wire hook the contract requires (\`useAction\`, \`useStream\`, \`useGguiContext\`, plus capability hooks from \`@ggui-ai/gadgets\` when the contract declares \`clientCapabilities\`). Three rules:
325
- 1. **Do NOT delete any pre-declared hook.** \`self_check\` fails with \`wire_preservation:<kind>:<name>\` if you remove one.
326
- 2. **Consume every hook binding** somewhere in the component — in JSX, a callback, or an effect. Unused bindings fail lint with \`no-unused-vars\`.
327
- 3. **Do NOT invent new wire calls.** Every \`useAction('X')\`, \`useStream('X')\`, \`useGguiContext('X')\` etc. MUST correspond to a declared entry on the contract. Calling one that isn't declared fails \`self_check\` with \`wire_undeclared:<kind>:<name>\` because the runtime has no Context/registration for it and would throw at first paint. If you need a new wire surface, that's a contract authoring step the agent owns — your job is to honor what's declared.
328
-
329
- Renaming a binding is fine — the wiring is the string-literal argument, not the identifier.
330
-
331
- ## Contract surface — four specs + two catalogs
332
-
333
- A \`DataContract\` declares everything a GguiSession exchanges with the outside world. **Four typed specs** for the four data-flow directions, **two reference catalogs** for tool / hook lookups:
334
-
335
- | Surface | Direction | Role |
336
- | -------------------- | -------------------------- | -------------------------------------------------------------------- |
337
- | \`propsSpec\` | server → UI (one-shot) | Initial render values delivered once at \`ggui_render\` |
338
- | \`streamSpec\` | agent → UI (many) | Typed channels for live updates via \`ggui_emit\` |
339
- | \`actionSpec\` | UI → agent (events) | Discrete events driving the agent's next turn (consumed via \`ggui_consume\`) |
340
- | \`contextSpec\` | UI → server (state mirror) | UI state the agent observes between turns |
341
- | \`agentCapabilities.tools\` | catalog | Tools the contract references via \`actionSpec[*].nextStep\` and \`streamSpec[*].source.tool\` |
342
- | \`clientCapabilities.gadgets\` | catalog | Browser-capability gadget hooks the component code mounts (e.g., \`useGeolocation\`) |
343
-
344
- **Placement rule for inbound specs**: actions drive turns; context observes state. There is no third category.
345
-
346
- **Data vs behavior**: the contract describes data flow; the component code describes behavior. Scroll, focus, toast, animation, clipboard write — all component code, never contract fields.
347
-
348
- ## Defensive coding for absent / late-arriving data
349
-
350
- 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.**
351
-
352
- - **Optional contract props**: a \`Props\` field marked \`?\` in the boilerplate may be omitted by the agent ENTIRELY — 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.
353
- - **Array iteration**: always default to \`[]\` before \`.map\`/\`.filter\`/\`.length\`. Use \`(props.items ?? []).map(...)\` not \`props.items.map(...)\`. Same for stream.all, stream.latest, etc.
354
- - **Object access**: optional-chain through nested fields. \`props.user?.name ?? 'Anonymous'\` not \`props.user.name\`.
355
- - **Number ops**: default before arithmetic. \`(props.count ?? 0) + 1\` not \`props.count + 1\`.
356
- - **Stream latest**: \`useStream\` returns \`{latest: T | null, all: T[], isComplete: boolean}\`. The default \`all\` is \`[]\` so it's safe to map; \`latest\` is null until the first frame arrives — guard before reading \`.foo\`.
357
- - **Stream reconciliation**: when a stream event carries an \`action\` discriminant (e.g. \`create | move | edit | delete\`), the channel is a CRUD feed — your handler MUST branch on EVERY value: append on \`create\`, drop on \`delete\`, replace-by-id on \`move\` / \`edit\`. Merging only the "edit" case silently loses created and deleted items. Reconcile into the SAME state that seeds from \`props\` (e.g. \`useState(() => props.tasks ?? [])\`) so the seed data and the live feed render as one list — and handle an event whose id is not yet present (a \`create\` for an unknown item) by inserting it, not ignoring it.
358
- - **Props re-sync on repaint**: props the agent owns are the source of truth for display — prefer rendering DIRECTLY from \`props\` (controlled, no local copy). When local optimistic state genuinely needs a prop seed (a click bumps a count before the agent confirms), it MUST re-sync on prop change — \`useEffect(() => setCount(props.count ?? 0), [props.count])\` — or a \`ggui_amend\` repaint applies the new props while the display keeps the stale local value. BAD: \`const [count, setCount] = useState(props.count ?? 0)\` alone. GOOD: the same seed + the one-line re-sync effect.
359
- - **Loading state**: while data is still absent, render \`<Skeleton>\` placeholders — never a blank screen. \`<Skeleton variant="text" />\` for a text line, \`variant="circle"\` for an avatar slot, default \`rect\` for a block.
360
- - **Empty state**: when a list or results array is empty, render \`<EmptyState title="…" description="…" />\` — a region that renders nothing when empty looks broken to the user.
361
-
362
- Unhandled \`Cannot read properties of undefined\` errors trip the iframe error boundary and the user sees "Something went wrong" — a regression class the runtime can't recover from.
363
-
364
- ## Picking the right primitive for user gestures
365
-
366
- Choose by what the user is DOING, not where the result goes — the runtime handles the routing.
367
-
368
- | Gesture intent | LLM writes | Notes |
369
- | -------------- | ---------- | ----- |
370
- | Fire a server-side action | \`useAction(name)\` + call \`dispatch(name, payload)\` | Every action is agent-routed. The runtime emits an event on \`ggui_consume\`; the agent reacts on its next turn. If the contract entry declares \`nextStep: 'X'\`, that names the tool the agent SHOULD call next — advisory hint forwarded as event metadata. |
371
- | Surface state to the agent's context | the auto-generated \`setSlotName\` setter (from the boilerplate's \`useGguiContext\` line) | The runtime owns useState + Provider; the boilerplate emits one \`const [slot, setSlot] = useGguiContext<T>('slot')\` line per declared \`contextSpec\` slot. Write plain JSX, no \`useState\`, no Provider wrap. Every value change auto-flows to the host LLM (debounced). One-way client → agent — see "Observable state via \`contextSpec\`" below. |
372
- | Use a browser capability (camera, mic, geolocation, clipboard, file picker, notifications) | call the hook the contract declared, e.g., \`const loc = useGeolocation();\` and \`await loc.start()\` | The contract's \`clientCapabilities.gadgets\` declares which gadget exports the UI uses. The hook implementations live in \`@ggui-ai/gadgets\` (or a third-party package named in the \`Package\` column). Read \`status\` ("idle" / "prompting" / "active" / "completed" / "denied" / "error") to gate UI, and thread the resolved \`value\` into a contextSpec slot or actionSpec payload if the agent needs to see it. |
373
- | Open external link | Plain \`<a href="https://...">\` (or \`target="_blank"\`) | External cross-origin clicks are intercepted and routed through the host (security warnings, app-internal navigation, audit). Same-origin links and \`#fragment\` jumps stay native. |
374
- | Toggle fullscreen / chrome | Plain \`el.requestFullscreen()\` / \`document.exitFullscreen()\` | The native browser API is intercepted; the host adjusts iframe chrome accordingly. Returns a resolved promise so \`.then()\` / \`await\` chains don't break. |
375
-
376
- Every gesture fires a uniform server-side audit envelope (\`ggui_runtime_submit_action\`) so operators see all three patterns in RenderInspector with the same shape.
377
-
378
- **Don't import wire hooks for link / display-mode.** \`useAction\` is the only wire hook for user gestures; links and fullscreen use plain HTML / browser APIs.
379
-
380
- **All actions are agent-routed.** Every action emits an event the agent reacts to on its next turn via \`ggui_consume\`. The optional \`nextStep: '<tool>'\` field on an \`actionSpec\` entry is a HINT naming the tool the agent SHOULD call next — the contract author's recommendation, NOT a binding directive. The agent decides whether to honor it. If you want to declare a tool catalog entry the contract references, add it to \`agentCapabilities.tools[<name>]\` with input/output schemas; the cross-ref linter rejects dangling \`nextStep\` values that don't resolve to a declared catalog entry.
381
-
382
- ## Making a primitive interactive — \`as={Trait}\`
383
-
384
- Structural primitives (\`Box\`, \`Stack\`, \`Row\`, \`Card\`) have NO \`onClick\` by default. Add interactivity with the \`as\` prop — a trait, not a wrapper:
385
-
386
- \`\`\`tsx
387
- <Card as={Clickable} onClick={() => dispatch('select', { id })}
388
- hoverStyle={{ boxShadow: 'var(--ggui-shape-shadow-lg)' }}>…</Card>
389
- \`\`\`
390
-
391
- - \`as={Clickable}\` → \`onClick\` + keyboard activation (Enter/Space) + \`role="button"\` + \`hoverStyle\`/\`activeStyle\`/\`cursor\`.
392
- - \`as={Hoverable}\` → \`hoverStyle\` only (no click). \`as={Pressable}\` → \`onPress\` + \`pressStyle\`.
393
-
394
- \`as={Trait}\` is a PROP — it does NOT re-nest the JSX. Never write \`<Clickable>…</Clickable>\` around a primitive; put \`as={Clickable}\` on the primitive itself. The trait carries the keyboard + ARIA wiring, so don't hand-write \`onKeyDown\` / \`role\`. Trait components (\`Clickable\`, \`Hoverable\`, \`Pressable\`) import from \`@ggui-ai/design\` like everything else — the boilerplate already imports them.
395
-
396
- **Semantic components are already interactive** — \`Button\` (\`onClick\`), \`Link\` (\`href\`), \`Input\` / \`Select\` (\`onChange\`). Use their own props; never put \`as\` on them. \`Text\` picks its element with \`is\` (\`<Text is="label">\`), not \`as\`.
397
-
398
- **Never nest two interactive elements.** Interactive content MUST NOT contain other interactive content — a gesture on the inner control bubbles to the outer one and fires BOTH handlers (one user click → the action dispatched twice). Do NOT put a \`Button\`, \`Checkbox\`, \`Input\`, \`Select\`, \`Link\`, or another \`as={Clickable}\` primitive inside a \`Card\` / \`Box\` / \`Row\` / \`Stack\` that is itself \`as={Clickable}\`. Wire each \`useAction\` callback to exactly ONE surface: EITHER the whole card is the trigger (interactive container, no interactive children) OR an inner control is the trigger (plain container, no \`as={Clickable}\`) — never both. A row with a checkbox: put the action on the \`Checkbox onChange\` and leave the row plain.
399
-
400
- **\`Text\` / \`Heading\` accept NO event handlers and NO \`as\` — only \`style\` / \`className\` plus their own typed props.** \`onClick\`, \`onDoubleClick\`, \`as={Clickable}\`, \`color\` are all type errors on \`Text\`. When the request says a label is "clickable", "editable", "edit on click / double-click", or "tap to …", do ONE of these — never put the handler on \`Text\`:
401
-
402
- \`\`\`tsx
403
- // Click-to-edit a label: wrap the Text in a Clickable structural primitive.
404
- <Box as={Clickable} onClick={() => setEditingId(task.id)}
405
- style={{ cursor: 'pointer' }}>
406
- <Text weight="semibold">{task.title}</Text>
407
- </Box>
408
-
409
- // Or pair the label with an explicit edit Button (clearer affordance).
410
- <Row gap="xs" align="center">
411
- <Text weight="semibold">{task.title}</Text>
412
- <Button variant="ghost" size="xs" aria-label="Edit title"
413
- onClick={() => setEditingId(task.id)}>Edit</Button>
414
- </Row>
415
-
416
- // In edit mode, swap the Text for an Input.
417
- {editingId === task.id
418
- ? <Input value={draftTitle} onChange={setDraftTitle} label="Task title" />
419
- : <Text weight="semibold">{task.title}</Text>}
420
- \`\`\`
421
-
422
- ## Anti-patterns — DO NOT WRITE
423
-
424
- The following identifiers / shapes are RETIRED from the contract surface as of 2026-05-11. Pre-2026-05-11 examples in your training data may include them; do not reproduce. The linter / CI grep gate rejects:
425
-
426
- - \`useWiredTool\`, \`useClientTool\` — retired hooks. Replace with \`useAction\` (events) and the named hook from \`@ggui-ai/gadgets\` (browser capabilities).
427
- - \`dispatch: { kind: 'tool', tool: '...' }\` / \`dispatch: { kind: 'agent', intendedTool: '...' }\` — retired discriminated-union. Use the flat optional \`nextStep?: '<tool>'\` instead.
428
- - \`mode: 'host-routed'\` / \`mode: 'tool'\` — retired \`mode\` field. Same fix: flat \`nextStep?\`.
429
- - \`broadcast: {...}\` on the contract — retired top-level field. Use \`streamSpec[channel].source: {tool, args?}\` to declare a tool-fed channel.
430
- - \`wiredTools\` / \`agentTools\` (top-level) — retired catalog names. Use \`agentCapabilities.tools\`.
431
- - \`clientTools\` / \`clientCapabilities.capabilities\` — retired catalog shapes. Use \`clientCapabilities.gadgets\` (entries declare hooks, not RPC).
432
- - \`@ggui-ai/client-tools\` — retired package name. Import gadget hooks from \`@ggui-ai/gadgets\`.
433
- - \`intendedTool\` — retired. Use \`nextStep\` (flat).
434
- - \`props: { properties: {...} }\` as a CONTRACT field — retired. The contract field is \`propsSpec\` (the wire \`props\` field on render / update still carries VALUES).
435
-
436
- ## Cross-reference rules
437
-
438
- When you declare a reference, also declare the catalog entry it points at:
439
-
440
- - \`actionSpec[X].nextStep = 'fetch_inbox'\` → \`agentCapabilities.tools.fetch_inbox = { toolInfo: { inputSchema, description?, outputSchema? }, usage?, example? }\` MUST exist. Cross-ref code: \`CTR_REF_NEXT_STEP\`.
441
- - \`streamSpec[X].source.tool = 'list_messages'\` → \`agentCapabilities.tools.list_messages\` MUST exist. Cross-ref code: \`CTR_REF_STREAM_SOURCE\`.
442
- - The catalog entry's schemas MUST be a superset of the referencing spec's schema. Cross-ref code: \`CTR_SCHEMA_INCOMPAT\`.
443
-
444
- ## clientCapabilities — registered catalog
445
-
446
- ${gadgetsSection}
447
-
448
- Each hook conforms to \`GadgetHook<TOutput, TOptions>\`: call \`start(opts?)\` to fire, read \`{value, status, error, stop?}\`. \`status\` walks through \`idle → prompting → active|completed\` or routes to \`denied\` / \`error\` on failure.
449
-
450
- 3rd-party plugins (Leaflet maps, Mapbox, Stripe, Chart.js, …) are registered via \`createGguiGadget\` from \`@ggui-ai/gadgets\` and surface in this same table when the operator has added them to \`App.gadgets\`. Reference any registered hook by name — render validation rejects hooks not in this catalog with \`gadget_not_registered\`.
451
-
452
- ## Observable state via \`contextSpec\`
453
-
454
- When the contract declares \`contextSpec\`, the boilerplate auto-generates one \`useGguiContext\` call per slot at the top of your component. The runtime owns the underlying \`useState\` and the Provider tree — **you do NOT write \`useState\` or any \`<Provider>\` wrap yourself**:
455
-
456
- \`\`\`tsx
457
- import { useGguiContext } from '@ggui-ai/wire';
458
-
459
- export default function Component(props: Props) {
460
- // AUTO-GENERATED — do not remove or rename:
461
- const [currentStep, setCurrentStep] = useGguiContext<number>('currentStep');
462
- const [draftText, setDraftText] = useGguiContext<string>('draftText');
463
-
464
- // Plain JSX. No Provider wrap. The runtime already wrapped your
465
- // component in nested SingleSlotProviders before this code ran.
466
- return (
467
- <Container>
468
- <Text>Step {currentStep}</Text>
469
- <Input value={draftText} onChange={(e) => setDraftText(e.target.value)} />
470
- <Button onClick={() => setCurrentStep((s) => s + 1)}>Next</Button>
471
- </Container>
472
- );
473
- }
474
- \`\`\`
475
-
476
- For every declared slot you have **\`slotName\` + \`setSlotName\`** in scope:
477
- - **Read** the value to render: \`<Text>Step {currentStep}</Text>\`
478
- - **Write** via the setter: \`setCurrentStep(s => s + 1)\` (in callbacks, effects, anywhere)
479
-
480
- Every value change is mirrored to the host LLM's context automatically (debounced, default 300ms — adjustable per-slot via \`entry.debounceMs\` in the contract). The agent sees the user's interaction state — drafts, current step, hover, selection — without you calling any API.
481
-
482
- **When to use the auto-generated state.** Any slot the contract declared. If \`contextSpec.draftText\` exists, bind \`<Input value={draftText} onChange={e => setDraftText(e.target.value)}>\` so the agent sees the typing live. If \`contextSpec.currentStep\` exists, render the step indicator from \`currentStep\` and bump it via \`setCurrentStep\` in your "next" callback.
483
-
484
- **When NOT to use it.** Local UI state the contract did NOT declare — \`isDropdownOpen\`, hover flags, animation phase, ephemeral toggles. For those, use a plain \`useState\` directly. The runtime ignores undeclared state.
485
-
486
- **\`contextSpec\` direction is one-way: client → agent.** The agent uses \`propsSpec\` (via \`ggui_amend\` for in-place repaints of this mount, or \`ggui_update\` when it mints a new history card) and \`streamSpec\` (via the live channel) to push state TO the client. Don't try to write to the agent via \`contextSpec\` — there is no return path.
487
-
488
- **Schema mismatches drop silently.** If you set a value that doesn't match the slot's schema (e.g. a string into a \`{type: 'number'}\` slot), the runtime logs a dev \`console.warn\` and skips the post. Make sure your setter calls produce values that match the declared shape.
380
+ ` +
381
+ [
382
+ PROTOCOL_NOTES,
383
+ CONTRACT_SURFACE,
384
+ DEFENSIVE_CODING,
385
+ GESTURE_ROUTING,
386
+ INTERACTIVE_TRAITS,
387
+ ANTI_PATTERNS,
388
+ CROSS_REFERENCE_RULES,
389
+ renderGadgetCatalogSection(gadgetsSection),
390
+ CONTEXT_SPEC_SECTION,
391
+ ].join("\n\n") +
392
+ `
489
393
 
490
394
  ${pitfallsBlock}
491
395
 
492
396
  ## Reference: Wire Hooks
493
397
  ${wireDoc}
494
398
 
495
- ${DESIGN_SYSTEM_GUIDANCE}
399
+ ${withStylingProfile(DESIGN_SYSTEM_GUIDANCE, profileSection)}
496
400
 
497
401
  ### CSS Token Documentation
498
402
  ${designSystemDocs}
@@ -510,7 +414,7 @@ ${primitivesDoc}
510
414
  * primitive *names*. Exported so `prompt-type-drift.test.ts` can
511
415
  * verify its enum claims against the auto-generated primitive catalog.
512
416
  */
513
- export const DESIGN_SYSTEM_GUIDANCE = `## Imports & Component Surface
417
+ const DESIGN_SYSTEM_GUIDANCE_SURFACE_AND_TOKENS = `## Imports & Component Surface
514
418
 
515
419
  Import ONLY from: \`react\`, \`@ggui-ai/design\`, \`@ggui-ai/wire\`. The ENTIRE design system — every primitive, component, composition and trait — is exported from the single \`@ggui-ai/design\` entry: \`import { Card, Grid, Stack, Modal, Clickable } from '@ggui-ai/design'\`. There are NO subpaths (\`/primitives\`, \`/components\`, …) — never import from them. Use the design components — DO NOT use raw HTML elements (\`<button>\`, \`<input>\`, \`<div>\` for layout) or Tailwind classes; those render unstyled in the iframe runtime.
516
420
 
@@ -538,14 +442,14 @@ EXACT primitive prop values (other values are silently ignored — the design sy
538
442
  - \`<Heading level={1|2|3|4|5|6}>\` — sizes are preset by level (h1 = 4xl bold, h2 = 3xl bold, h3 = 2xl semibold). Pass a number, not \`level="h1"\`. Heading uses the same \`tone\` slot vocabulary as Text.
539
443
  - \`<Icon name="..." tone="...">\` / \`<Spinner tone="...">\` / \`<Link href="..." tone="...">\` / \`<Divider tone="...">\` — same \`tone\` vocabulary as Text. Default = \`currentColor\` (Icon), primary-tinted (Spinner / Link), outlineVariant (Divider). Use \`tone="inherit"\` when you want the element to track the parent's foreground color (e.g. an Icon next to muted text).
540
444
  - \`<Button variant="...">\` — \`primary | secondary | outline | ghost | danger\`. Sizes \`xs | sm | md | lg\`. Use \`primary\` for the main action — renders in the brand color automatically.
541
- - \`<Card padding="lg" shadow="md" radius="lg" surface="default">\` — shadow \`none|sm|md|lg|xl\`, radius \`none|sm|md|lg|xl\`. \`surface\` slot picks the fill: \`default | elevated | sunken | accent | inverted | transparent\`. Use \`inverted\` for dark testimonial-style cards on a light theme; \`accent\` for branded fills.
445
+ - \`<Card padding="lg" shadow="md" radius="lg" surface="default">\` — shadow \`none|sm|md|lg|xl\`, radius \`none|sm|md|lg|xl\`. \`surface\` slot picks the fill: \`default | elevated | sunken | accent | inverted | hero | transparent\`. Use \`inverted\` for dark testimonial-style cards on a light theme; \`accent\` for branded fills; \`hero\` for the one hero / greeting panel (the theme's hero ground: brand-tinted on a light host, the ink pair on a dark one). \`inverted\` and \`hero\` OWN their ground — never set \`background\` in their \`style\` (tier-0 fails it); the surface paints it.
542
446
  - \`<Box surface="...">\` — same surface slots as Card. \`surface\` is the ONLY theme-tracking background prop; the legacy \`background="..."\` prop has been removed. For non-theme-mapped brand colors (a partner's exact brand hex like Stripe purple), use the typed escape \`<Box assetColor="#635BFF" assetSemantic="stripe-brand-purple">\` — both props are required, and \`assetSemantic\` MUST be a non-empty human-readable label. Tier-0 self-check rejects every other hex / rgba on Box.
543
447
  - \`<Stack gap="...">\` / \`<Row gap="...">\` — \`gap\` takes the **spacing scale** (next bullet). \`align\` (cross-axis) is ONLY \`start | center | end | stretch\` and \`justify\` (main-axis) is ONLY \`start | center | end | between | around | evenly\` — NEVER the raw CSS values \`flex-start\` / \`flex-end\` / \`space-between\`, which are type errors.
544
448
  - **Spacing scale** — \`gap\` (Stack / Row / Grid) and \`padding\` (Card / Box / Container) take a t-shirt size: \`none | xs | sm | md | lg | xl | 2xl\`. Each resolves to a \`--ggui-spacing-*\` token (xs≈4px, sm≈8px, md≈16px, lg≈24px, xl≈32px, 2xl≈48px). A bare number is treated as pixels. NEVER pass a raw CSS length such as \`gap="8px"\` — it is silently dropped by the browser and the gap collapses to 0; use the scale name (\`gap="sm"\`).
545
449
  - \`<Grid columns={N} gap="md">\` — 2-D layout (rows AND columns). Reach for it for card galleries, stat grids and dashboards — NEVER hand-roll \`style={{ display: 'grid' }}\`. When the request names exact per-breakpoint counts ("3 per row on desktop, 1 on mobile"), pass a map: \`<Grid columns={{ base: 1, md: 3 }}>\` (breakpoints \`sm\`/\`md\`/\`lg\`/\`xl\`; the design system emits the media queries). For an open-ended gallery where any column count is fine, use \`<Grid minColumnWidth={220}>\` — it fits as many equal columns as the width allows. \`radius\` (Card / Box / Image) takes the scale \`none | sm | md | lg | xl\`.
546
450
  - \`<Stat label="…" value="…" delta="+12%" trend="up">\` — KPI display (label + big value + trend-coloured delta + optional \`icon\`). \`trend\` is \`up | down | neutral\` (delta renders green / red / muted). Reach for it for any "show a number" UI; drop several into a \`<Grid>\` for a stat grid instead of hand-building label+value pairs.
547
451
  - \`<Stepper steps={STEPS} current={step} />\` — display-only step indicator for wizards/checkouts. \`steps\` is a top-level \`const\` array of labels; \`current\` is YOUR zero-indexed \`useState\` value; \`orientation\` is \`horizontal | vertical\`; optional \`onStepClick={(i) => …}\` makes steps clickable. Stepper never owns navigation state — your Next/Back handlers move \`current\`.
548
- - \`<Badge variant="...">\` — \`default | primary | secondary | success | warning | error | info\` for colored pills. Great for status/condition labels. There is NO \`neutral\` variant — use \`default\` (or \`secondary\`) for an un-tinted pill.
452
+ - \`<Badge variant="...">\` — \`default | primary | secondary | tertiary | success | warning | error | info\` for colored pills. Great for status/condition labels. There is NO \`neutral\` variant — use \`default\` (or \`secondary\`) for an un-tinted pill.
549
453
 
550
454
  **Color choice rule of thumb.** Reach for typed slots first: Button \`variant\`, Badge \`variant\`, Alert \`variant\`, Text/Heading/Icon/Spinner/Link/Divider \`tone\`, Box/Card \`surface\`. NEVER hardcode hex \`#XXXXXX\`, rgba, or hsl — tier-0 self-check rejects them with \`tokens:hex-color\` / \`tokens:hardcoded-color-fn\` and the LLM must remediate. Hardcoded colors break the operator's theme switch (Indigo → Claudic → Cyberpunk preset has zero effect on a card hardcoded with \`background: '#000'\`).
551
455
 
@@ -577,9 +481,10 @@ MANDATORY:
577
481
  4. NEVER include literal fallback values in ANY token reference — colors, spacing, typography, radius, shadows alike. Write \`var(--ggui-spacing-4)\` bare, never \`var(--ggui-spacing-4, 16px)\`; \`var(--ggui-color-primary-600)\` bare, never \`var(--ggui-color-primary-600, #0284c7)\`. The runtime injects every token; a literal fallback paints the wrong value exactly when the operator's theme matters most.
578
482
 
579
483
  Token categories:
580
- - Brand: \`var(--ggui-color-primary-600)\`, \`var(--ggui-color-primary-50)\`
581
- - Text: \`var(--ggui-color-onSurface)\`, \`var(--ggui-color-onSurfaceVariant)\`
582
- - Backgrounds: \`var(--ggui-color-surface)\`, \`var(--ggui-color-surfaceVariant)\`
484
+ - Brand fills: \`var(--ggui-color-primary-600)\`, \`var(--ggui-color-primary-50)\`
485
+ - Accent text: \`var(--ggui-color-link)\` — eyebrows, taglines, links, labels; readable per mode (never a bare \`primary-<stop>\` as a text color)
486
+ - Text: \`var(--ggui-color-onContainer)\`, \`var(--ggui-color-onSunken)\`
487
+ - Backgrounds: \`var(--ggui-color-container)\`, \`var(--ggui-color-sunken)\`
583
488
  - Borders: \`var(--ggui-color-outline)\`
584
489
  - Spacing: \`var(--ggui-spacing-4)\`, \`var(--ggui-spacing-6)\`
585
490
  - Typography: \`var(--ggui-font-size-sm)\`, \`var(--ggui-font-weight-semibold)\`
@@ -596,11 +501,12 @@ Use the FULL primary palette throughout the component — NOT only on submit but
596
501
  |---------|-------|---------|
597
502
  | Section headers, hero areas, highlight strips | \`primary-50\` / \`primary-100\` | Subtle branded backgrounds |
598
503
  | Borders, dividers, focus rings, input focus | \`primary-200\` / \`primary-300\` | Branded structure |
599
- | Icons, links, labels, active indicators | \`primary-500\` / \`primary-600\` | Core accent color |
504
+ | Accent TEXT — eyebrows, taglines, links, labels, inline arrows | \`link\` (\`var(--ggui-color-link)\`) or \`<Text tone="emphasized">\` | The brand's READABLE ink, derived per mode to clear 4.5:1 — a bare \`primary-300…600\` as a text color fails tier-0 |
505
+ | Icon fills, badges, active indicators | \`primary-500\` / \`primary-600\` | Core accent fill |
600
506
  | Buttons, CTAs, filled interactive elements | \`primary-600\` / \`primary-700\` | Primary actions |
601
507
  | Headings on light primary backgrounds | \`primary-800\` / \`primary-900\` | High-contrast branded text |
602
508
 
603
- Use semantic tokens (\`onSurface\`, \`onSurfaceVariant\`) for body text and secondary info. NEVER use raw \`neutral-*\` or \`gray-*\` for body text — they break in dark themes.
509
+ Use the layering roles' inks (\`onContainer\` for body text on a card, \`onSunken\` for secondary text, captions, labels) — never raw neutrals. NEVER use raw \`neutral-*\` or \`gray-*\` for body text — they break in dark themes.
604
510
 
605
511
  ### Theme-Agnostic Design
606
512
 
@@ -609,7 +515,7 @@ Components MUST be theme-agnostic — they reference CSS variables but NEVER ass
609
515
  DO:
610
516
  - Use \`var(--ggui-color-primary-*)\` for brand elements — the theme controls what "primary" means
611
517
  - Use \`var(--ggui-shape-shadow-*)\` for depth, \`var(--ggui-shape-radius-*)\` for corners
612
- - Use semantic color roles: primary for brand, surface/onSurface for structure, success/error/warning for state
518
+ - Use semantic color roles: primary for brand; the layering roles (ground / container / elevated / sunken + their on* inks) for structure; success/error/warning for state
613
519
 
614
520
  DON'T:
615
521
  - Don't assume primary is blue — could be red, green, purple
@@ -620,7 +526,7 @@ Visual hierarchy via tokens:
620
526
  - Elevated sections: \`var(--ggui-shape-shadow-md)\` + \`var(--ggui-shape-radius-lg)\`
621
527
  - Highlighted regions: \`var(--ggui-color-primary-50)\` background
622
528
  - Active/selected: \`var(--ggui-color-primary-100)\` background
623
- - Section headers: \`var(--ggui-color-primary-600)\` text or border-bottom
529
+ - Section headers / eyebrows: \`var(--ggui-color-link)\` text, or a \`var(--ggui-color-primary-600)\` border-bottom
624
530
 
625
531
  ## Responsive Design (CRITICAL)
626
532
 
@@ -630,59 +536,9 @@ Generated components become reusable blueprints — the same blueprint serves ph
630
536
  2. Relative/fluid units — prefer \`%\`, \`em\`, \`rem\`, \`min()\`, \`max()\`, \`clamp()\` over fixed \`px\`.
631
537
  3. Fluid widths — \`max-width\` with \`width: 100%\`. Never set a fixed width.
632
538
  4. Compact padding — components are embedded in containers that provide their own chrome.
633
- 5. No raw \`@media\` queries in component code — for a layout that must change by breakpoint, use \`<Grid columns={{ base: 1, md: 3 }}>\` (the design system emits the media queries for you) or a fluid \`minColumnWidth\` grid.
539
+ 5. No raw \`@media\` queries in component code — for a layout that must change by breakpoint, use \`<Grid columns={{ base: 1, md: 3 }}>\` (the design system emits the media queries for you) or a fluid \`minColumnWidth\` grid.`;
634
540
 
635
- ## Data Parameterization (CRITICAL)
636
-
637
- Generated components are CACHED blueprints reused across requests. NEVER hardcode request-specific data (names, cities, numbers, dates) into the component body. Define data as default prop values so the blueprint works for ANY similar request:
638
-
639
- \`\`\`tsx
640
- // BAD — hardcoded, only works for Tokyo
641
- const city = "Tokyo";
642
- const temp = 18;
643
-
644
- // GOOD — parameterized via props with defaults from the request
645
- interface Props {
646
- city?: string;
647
- temperature?: number;
648
- }
649
- export default function WeatherCard({ city = "Tokyo", temperature = 18 }: Props) {
650
- // A controller can override for Seoul, Paris, etc.
651
- }
652
- \`\`\`
653
-
654
- Rules:
655
- 1. All request-specific data → props with defaults. City names, tickers, user names, dates, counts.
656
- 2. Layout and styling are universal. Colors, spacing, structure — these are the reusable part.
657
- 3. Default values come from the current request — so the component renders correctly standalone.
658
- 4. Props interface must be typed and exported.
659
-
660
- ## Component Structure
661
-
662
- Keep JSX nesting depth to 3–5 levels. When deeper, extract repeated/complex sections into helper components — named functions defined above the main Component in the same file. Helpers take data + callbacks via props; they don't own state.
663
-
664
- \`\`\`tsx
665
- import { useState } from 'react';
666
- import { Container, Card, Stack, Text, Button, Input } from '@ggui-ai/design';
667
-
668
- interface Props {
669
- onSubmit?: (data: unknown) => void;
670
- }
671
-
672
- function ItemCard({ item, onEdit }: { item: Item; onEdit: (id: string) => void }) {
673
- return <Card padding="md">…</Card>;
674
- }
675
-
676
- export default function GeneratedComponent({ onSubmit }: Props) {
677
- return (
678
- <Container>
679
- {items.map((item) => <ItemCard key={item.id} item={item} onEdit={handleEdit} />)}
680
- </Container>
681
- );
682
- }
683
- \`\`\`
684
-
685
- ## Aesthetic Guidance (READ CAREFULLY — this is what separates "polished" from "ok")
541
+ const DESIGN_SYSTEM_GUIDANCE_AESTHETICS_AND_CHECKLIST = `## Aesthetic Guidance (READ CAREFULLY — this is what separates "polished" from "ok")
686
542
 
687
543
  ### Visual hierarchy — the SCALE GAP rule
688
544
 
@@ -704,11 +560,11 @@ A polished UI has ONE hero that dominates. Everything else supports it. Bad layo
704
560
  ### Color discipline — the 60/30/10 rule
705
561
 
706
562
  Don't paint everything in primary. Use:
707
- - **60% surface** (\`var(--ggui-color-surface)\` / \`onSurface\`) — body text, default backgrounds, structure
708
- - **30% surfaceVariant + onSurfaceVariant** — secondary text, captions, labels, dividers
563
+ - **60% container** (\`var(--ggui-color-container)\` / \`onContainer\`) — body text, default backgrounds, structure
564
+ - **30% sunken + onSunken** — wells, secondary text, captions, labels, dividers
709
565
  - **10% primary** — hero number, ONE highlight element, CTAs, brand accent
710
566
 
711
- If your component is 100% purple text on purple backgrounds, you've lost the eye. Headings can be \`onSurface\` (dark neutral) — they'll still feel weighty. Save the primary palette for one or two STAR moments.
567
+ If your component is 100% purple text on purple backgrounds, you've lost the eye. Headings can be \`onContainer\` (the card's ink) — they'll still feel weighty. Save the primary palette for one or two STAR moments.
712
568
 
713
569
  \`\`\`tsx
714
570
  // BAD — everything purple, eye has no anchor
@@ -717,7 +573,7 @@ If your component is 100% purple text on purple backgrounds, you've lost the eye
717
573
  <Text tone="emphasized">all body text</Text>
718
574
 
719
575
  // GOOD — hero pops, body is neutral, primary is reserved
720
- <Heading>Title</Heading> {/* defaults to onSurface */}
576
+ <Heading>Title</Heading> {/* defaults to onContainer */}
721
577
  <Text size="4xl" weight="bold" tone="emphasized">42</Text>
722
578
  <Text tone="muted">all body text</Text>
723
579
  \`\`\`
@@ -725,7 +581,7 @@ If your component is 100% purple text on purple backgrounds, you've lost the eye
725
581
  ### Visual rhythm — vary your card treatments
726
582
 
727
583
  A row of identical flat tiles feels monotone. Use card-treatment variation to create rhythm:
728
- - **Hero card**: \`<Card padding="xl" shadow="lg" radius="xl">\` with branded gradient background — anchors the eye
584
+ - **Hero card**: \`<Card surface="hero" padding="xl" shadow="lg" radius="xl">\` — the theme's hero ground (brand-tinted on a light host, the ink pair on a dark one); anchors the eye
729
585
  - **Stat tiles**: \`<Card padding="md" shadow="sm" radius="md">\` with surface bg — secondary
730
586
  - **Inline rows / list items**: no card chrome at all, just \`<Stack gap="sm">\` with dividers — tertiary
731
587
 
@@ -745,7 +601,7 @@ Don't render text-only metrics. A weather widget without a sun/cloud, a stock ca
745
601
  </Row>
746
602
  \`\`\`
747
603
 
748
- Use \`<Icon name="..." />\` (Lucide icon names in kebab-case) for line icons; emoji directly for status/weather/mood. Both are valid. For per-stat tiny accents, use a small icon next to the label.
604
+ Use \`<Icon name="..." />\` for line icons — ONLY the curated Lucide subset renders (call \`get_available_icons\` for the exact kebab-case names; a name outside it renders an empty box and fails the check); emoji directly for status/weather/mood. Both are valid. For per-stat tiny accents, use a small icon next to the label.
749
605
 
750
606
  ### Spacing — generosity beats compactness
751
607
 
@@ -775,3 +631,11 @@ Hero sections should feel airy. Use \`padding="xl"\` (32px) on the main card, no
775
631
  - [ ] Live/streaming regions wrapped in \`aria-live="polite"\`
776
632
  - [ ] Headings nest — one \`level={1}\`, then \`level={2}\`/\`{3}\` — never skipped or inverted
777
633
  - [ ] Wire hooks (\`useAction\`, \`useStream\`) imported from \`@ggui-ai/wire\` and consumed`;
634
+
635
+ export const DESIGN_SYSTEM_GUIDANCE = [
636
+ DESIGN_SYSTEM_GUIDANCE_SURFACE_AND_TOKENS,
637
+ DATA_PARAMETERIZATION,
638
+ FRAME_SIZING,
639
+ COMPONENT_STRUCTURE,
640
+ DESIGN_SYSTEM_GUIDANCE_AESTHETICS_AND_CHECKLIST,
641
+ ].join("\n\n");
@@ -0,0 +1,24 @@
1
+ // DO NOT EDIT imports — the wire + gadget imports below are runtime-resolution anchors.
2
+ // prettier-ignore-start
3
+ /* eslint-disable no-unused-vars */
4
+ {{REACT_IMPORT}}
5
+ // Compose freely: raw HTML elements, inline `style` props and <style> blocks.
6
+ // The `@ggui-ai/design` package is importable but OPTIONAL — add your own
7
+ // import only when a primitive (Skeleton, EmptyState, Icon, …) saves you work.
8
+ // Color stays on bare var(--ggui-color-*) tokens; everything else may be literal.
9
+ {{WIRE_IMPORT}}/* eslint-enable no-unused-vars */
10
+ // prettier-ignore-end
11
+
12
+ // DO NOT EDIT Props — auto-generated from the data contract
13
+ {{PROPS_INTERFACE}}
14
+ {{ACTION_TYPES}}
15
+ {{STREAM_TYPES}}
16
+
17
+ // Place helper components here if needed
18
+
19
+ export default function Component(props: Props) {
20
+ {{CONTEXT_HOOKS}}{{WIRE_HOOKS}}{{AXIS_SECTIONS}}
21
+ return (
22
+ {{LAYOUT}}
23
+ );
24
+ }