@ggui-ai/ui-gen 0.7.0 → 0.9.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 (52) hide show
  1. package/dist/adapters/generation-dispatch.d.ts +10 -0
  2. package/dist/adapters/generation-dispatch.js +172 -1392
  3. package/dist/adapters/generation-dispatch.js.map +1 -1
  4. package/dist/advanced/index.d.ts +1 -1
  5. package/dist/advanced/index.js +191 -1403
  6. package/dist/advanced/index.js.map +1 -1
  7. package/dist/boilerplate.js +3 -2
  8. package/dist/boilerplate.js.map +1 -1
  9. package/dist/compose.js +5 -5
  10. package/dist/compose.js.map +1 -1
  11. package/dist/{create-ui-generator-GX7tvbyH.d.ts → create-ui-generator-B9kV4U9h.d.ts} +13 -0
  12. package/dist/evaluation/axis-checks/index.js +32 -1
  13. package/dist/evaluation/axis-checks/index.js.map +1 -1
  14. package/dist/evaluation/axis-checks/registry.js +32 -1
  15. package/dist/evaluation/axis-checks/registry.js.map +1 -1
  16. package/dist/evaluation/index.js +57 -12
  17. package/dist/evaluation/index.js.map +1 -1
  18. package/dist/fragments/index.js +5 -5
  19. package/dist/fragments/index.js.map +1 -1
  20. package/dist/harness/index.d.ts +4 -125
  21. package/dist/harness/index.js +34 -19
  22. package/dist/harness/index.js.map +1 -1
  23. package/dist/harness/runtime.d.ts +9 -0
  24. package/dist/harness/runtime.js +3 -2
  25. package/dist/harness/runtime.js.map +1 -1
  26. package/dist/harness/types-public.js +8 -7
  27. package/dist/harness/types-public.js.map +1 -1
  28. package/dist/index.d.ts +1 -1
  29. package/dist/index.js +191 -1403
  30. package/dist/index.js.map +1 -1
  31. package/dist/llm-router-1TyI0_TV.d.ts +156 -0
  32. package/dist/providers/index.d.ts +35 -93
  33. package/dist/providers/index.js +13 -8
  34. package/dist/providers/index.js.map +1 -1
  35. package/dist/validation/index.js +2 -2
  36. package/dist/validation/index.js.map +1 -1
  37. package/package.json +18 -18
  38. package/src/adapters/generation-dispatch.ts +19 -2
  39. package/src/boilerplate/system-prompt.ts +3 -2
  40. package/src/create-ui-generator.ts +44 -11
  41. package/src/evaluation/axis-checks/checks/state-merge.ts +1 -1
  42. package/src/evaluation/axis-checks/checks/universal.ts +39 -0
  43. package/src/evaluation/llm-evaluator.ts +13 -3
  44. package/src/evaluation/visual-evaluator.ts +5 -1
  45. package/src/fragments/realtime.ts +3 -3
  46. package/src/fragments/state.ts +2 -2
  47. package/src/harness/coding/init-session.ts +4 -1
  48. package/src/harness/coding/run-eval-round.ts +6 -0
  49. package/src/harness/llm-router.ts +59 -14
  50. package/src/harness/runtime.ts +8 -0
  51. package/src/providers/bedrock.ts +84 -22
  52. package/src/validation/ui-compiler.ts +2 -2
@@ -64,11 +64,11 @@ var stateFragments = {
64
64
  axis: "state",
65
65
  value: "merge",
66
66
  cacheTier: "axisDelta",
67
- promptText: "## State: merge (live entity reconciliation)\n1. Seed `const [items, setItems] = useState(props.items ?? [])`.\n2. On stream updates: `setItems(prev => prev.map(it => it.id === event.id ? { ...it, ...event } : it))` \u2014 merge by id, do NOT append.\n3. Memoize derived views (grouping/sorting/filtering) with useMemo.\n4. Per-item actions pass `{ id, ... }` in the payload.\n5. Never push the stream payload into an append-only list unless realtime=append.",
67
+ promptText: "## State: merge (live entity reconciliation)\n1. Seed `const [items, setItems] = useState(props.items ?? [])` AND re-seed on prop change: `useEffect(() => setItems(props.items ?? []), [props.items])` \u2014 a `ggui_amend` repaint must move the list.\n2. On stream updates: `setItems(prev => prev.map(it => it.id === event.id ? { ...it, ...event } : it))` \u2014 merge by id, do NOT append.\n3. Memoize derived views (grouping/sorting/filtering) with useMemo.\n4. Per-item actions pass `{ id, ... }` in the payload.\n5. Never push the stream payload into an append-only list unless realtime=append.",
68
68
  boilerplateMarker: [
69
69
  "",
70
70
  " // \u2500\u2500 Live entity state (merge-by-id) \u2500\u2500",
71
- " // useState(props.items ?? []); merge stream events by item.id; never append.",
71
+ " // useState(props.items ?? []); re-seed via useEffect on [props.items]; merge stream events by item.id; never append.",
72
72
  ""
73
73
  ].join("\n")
74
74
  },
@@ -186,13 +186,13 @@ var realtimeFragments = {
186
186
  axis: "realtime",
187
187
  value: "merge",
188
188
  cacheTier: "axisDelta",
189
- promptText: "## Realtime: merge\nStream events carry an id; merge into LOCAL STATE. Subscribing alone is not enough \u2014 the local list must update on each event for the DOM to re-render.\n\n```tsx\nconst stream = useStream<UpdateT>('streamName');\nconst [items, setItems] = useState(props.items);\nuseEffect(() => {\n if (!stream.latest) return;\n setItems((prev) => prev.map((it) => it.id === stream.latest!.id ? { ...it, ...stream.latest } : it));\n}, [stream.latest]);\n// Render `items.map(...)` \u2014 NOT props.items, NOT stream.all.\n```\n\nNever append \u2014 stream=merge means the entity already exists locally."
189
+ promptText: "## Realtime: merge\nStream events carry an id; merge into LOCAL STATE. Subscribing alone is not enough \u2014 the local list must update on each event for the DOM to re-render.\n\n```tsx\nconst stream = useStream<UpdateT>('streamName');\nconst [items, setItems] = useState(props.items);\nuseEffect(() => setItems(props.items), [props.items]); // re-seed on ggui_amend repaint \u2014 props stay the source of truth\nuseEffect(() => {\n if (!stream.latest) return;\n setItems((prev) => prev.map((it) => it.id === stream.latest!.id ? { ...it, ...stream.latest } : it));\n}, [stream.latest]);\n// Render `items.map(...)` \u2014 NOT props.items, NOT stream.all.\n```\n\nNever append \u2014 stream=merge means the entity already exists locally."
190
190
  },
191
191
  append: {
192
192
  axis: "realtime",
193
193
  value: "append",
194
194
  cacheTier: "axisDelta",
195
- promptText: "## Realtime: append\nStream events are new entities; APPEND to local state. Subscribing alone is not enough \u2014 the local list must update for the DOM to re-render.\n\n```tsx\nconst stream = useStream<EventT>('streamName');\nconst [items, setItems] = useState(props.items);\nuseEffect(() => {\n if (!stream.latest) return;\n setItems((prev) => [...prev, stream.latest!]);\n}, [stream.latest]);\n// Render `items.map(...)` \u2014 NOT props.items, NOT stream.all (which accumulates only post-mount).\n```\n\nUse head for newest-first, tail for chat-style. Cap list length if needed. Do not dedupe unless the contract guarantees at-most-once."
195
+ promptText: "## Realtime: append\nStream events are new entities; APPEND to local state. Subscribing alone is not enough \u2014 the local list must update for the DOM to re-render.\n\n```tsx\nconst stream = useStream<EventT>('streamName');\nconst [items, setItems] = useState(props.items);\nuseEffect(() => setItems(props.items), [props.items]); // re-seed on ggui_amend repaint \u2014 props stay the source of truth\nuseEffect(() => {\n if (!stream.latest) return;\n setItems((prev) => [...prev, stream.latest!]);\n}, [stream.latest]);\n// Render `items.map(...)` \u2014 NOT props.items, NOT stream.all (which accumulates only post-mount).\n```\n\nUse head for newest-first, tail for chat-style. Cap list length if needed. Do not dedupe unless the contract guarantees at-most-once."
196
196
  },
197
197
  status: {
198
198
  axis: "realtime",
@@ -210,7 +210,7 @@ var realtimeFragments = {
210
210
  axis: "realtime",
211
211
  value: "mixed",
212
212
  cacheTier: "axisDelta",
213
- promptText: "## Realtime: mixed\nMultiple stream channels with DIFFERENT semantics. Each `streamKinds` entry in the contract maps to one of: merge, append, status, presence. Subscribe + sync to local state PER CHANNEL \u2014 subscribing alone never re-renders the DOM.\n\nExample (chat-interface \u2014 message=append, typing=presence):\n\n```tsx\nconst messages = useStream<MessageT>('message');\nconst typing = useStream<TypingT>('typing');\nconst [messageList, setMessageList] = useState(props.messages);\nconst [typingUsers, setTypingUsers] = useState<Set<string>>(new Set());\n\nuseEffect(() => {\n if (!messages.latest) return;\n setMessageList((prev) => [...prev, messages.latest!]);\n}, [messages.latest]);\n\nuseEffect(() => {\n if (!typing.latest) return;\n setTypingUsers((prev) => {\n const next = new Set(prev);\n if (typing.latest!.active) next.add(typing.latest!.sender);\n else next.delete(typing.latest!.sender);\n return next;\n });\n}, [typing.latest]);\n```\n\nDo NOT treat all events the same. Do NOT skip the local-state sync \u2014 `useStream` alone won't trigger DOM updates of your derived view.",
213
+ promptText: "## Realtime: mixed\nMultiple stream channels with DIFFERENT semantics. Each `streamKinds` entry in the contract maps to one of: merge, append, status, presence. Subscribe + sync to local state PER CHANNEL \u2014 subscribing alone never re-renders the DOM.\n\nExample (chat-interface \u2014 message=append, typing=presence):\n\n```tsx\nconst messages = useStream<MessageT>('message');\nconst typing = useStream<TypingT>('typing');\nconst [messageList, setMessageList] = useState(props.messages);\nconst [typingUsers, setTypingUsers] = useState<Set<string>>(new Set());\n\nuseEffect(() => setMessageList(props.messages), [props.messages]); // re-seed on ggui_amend repaint\n\nuseEffect(() => {\n if (!messages.latest) return;\n setMessageList((prev) => [...prev, messages.latest!]);\n}, [messages.latest]);\n\nuseEffect(() => {\n if (!typing.latest) return;\n setTypingUsers((prev) => {\n const next = new Set(prev);\n if (typing.latest!.active) next.add(typing.latest!.sender);\n else next.delete(typing.latest!.sender);\n return next;\n });\n}, [typing.latest]);\n```\n\nDo NOT treat all events the same. Do NOT skip the local-state sync \u2014 `useStream` alone won't trigger DOM updates of your derived view.",
214
214
  boilerplateMarker: [
215
215
  "",
216
216
  " // \u2500\u2500 Mixed stream handlers \u2500\u2500",
@@ -1 +1 @@
1
- {"version":3,"sources":["../../src/fragments/render.ts","../../src/fragments/state.ts","../../src/fragments/writes.ts","../../src/fragments/realtime.ts","../../src/fragments/fetch.ts","../../src/fragments/layout.ts","../../src/fragments/tooling.ts","../../src/fragments/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// packages/ui-gen/src/fragments/render.ts\n//\n// Render-axis fragments. One per RenderShape. Kept minimal — the stable\n// prefix already covers primitives + design tokens. These only add the\n// per-shape layout pattern the LLM needs to commit to up front.\n\nimport type { HarnessFragment } from \"./types.js\";\n\nexport const renderFragments: Record<string, HarnessFragment> = {\n static: {\n axis: \"render\",\n value: \"static\",\n cacheTier: \"axisDelta\",\n // Static = single entity detail. Stable prefix already covers the\n // obvious pattern (Card + Stack + Heading + fields). No prompt text\n // here keeps low-risk fixtures fast.\n },\n list: {\n axis: \"render\",\n value: \"list\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Render: list\\nVertical column of items. Each item is a <Card> with <Row> + <Text>. Use key={item.id} on each item. Do not paginate unless the contract has a fetch tool with cursor/offset.\",\n },\n grid: {\n axis: \"render\",\n value: \"grid\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Render: grid\\n2D tile layout. Use <CardGrid> or CSS `display: grid` with `grid-template-columns: repeat(N, 1fr)`. If items carry row/col fields, position each tile at (row, col). Do not scroll horizontally.\",\n },\n spatial: {\n axis: \"render\",\n value: \"spatial\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Render: spatial\\nGeo/coord-positioned items. Use absolute positioning inside a relative container, or a map primitive if available. Normalize coords to the container's bounds. Treat lat/lng as y/x, not strings.\",\n },\n timeline: {\n axis: \"render\",\n value: \"timeline\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Render: timeline\\nGroup items by day (or other temporal bucket). Render a sticky date header per group, then the grouped items. Sort newest-first unless the contract says otherwise. Memoize the grouping with useMemo.\",\n },\n chart: {\n axis: \"render\",\n value: \"chart\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Render: chart\\nNumeric → visual. Render with inline SVG (no chart library). Compute the viewBox from data min/max. Add axis labels and one value label on the latest/peak point. Keep it readable on a 400px container.\",\n },\n \"master-detail\": {\n axis: \"render\",\n value: \"master-detail\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Render: master-detail\\nSplit view: list on one side, detail panel on the other. Track `selectedId` in useState, default to the first item's id. Desktop = side-by-side; mobile/chat shell = stacked with back button.\",\n },\n};\n","// packages/ui-gen/src/fragments/state.ts\n//\n// State-axis fragments. This is the most error-prone axis — \"merge\" in\n// particular collapses into append-only bugs without explicit guidance.\n\nimport type { HarnessFragment } from \"./types.js\";\n\nexport const stateFragments: Record<string, HarnessFragment> = {\n none: {\n axis: \"state\",\n value: \"none\",\n cacheTier: \"axisDelta\",\n // Pure props → JSX. Stable prefix is enough — no useState call at all.\n },\n \"ui-affordance\": {\n axis: \"state\",\n value: \"ui-affordance\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## State: ui-affordance\\nLocal UI state for filter text / selected id / active tab / quantity. Use useState with a sensible default. Do NOT mirror props into state — read props directly and track only the affordance value.\",\n },\n merge: {\n axis: \"state\",\n value: \"merge\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## State: merge (live entity reconciliation)\\n1. Seed `const [items, setItems] = useState(props.items ?? [])`.\\n2. On stream updates: `setItems(prev => prev.map(it => it.id === event.id ? { ...it, ...event } : it))` — merge by id, do NOT append.\\n3. Memoize derived views (grouping/sorting/filtering) with useMemo.\\n4. Per-item actions pass `{ id, ... }` in the payload.\\n5. Never push the stream payload into an append-only list unless realtime=append.\",\n boilerplateMarker: [\n \"\",\n \" // ── Live entity state (merge-by-id) ──\",\n \" // useState(props.items ?? []); merge stream events by item.id; never append.\",\n \"\",\n ].join(\"\\n\"),\n },\n payload: {\n axis: \"state\",\n value: \"payload\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## State: payload (form assembly)\\nAccumulate form fields in a single state object. Validate on blur or on submit — do NOT block keystrokes. The submit action fires once, with the assembled payload.\",\n boilerplateMarker: [\n \"\",\n \" // ── Form payload ──\",\n \" // useState<FormData> seeded with defaults from props or empty; validate on submit.\",\n \"\",\n ].join(\"\\n\"),\n },\n draft: {\n axis: \"state\",\n value: \"draft\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## State: draft (in-place editor)\\nEdit one item at a time. Track `draft` as a separate useState (not mutating the source). Support cancel (discard draft) and save (commit draft → fire action).\",\n },\n};\n","// packages/ui-gen/src/fragments/writes.ts\n//\n// Writes-axis fragments. Covers both WriteShape (what) and WriteTrigger (how).\n\nimport type { HarnessFragment } from \"./types.js\";\n\nexport const writeFragments: Record<string, HarnessFragment> = {\n none: {\n axis: \"writes\",\n value: \"none\",\n cacheTier: \"axisDelta\",\n },\n commit: {\n axis: \"writes\",\n value: \"commit\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Writes: commit\\nOne action with a small payload (e.g., addToCart). Wire useAction on a single Button. Do not block the UI on completion — this is fire-and-forget.\",\n },\n \"multi-commit\": {\n axis: \"writes\",\n value: \"multi-commit\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Writes: multi-commit\\nMultiple unrelated single-commit actions (e.g., cancel / change destination / contact driver). Each action has its own Button. No shared payload assembly.\",\n },\n \"per-item\": {\n axis: \"writes\",\n value: \"per-item\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Writes: per-item\\nEach item in the entity list has its own action button(s). The payload must include the item's id. Example: `onClick={() => toggle({ id: item.id, completed: !item.completed })}`.\",\n boilerplateMarker: [\n \"\",\n \" // ── Per-item actions ──\",\n \" // Pass {id, ...} in the payload; wire to a Button inside each item's card.\",\n \"\",\n ].join(\"\\n\"),\n },\n submit: {\n axis: \"writes\",\n value: \"submit\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Writes: submit\\nTerminal form submit. One action fires with the full assembled payload at the end. Disable the submit button while pending and after success.\",\n },\n compose: {\n axis: \"writes\",\n value: \"compose\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Writes: compose (cross-entity action)\\nOne trigger references ids from two or more entity lists (e.g., {eventId, calendarId}). Track both selections in local state. Only enable the trigger once both are chosen.\",\n },\n};\n\n// WriteTrigger is a separate axis but lives in the same file for locality.\nexport const writeTriggerFragments: Record<string, HarnessFragment> = {\n click: {\n axis: \"writeTrigger\",\n value: \"click\",\n cacheTier: \"axisDelta\",\n // Default case — covered by stable prefix.\n },\n drag: {\n axis: \"writeTrigger\",\n value: \"drag\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Trigger: drag\\nDrag-drop interaction. Use HTML5 drag events (onDragStart, onDragOver, onDrop) on the item and drop zones. Track the dragged item id in useState. Set data-ggui-draggable on draggable elements. Do NOT pull in an external dnd library.\",\n boilerplateMarker: [\n \"\",\n \" // ── Drag state ──\",\n \" // Track the dragged item id; fire the action on drop with {id, destination}.\",\n \"\",\n ].join(\"\\n\"),\n },\n swipe: {\n axis: \"writeTrigger\",\n value: \"swipe\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Trigger: swipe\\nTouch gesture → one of N actions. Use onTouchStart/onTouchMove/onTouchEnd. Also expose fallback Buttons so desktop users can trigger the same actions by click.\",\n },\n keystroke: {\n axis: \"writeTrigger\",\n value: \"keystroke\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Trigger: keystroke\\nKeyboard shortcut. Attach onKeyDown to the container (with tabIndex={0}) or use a window listener inside useEffect with cleanup. Document visible shortcuts in the UI.\",\n },\n auto: {\n axis: \"writeTrigger\",\n value: \"auto\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Trigger: auto\\nEffect-driven. Use useEffect with a debounce (setTimeout + clearTimeout). Do not fire the action on every keystroke.\",\n },\n};\n","// packages/ui-gen/src/fragments/realtime.ts\n//\n// Realtime-axis fragments. Stream semantics drive whether you merge,\n// append, replace, or do nothing. \"mixed\" needs per-event kind dispatch.\n\nimport type { HarnessFragment } from \"./types.js\";\n\nexport const realtimeFragments: Record<string, HarnessFragment> = {\n none: {\n axis: \"realtime\",\n value: \"none\",\n cacheTier: \"axisDelta\",\n },\n merge: {\n axis: \"realtime\",\n value: \"merge\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Realtime: merge\\nStream events carry an id; merge into LOCAL STATE. Subscribing alone is not enough — the local list must update on each event for the DOM to re-render.\\n\\n```tsx\\nconst stream = useStream<UpdateT>('streamName');\\nconst [items, setItems] = useState(props.items);\\nuseEffect(() => {\\n if (!stream.latest) return;\\n setItems((prev) => prev.map((it) => it.id === stream.latest!.id ? { ...it, ...stream.latest } : it));\\n}, [stream.latest]);\\n// Render `items.map(...)` — NOT props.items, NOT stream.all.\\n```\\n\\nNever append — stream=merge means the entity already exists locally.\",\n },\n append: {\n axis: \"realtime\",\n value: \"append\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Realtime: append\\nStream events are new entities; APPEND to local state. Subscribing alone is not enough — the local list must update for the DOM to re-render.\\n\\n```tsx\\nconst stream = useStream<EventT>('streamName');\\nconst [items, setItems] = useState(props.items);\\nuseEffect(() => {\\n if (!stream.latest) return;\\n setItems((prev) => [...prev, stream.latest!]);\\n}, [stream.latest]);\\n// Render `items.map(...)` — NOT props.items, NOT stream.all (which accumulates only post-mount).\\n```\\n\\nUse head for newest-first, tail for chat-style. Cap list length if needed. Do not dedupe unless the contract guarantees at-most-once.\",\n },\n status: {\n axis: \"realtime\",\n value: \"status\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Realtime: status\\nStream replaces a singleton (e.g., marketStatus, rideStatus). `stream.latest` is the current value — bind directly into JSX, no local state needed for the singleton itself:\\n\\n```tsx\\nconst statusStream = useStream<StatusT>('streamName');\\nconst status = statusStream.latest;\\n// Render `<Badge>{status?.state ?? 'loading'}</Badge>`.\\n```\\n\\nIf you also need the timestamp of the last update, derive it from `useEffect` on `statusStream.latest`.\",\n },\n presence: {\n axis: \"realtime\",\n value: \"presence\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Realtime: presence\\nEphemeral per-user state (typing, cursors, online). Do NOT persist to a list. Sync into a `Set` and clear entries based on the active flag or a timeout:\\n\\n```tsx\\nconst stream = useStream<{ sender: string; active: boolean }>('streamName');\\nconst [active, setActive] = useState<Set<string>>(new Set());\\nuseEffect(() => {\\n if (!stream.latest) return;\\n setActive((prev) => {\\n const next = new Set(prev);\\n if (stream.latest!.active) next.add(stream.latest!.sender);\\n else next.delete(stream.latest!.sender);\\n return next;\\n });\\n}, [stream.latest]);\\n```\",\n },\n mixed: {\n axis: \"realtime\",\n value: \"mixed\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Realtime: mixed\\nMultiple stream channels with DIFFERENT semantics. Each `streamKinds` entry in the contract maps to one of: merge, append, status, presence. Subscribe + sync to local state PER CHANNEL — subscribing alone never re-renders the DOM.\\n\\nExample (chat-interface — message=append, typing=presence):\\n\\n```tsx\\nconst messages = useStream<MessageT>('message');\\nconst typing = useStream<TypingT>('typing');\\nconst [messageList, setMessageList] = useState(props.messages);\\nconst [typingUsers, setTypingUsers] = useState<Set<string>>(new Set());\\n\\nuseEffect(() => {\\n if (!messages.latest) return;\\n setMessageList((prev) => [...prev, messages.latest!]);\\n}, [messages.latest]);\\n\\nuseEffect(() => {\\n if (!typing.latest) return;\\n setTypingUsers((prev) => {\\n const next = new Set(prev);\\n if (typing.latest!.active) next.add(typing.latest!.sender);\\n else next.delete(typing.latest!.sender);\\n return next;\\n });\\n}, [typing.latest]);\\n```\\n\\nDo NOT treat all events the same. Do NOT skip the local-state sync — `useStream` alone won't trigger DOM updates of your derived view.\",\n boilerplateMarker: [\n \"\",\n \" // ── Mixed stream handlers ──\",\n \" // One useStream + useEffect+setState per channel — see realtime fragment.\",\n \"\",\n ].join(\"\\n\"),\n },\n};\n","// packages/ui-gen/src/fragments/fetch.ts\n//\n// Fetch-axis fragments. Covers how the component reacts to agent-fetched\n// data — agent-side tools live in `agentCapabilities.tools` (catalog\n// only); the component itself never invokes them directly.\n\nimport type { HarnessFragment } from \"./types.js\";\n\nexport const fetchFragments: Record<string, HarnessFragment> = {\n none: {\n axis: \"fetch\",\n value: \"none\",\n cacheTier: \"axisDelta\",\n },\n pagination: {\n axis: \"fetch\",\n value: \"pagination\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Fetch: pagination\\nTrack `cursor` (or offset/page) in useState. Expose a 'Load more' Button that calls the tool with the next cursor and appends results. Show a spinner while tool.isPending.\",\n },\n search: {\n axis: \"fetch\",\n value: \"search\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Fetch: search\\nDebounced query → tool call. Track `query` in useState; fire the tool inside useEffect after a 300ms debounce. Replace (don't append) results on each call. Guard against stale responses.\",\n },\n \"drill-down\": {\n axis: \"fetch\",\n value: \"drill-down\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Fetch: drill-down\\nOn item click, call the tool with the item's id and show the detail. Track `selectedId` and `detail` separately; render a loading placeholder while tool.isPending.\",\n },\n refresh: {\n axis: \"fetch\",\n value: \"refresh\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Fetch: refresh\\nUser-triggered re-fetch (e.g., pull-to-refresh or a Refresh button). Re-call the tool with the same args; show a small spinner while pending.\",\n },\n};\n","// packages/ui-gen/src/fragments/layout.ts\n//\n// Layout-axis fragments. Structural composition — how the screen is split.\n\nimport type { HarnessFragment } from \"./types.js\";\n\nexport const layoutFragments: Record<string, HarnessFragment> = {\n single: {\n axis: \"layout\",\n value: \"single\",\n cacheTier: \"axisDelta\",\n },\n \"multi-step\": {\n axis: \"layout\",\n value: \"multi-step\",\n cacheTier: \"axisDelta\",\n // 2026-04-27: anti-pattern warnings added after 6× n=3 benches showed\n // survey-form + onboarding-wizard accounted for 9 of 11 probe FAILs\n // (\"Too many re-renders\" 6×, \"function is not iterable\" 2×, TDZ 1×).\n // All 3 classes trace to the same multi-step-specific anti-patterns\n // below; non-multi-step fixtures had 0 fails across the same benches.\n promptText:\n \"## Layout: multi-step\\n\" +\n \"Wizard. Track `step` in useState (0-indexed). Render \" +\n \"`<Stepper steps={STEPS} current={step} />` as the progress \" +\n \"indicator — do NOT hand-roll a '2 of 4' counter, and do NOT \" +\n \"reach for Tabs/Progress/Breadcrumb. Stepper is display-only: \" +\n \"your Next/Back buttons move `step`. Keep all step state in a \" +\n \"single payload object; do NOT reset previous-step data on \" +\n \"navigation.\\n\\n\" +\n \"### Anti-patterns that crash at runtime — DO NOT do these:\\n\\n\" +\n \"1. **No setState in render body** — causes 'Too many re-renders'.\\n\" +\n \"```tsx\\n\" +\n \"// ❌ WRONG — fires every render, infinite loop\\n\" +\n \"const Form = () => {\\n\" +\n \" const [errors, setErrors] = useState({});\\n\" +\n \" setErrors(validate(values)); // setState in render → loop\\n\" +\n \"};\\n\" +\n \"// ✅ RIGHT — derive with useMemo (no state)\\n\" +\n \"const Form = () => {\\n\" +\n \" const errors = useMemo(() => validate(values), [values]);\\n\" +\n \"};\\n\" +\n \"```\\n\\n\" +\n \"2. **No setState in useEffect with state-derived deps** — also \" +\n \"causes 'Too many re-renders'.\\n\" +\n \"```tsx\\n\" +\n \"// ❌ WRONG — payload identity changes → setIsValid → re-render → ...\\n\" +\n \"useEffect(() => { setIsValid(check(payload)); }, [payload]);\\n\" +\n \"// ✅ RIGHT — useMemo, no setState\\n\" +\n \"const isValid = useMemo(() => check(payload), [payload]);\\n\" +\n \"```\\n\\n\" +\n \"3. **Declare `steps` array as a top-level `const`, NEVER state.** \" +\n \"Default arrays (`fields`, `options`) the same way. Iterating over \" +\n \"an undefined or unstable array throws 'function is not iterable'.\\n\" +\n \"```tsx\\n\" +\n \"// ❌ WRONG — useState('steps array...') is a string, .map crashes\\n\" +\n \"const [steps] = useState('Welcome,Profile,Done');\\n\" +\n \"// ✅ RIGHT — top-level const\\n\" +\n \"const STEPS = ['Welcome', 'Profile', 'Done'] as const;\\n\" +\n \"```\\n\\n\" +\n \"4. **Hooks/`const` declarations come BEFORE any useEffect/\" +\n \"useMemo/useCallback that reads them.** Otherwise: TDZ 'Cannot \" +\n \"access X before initialization'.\\n\" +\n \"```tsx\\n\" +\n \"// ❌ WRONG — useEffect reads `total` before its `const` line\\n\" +\n \"useEffect(() => log(total), [total]);\\n\" +\n \"const total = items.reduce(sum, 0);\\n\" +\n \"// ✅ RIGHT — declare first, read after\\n\" +\n \"const total = items.reduce(sum, 0);\\n\" +\n \"useEffect(() => log(total), [total]);\\n\" +\n \"```\",\n },\n \"master-detail\": {\n axis: \"layout\",\n value: \"master-detail\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Layout: master-detail\\nSplit container: master list on the left/top, detail panel on the right/bottom. Track `selectedId` and show a 'select an item' placeholder in the detail panel when null.\",\n },\n overlay: {\n axis: \"layout\",\n value: \"overlay\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Layout: overlay\\nFloating controls layered on top of content. Use position: absolute with explicit insets. Ensure the overlay does not block critical content (leave a safe area).\",\n },\n modal: {\n axis: \"layout\",\n value: \"modal\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Layout: modal\\nUse the <Modal> primitive. Track `isOpen` in useState. Provide a clear close affordance (X button + clicking backdrop). Trap focus inside the modal.\",\n },\n};\n","// packages/ui-gen/src/fragments/tooling.ts\n//\n// tooling-axis fragments. The contract carries two tooling surfaces:\n//\n// - `agentCapabilities.tools` — catalog of agent-invoked tools the\n// contract references via `actionSpec[*].nextStep` and\n// `streamSpec[*].source.tool`. NO component-side hook surface.\n// - `clientCapabilities.gadgets` — catalog of browser-capability\n// gadget hooks (geolocation, mic, camera, clipboard, file picker,\n// notifications). The component imports + calls these hooks\n// from `@ggui-ai/gadgets` (or the third-party package named in\n// `entry.package`).\n//\n// The `wired` / `client` / `both` axis values map onto these surfaces\n// by what the contract carries; the prompt text steers the LLM toward\n// the correct patterns.\n\nimport type { HarnessFragment } from \"./types.js\";\n\nexport const toolingFragments: Record<string, HarnessFragment> = {\n none: {\n axis: \"tooling\",\n value: \"none\",\n cacheTier: \"axisDelta\",\n // Nothing to say — contract carries no agentCapabilities.tools /\n // clientCapabilities.gadgets.\n },\n wired: {\n axis: \"tooling\",\n value: \"wired\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Tooling: agent-side tools (catalog only)\\nThe contract declares `agentCapabilities.tools[X]` for tools the AGENT invokes — the component never calls these directly. References surface via `actionSpec[Y].nextStep = 'X'` (the agent's next-turn hint forwarded on action events) and `streamSpec[Z].source.tool = 'X'` (the runtime polls / subscribes the tool, deliveries land on the stream channel). Author UI controls fire actions via `useAction`; data feeds appear via `useStream`. The `useWiredTool` hook from before 2026-05-11 is RETIRED.\",\n },\n client: {\n axis: \"tooling\",\n value: \"client\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Tooling: gadgets (browser-capability hooks)\\nThe contract declares `clientCapabilities.gadgets[X]` for browser-capability gadget hooks the UI mounts. The boilerplate has pre-emitted a direct import per gadget package — `import { useFoo, useBar } from '<package>';` — above a `// DO NOT EDIT` banner (STDLIB hooks come from `@ggui-ai/gadgets`, third-party hooks from the package on `clientCapabilities.gadgets[*].package`). Call the imported hook inside the component (`const loc = useGeolocation();`) and trigger via `.start()` from a UI control. Read `.value` / `.status` to render. If the agent needs to observe the result, thread `.value` into a `contextSpec` slot or an `actionSpec` payload. Library hooks are UI-owned lifecycle; the agent never invokes them. **KEEP every pre-emitted gadget import — do NOT delete it and do NOT change its package**; self_check rejects the code with `gadget_preservation:<hook>` if a gadget import disappears. The pre-2026-05-11 `useClientTool(name, handler)` shape is RETIRED, as is the `@ggui-ai/client-tools` package name (renamed to `@ggui-ai/gadgets`).\",\n },\n both: {\n axis: \"tooling\",\n value: \"both\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Tooling: both surfaces present\\nThe contract declares BOTH `agentCapabilities.tools` (agent-invoked catalog; referenced via `actionSpec.nextStep` / `streamSpec.source.tool` — NO component hook) AND `clientCapabilities.gadgets` (browser-capability gadget hooks the component direct-imports — the boilerplate pre-emits `import { useCamera } from '@ggui-ai/gadgets';` (STDLIB) or `import { useFoo } from '<package>';` (third-party) above a `// DO NOT EDIT` banner). Don't conflate: agentCapabilities entries are catalog declarations the agent uses, NOT component hooks. clientCapabilities entries DO emit component-side hook calls (e.g., `const cam = useCamera();`).\",\n },\n};\n","// packages/ui-gen/src/fragments/index.ts\n//\n// Public barrel for the `@ggui-ai/ui-gen/fragments` subpath.\n//\n// Stable public surface (what external consumers should build against):\n// - `FRAGMENT_REGISTRY` — the axis-keyed registry\n// - `lookupFragment` — the one-call lookup helper\n// - fragment types — HarnessFragment, ComposedHarness, CacheTier, AxisKey\n//\n// Per-axis fragment records (renderFragments, stateFragments, etc.) are\n// intentionally NOT re-exported here — consumers who genuinely need one\n// can read `FRAGMENT_REGISTRY.render` / `.state` / etc. Keeping the public\n// surface to a single entry point preserves our freedom to reshape the\n// per-axis files without breaking semver.\n\nimport type { AxisKey, HarnessFragment } from \"./types.js\";\nimport { renderFragments } from \"./render.js\";\nimport { stateFragments } from \"./state.js\";\nimport { writeFragments, writeTriggerFragments } from \"./writes.js\";\nimport { realtimeFragments } from \"./realtime.js\";\nimport { fetchFragments } from \"./fetch.js\";\nimport { layoutFragments } from \"./layout.js\";\nimport { toolingFragments } from \"./tooling.js\";\n\nexport type { HarnessFragment, ComposedHarness, CacheTier, AxisKey } from \"./types.js\";\n\nexport const FRAGMENT_REGISTRY: Record<AxisKey, Record<string, HarnessFragment>> = {\n render: renderFragments,\n state: stateFragments,\n writes: writeFragments,\n writeTrigger: writeTriggerFragments,\n realtime: realtimeFragments,\n fetch: fetchFragments,\n layout: layoutFragments,\n tooling: toolingFragments,\n};\n\nexport function lookupFragment(axis: AxisKey, value: string): HarnessFragment | undefined {\n return FRAGMENT_REGISTRY[axis]?.[value];\n}\n"]}
1
+ {"version":3,"sources":["../../src/fragments/render.ts","../../src/fragments/state.ts","../../src/fragments/writes.ts","../../src/fragments/realtime.ts","../../src/fragments/fetch.ts","../../src/fragments/layout.ts","../../src/fragments/tooling.ts","../../src/fragments/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["// packages/ui-gen/src/fragments/render.ts\n//\n// Render-axis fragments. One per RenderShape. Kept minimal — the stable\n// prefix already covers primitives + design tokens. These only add the\n// per-shape layout pattern the LLM needs to commit to up front.\n\nimport type { HarnessFragment } from \"./types.js\";\n\nexport const renderFragments: Record<string, HarnessFragment> = {\n static: {\n axis: \"render\",\n value: \"static\",\n cacheTier: \"axisDelta\",\n // Static = single entity detail. Stable prefix already covers the\n // obvious pattern (Card + Stack + Heading + fields). No prompt text\n // here keeps low-risk fixtures fast.\n },\n list: {\n axis: \"render\",\n value: \"list\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Render: list\\nVertical column of items. Each item is a <Card> with <Row> + <Text>. Use key={item.id} on each item. Do not paginate unless the contract has a fetch tool with cursor/offset.\",\n },\n grid: {\n axis: \"render\",\n value: \"grid\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Render: grid\\n2D tile layout. Use <CardGrid> or CSS `display: grid` with `grid-template-columns: repeat(N, 1fr)`. If items carry row/col fields, position each tile at (row, col). Do not scroll horizontally.\",\n },\n spatial: {\n axis: \"render\",\n value: \"spatial\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Render: spatial\\nGeo/coord-positioned items. Use absolute positioning inside a relative container, or a map primitive if available. Normalize coords to the container's bounds. Treat lat/lng as y/x, not strings.\",\n },\n timeline: {\n axis: \"render\",\n value: \"timeline\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Render: timeline\\nGroup items by day (or other temporal bucket). Render a sticky date header per group, then the grouped items. Sort newest-first unless the contract says otherwise. Memoize the grouping with useMemo.\",\n },\n chart: {\n axis: \"render\",\n value: \"chart\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Render: chart\\nNumeric → visual. Render with inline SVG (no chart library). Compute the viewBox from data min/max. Add axis labels and one value label on the latest/peak point. Keep it readable on a 400px container.\",\n },\n \"master-detail\": {\n axis: \"render\",\n value: \"master-detail\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Render: master-detail\\nSplit view: list on one side, detail panel on the other. Track `selectedId` in useState, default to the first item's id. Desktop = side-by-side; mobile/chat shell = stacked with back button.\",\n },\n};\n","// packages/ui-gen/src/fragments/state.ts\n//\n// State-axis fragments. This is the most error-prone axis — \"merge\" in\n// particular collapses into append-only bugs without explicit guidance.\n\nimport type { HarnessFragment } from \"./types.js\";\n\nexport const stateFragments: Record<string, HarnessFragment> = {\n none: {\n axis: \"state\",\n value: \"none\",\n cacheTier: \"axisDelta\",\n // Pure props → JSX. Stable prefix is enough — no useState call at all.\n },\n \"ui-affordance\": {\n axis: \"state\",\n value: \"ui-affordance\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## State: ui-affordance\\nLocal UI state for filter text / selected id / active tab / quantity. Use useState with a sensible default. Do NOT mirror props into state — read props directly and track only the affordance value.\",\n },\n merge: {\n axis: \"state\",\n value: \"merge\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## State: merge (live entity reconciliation)\\n1. Seed `const [items, setItems] = useState(props.items ?? [])` AND re-seed on prop change: `useEffect(() => setItems(props.items ?? []), [props.items])` — a `ggui_amend` repaint must move the list.\\n2. On stream updates: `setItems(prev => prev.map(it => it.id === event.id ? { ...it, ...event } : it))` — merge by id, do NOT append.\\n3. Memoize derived views (grouping/sorting/filtering) with useMemo.\\n4. Per-item actions pass `{ id, ... }` in the payload.\\n5. Never push the stream payload into an append-only list unless realtime=append.\",\n boilerplateMarker: [\n \"\",\n \" // ── Live entity state (merge-by-id) ──\",\n \" // useState(props.items ?? []); re-seed via useEffect on [props.items]; merge stream events by item.id; never append.\",\n \"\",\n ].join(\"\\n\"),\n },\n payload: {\n axis: \"state\",\n value: \"payload\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## State: payload (form assembly)\\nAccumulate form fields in a single state object. Validate on blur or on submit — do NOT block keystrokes. The submit action fires once, with the assembled payload.\",\n boilerplateMarker: [\n \"\",\n \" // ── Form payload ──\",\n \" // useState<FormData> seeded with defaults from props or empty; validate on submit.\",\n \"\",\n ].join(\"\\n\"),\n },\n draft: {\n axis: \"state\",\n value: \"draft\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## State: draft (in-place editor)\\nEdit one item at a time. Track `draft` as a separate useState (not mutating the source). Support cancel (discard draft) and save (commit draft → fire action).\",\n },\n};\n","// packages/ui-gen/src/fragments/writes.ts\n//\n// Writes-axis fragments. Covers both WriteShape (what) and WriteTrigger (how).\n\nimport type { HarnessFragment } from \"./types.js\";\n\nexport const writeFragments: Record<string, HarnessFragment> = {\n none: {\n axis: \"writes\",\n value: \"none\",\n cacheTier: \"axisDelta\",\n },\n commit: {\n axis: \"writes\",\n value: \"commit\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Writes: commit\\nOne action with a small payload (e.g., addToCart). Wire useAction on a single Button. Do not block the UI on completion — this is fire-and-forget.\",\n },\n \"multi-commit\": {\n axis: \"writes\",\n value: \"multi-commit\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Writes: multi-commit\\nMultiple unrelated single-commit actions (e.g., cancel / change destination / contact driver). Each action has its own Button. No shared payload assembly.\",\n },\n \"per-item\": {\n axis: \"writes\",\n value: \"per-item\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Writes: per-item\\nEach item in the entity list has its own action button(s). The payload must include the item's id. Example: `onClick={() => toggle({ id: item.id, completed: !item.completed })}`.\",\n boilerplateMarker: [\n \"\",\n \" // ── Per-item actions ──\",\n \" // Pass {id, ...} in the payload; wire to a Button inside each item's card.\",\n \"\",\n ].join(\"\\n\"),\n },\n submit: {\n axis: \"writes\",\n value: \"submit\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Writes: submit\\nTerminal form submit. One action fires with the full assembled payload at the end. Disable the submit button while pending and after success.\",\n },\n compose: {\n axis: \"writes\",\n value: \"compose\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Writes: compose (cross-entity action)\\nOne trigger references ids from two or more entity lists (e.g., {eventId, calendarId}). Track both selections in local state. Only enable the trigger once both are chosen.\",\n },\n};\n\n// WriteTrigger is a separate axis but lives in the same file for locality.\nexport const writeTriggerFragments: Record<string, HarnessFragment> = {\n click: {\n axis: \"writeTrigger\",\n value: \"click\",\n cacheTier: \"axisDelta\",\n // Default case — covered by stable prefix.\n },\n drag: {\n axis: \"writeTrigger\",\n value: \"drag\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Trigger: drag\\nDrag-drop interaction. Use HTML5 drag events (onDragStart, onDragOver, onDrop) on the item and drop zones. Track the dragged item id in useState. Set data-ggui-draggable on draggable elements. Do NOT pull in an external dnd library.\",\n boilerplateMarker: [\n \"\",\n \" // ── Drag state ──\",\n \" // Track the dragged item id; fire the action on drop with {id, destination}.\",\n \"\",\n ].join(\"\\n\"),\n },\n swipe: {\n axis: \"writeTrigger\",\n value: \"swipe\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Trigger: swipe\\nTouch gesture → one of N actions. Use onTouchStart/onTouchMove/onTouchEnd. Also expose fallback Buttons so desktop users can trigger the same actions by click.\",\n },\n keystroke: {\n axis: \"writeTrigger\",\n value: \"keystroke\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Trigger: keystroke\\nKeyboard shortcut. Attach onKeyDown to the container (with tabIndex={0}) or use a window listener inside useEffect with cleanup. Document visible shortcuts in the UI.\",\n },\n auto: {\n axis: \"writeTrigger\",\n value: \"auto\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Trigger: auto\\nEffect-driven. Use useEffect with a debounce (setTimeout + clearTimeout). Do not fire the action on every keystroke.\",\n },\n};\n","// packages/ui-gen/src/fragments/realtime.ts\n//\n// Realtime-axis fragments. Stream semantics drive whether you merge,\n// append, replace, or do nothing. \"mixed\" needs per-event kind dispatch.\n\nimport type { HarnessFragment } from \"./types.js\";\n\nexport const realtimeFragments: Record<string, HarnessFragment> = {\n none: {\n axis: \"realtime\",\n value: \"none\",\n cacheTier: \"axisDelta\",\n },\n merge: {\n axis: \"realtime\",\n value: \"merge\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Realtime: merge\\nStream events carry an id; merge into LOCAL STATE. Subscribing alone is not enough — the local list must update on each event for the DOM to re-render.\\n\\n```tsx\\nconst stream = useStream<UpdateT>('streamName');\\nconst [items, setItems] = useState(props.items);\\nuseEffect(() => setItems(props.items), [props.items]); // re-seed on ggui_amend repaint — props stay the source of truth\\nuseEffect(() => {\\n if (!stream.latest) return;\\n setItems((prev) => prev.map((it) => it.id === stream.latest!.id ? { ...it, ...stream.latest } : it));\\n}, [stream.latest]);\\n// Render `items.map(...)` — NOT props.items, NOT stream.all.\\n```\\n\\nNever append — stream=merge means the entity already exists locally.\",\n },\n append: {\n axis: \"realtime\",\n value: \"append\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Realtime: append\\nStream events are new entities; APPEND to local state. Subscribing alone is not enough — the local list must update for the DOM to re-render.\\n\\n```tsx\\nconst stream = useStream<EventT>('streamName');\\nconst [items, setItems] = useState(props.items);\\nuseEffect(() => setItems(props.items), [props.items]); // re-seed on ggui_amend repaint — props stay the source of truth\\nuseEffect(() => {\\n if (!stream.latest) return;\\n setItems((prev) => [...prev, stream.latest!]);\\n}, [stream.latest]);\\n// Render `items.map(...)` — NOT props.items, NOT stream.all (which accumulates only post-mount).\\n```\\n\\nUse head for newest-first, tail for chat-style. Cap list length if needed. Do not dedupe unless the contract guarantees at-most-once.\",\n },\n status: {\n axis: \"realtime\",\n value: \"status\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Realtime: status\\nStream replaces a singleton (e.g., marketStatus, rideStatus). `stream.latest` is the current value — bind directly into JSX, no local state needed for the singleton itself:\\n\\n```tsx\\nconst statusStream = useStream<StatusT>('streamName');\\nconst status = statusStream.latest;\\n// Render `<Badge>{status?.state ?? 'loading'}</Badge>`.\\n```\\n\\nIf you also need the timestamp of the last update, derive it from `useEffect` on `statusStream.latest`.\",\n },\n presence: {\n axis: \"realtime\",\n value: \"presence\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Realtime: presence\\nEphemeral per-user state (typing, cursors, online). Do NOT persist to a list. Sync into a `Set` and clear entries based on the active flag or a timeout:\\n\\n```tsx\\nconst stream = useStream<{ sender: string; active: boolean }>('streamName');\\nconst [active, setActive] = useState<Set<string>>(new Set());\\nuseEffect(() => {\\n if (!stream.latest) return;\\n setActive((prev) => {\\n const next = new Set(prev);\\n if (stream.latest!.active) next.add(stream.latest!.sender);\\n else next.delete(stream.latest!.sender);\\n return next;\\n });\\n}, [stream.latest]);\\n```\",\n },\n mixed: {\n axis: \"realtime\",\n value: \"mixed\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Realtime: mixed\\nMultiple stream channels with DIFFERENT semantics. Each `streamKinds` entry in the contract maps to one of: merge, append, status, presence. Subscribe + sync to local state PER CHANNEL — subscribing alone never re-renders the DOM.\\n\\nExample (chat-interface — message=append, typing=presence):\\n\\n```tsx\\nconst messages = useStream<MessageT>('message');\\nconst typing = useStream<TypingT>('typing');\\nconst [messageList, setMessageList] = useState(props.messages);\\nconst [typingUsers, setTypingUsers] = useState<Set<string>>(new Set());\\n\\nuseEffect(() => setMessageList(props.messages), [props.messages]); // re-seed on ggui_amend repaint\\n\\nuseEffect(() => {\\n if (!messages.latest) return;\\n setMessageList((prev) => [...prev, messages.latest!]);\\n}, [messages.latest]);\\n\\nuseEffect(() => {\\n if (!typing.latest) return;\\n setTypingUsers((prev) => {\\n const next = new Set(prev);\\n if (typing.latest!.active) next.add(typing.latest!.sender);\\n else next.delete(typing.latest!.sender);\\n return next;\\n });\\n}, [typing.latest]);\\n```\\n\\nDo NOT treat all events the same. Do NOT skip the local-state sync — `useStream` alone won't trigger DOM updates of your derived view.\",\n boilerplateMarker: [\n \"\",\n \" // ── Mixed stream handlers ──\",\n \" // One useStream + useEffect+setState per channel — see realtime fragment.\",\n \"\",\n ].join(\"\\n\"),\n },\n};\n","// packages/ui-gen/src/fragments/fetch.ts\n//\n// Fetch-axis fragments. Covers how the component reacts to agent-fetched\n// data — agent-side tools live in `agentCapabilities.tools` (catalog\n// only); the component itself never invokes them directly.\n\nimport type { HarnessFragment } from \"./types.js\";\n\nexport const fetchFragments: Record<string, HarnessFragment> = {\n none: {\n axis: \"fetch\",\n value: \"none\",\n cacheTier: \"axisDelta\",\n },\n pagination: {\n axis: \"fetch\",\n value: \"pagination\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Fetch: pagination\\nTrack `cursor` (or offset/page) in useState. Expose a 'Load more' Button that calls the tool with the next cursor and appends results. Show a spinner while tool.isPending.\",\n },\n search: {\n axis: \"fetch\",\n value: \"search\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Fetch: search\\nDebounced query → tool call. Track `query` in useState; fire the tool inside useEffect after a 300ms debounce. Replace (don't append) results on each call. Guard against stale responses.\",\n },\n \"drill-down\": {\n axis: \"fetch\",\n value: \"drill-down\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Fetch: drill-down\\nOn item click, call the tool with the item's id and show the detail. Track `selectedId` and `detail` separately; render a loading placeholder while tool.isPending.\",\n },\n refresh: {\n axis: \"fetch\",\n value: \"refresh\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Fetch: refresh\\nUser-triggered re-fetch (e.g., pull-to-refresh or a Refresh button). Re-call the tool with the same args; show a small spinner while pending.\",\n },\n};\n","// packages/ui-gen/src/fragments/layout.ts\n//\n// Layout-axis fragments. Structural composition — how the screen is split.\n\nimport type { HarnessFragment } from \"./types.js\";\n\nexport const layoutFragments: Record<string, HarnessFragment> = {\n single: {\n axis: \"layout\",\n value: \"single\",\n cacheTier: \"axisDelta\",\n },\n \"multi-step\": {\n axis: \"layout\",\n value: \"multi-step\",\n cacheTier: \"axisDelta\",\n // 2026-04-27: anti-pattern warnings added after 6× n=3 benches showed\n // survey-form + onboarding-wizard accounted for 9 of 11 probe FAILs\n // (\"Too many re-renders\" 6×, \"function is not iterable\" 2×, TDZ 1×).\n // All 3 classes trace to the same multi-step-specific anti-patterns\n // below; non-multi-step fixtures had 0 fails across the same benches.\n promptText:\n \"## Layout: multi-step\\n\" +\n \"Wizard. Track `step` in useState (0-indexed). Render \" +\n \"`<Stepper steps={STEPS} current={step} />` as the progress \" +\n \"indicator — do NOT hand-roll a '2 of 4' counter, and do NOT \" +\n \"reach for Tabs/Progress/Breadcrumb. Stepper is display-only: \" +\n \"your Next/Back buttons move `step`. Keep all step state in a \" +\n \"single payload object; do NOT reset previous-step data on \" +\n \"navigation.\\n\\n\" +\n \"### Anti-patterns that crash at runtime — DO NOT do these:\\n\\n\" +\n \"1. **No setState in render body** — causes 'Too many re-renders'.\\n\" +\n \"```tsx\\n\" +\n \"// ❌ WRONG — fires every render, infinite loop\\n\" +\n \"const Form = () => {\\n\" +\n \" const [errors, setErrors] = useState({});\\n\" +\n \" setErrors(validate(values)); // setState in render → loop\\n\" +\n \"};\\n\" +\n \"// ✅ RIGHT — derive with useMemo (no state)\\n\" +\n \"const Form = () => {\\n\" +\n \" const errors = useMemo(() => validate(values), [values]);\\n\" +\n \"};\\n\" +\n \"```\\n\\n\" +\n \"2. **No setState in useEffect with state-derived deps** — also \" +\n \"causes 'Too many re-renders'.\\n\" +\n \"```tsx\\n\" +\n \"// ❌ WRONG — payload identity changes → setIsValid → re-render → ...\\n\" +\n \"useEffect(() => { setIsValid(check(payload)); }, [payload]);\\n\" +\n \"// ✅ RIGHT — useMemo, no setState\\n\" +\n \"const isValid = useMemo(() => check(payload), [payload]);\\n\" +\n \"```\\n\\n\" +\n \"3. **Declare `steps` array as a top-level `const`, NEVER state.** \" +\n \"Default arrays (`fields`, `options`) the same way. Iterating over \" +\n \"an undefined or unstable array throws 'function is not iterable'.\\n\" +\n \"```tsx\\n\" +\n \"// ❌ WRONG — useState('steps array...') is a string, .map crashes\\n\" +\n \"const [steps] = useState('Welcome,Profile,Done');\\n\" +\n \"// ✅ RIGHT — top-level const\\n\" +\n \"const STEPS = ['Welcome', 'Profile', 'Done'] as const;\\n\" +\n \"```\\n\\n\" +\n \"4. **Hooks/`const` declarations come BEFORE any useEffect/\" +\n \"useMemo/useCallback that reads them.** Otherwise: TDZ 'Cannot \" +\n \"access X before initialization'.\\n\" +\n \"```tsx\\n\" +\n \"// ❌ WRONG — useEffect reads `total` before its `const` line\\n\" +\n \"useEffect(() => log(total), [total]);\\n\" +\n \"const total = items.reduce(sum, 0);\\n\" +\n \"// ✅ RIGHT — declare first, read after\\n\" +\n \"const total = items.reduce(sum, 0);\\n\" +\n \"useEffect(() => log(total), [total]);\\n\" +\n \"```\",\n },\n \"master-detail\": {\n axis: \"layout\",\n value: \"master-detail\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Layout: master-detail\\nSplit container: master list on the left/top, detail panel on the right/bottom. Track `selectedId` and show a 'select an item' placeholder in the detail panel when null.\",\n },\n overlay: {\n axis: \"layout\",\n value: \"overlay\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Layout: overlay\\nFloating controls layered on top of content. Use position: absolute with explicit insets. Ensure the overlay does not block critical content (leave a safe area).\",\n },\n modal: {\n axis: \"layout\",\n value: \"modal\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Layout: modal\\nUse the <Modal> primitive. Track `isOpen` in useState. Provide a clear close affordance (X button + clicking backdrop). Trap focus inside the modal.\",\n },\n};\n","// packages/ui-gen/src/fragments/tooling.ts\n//\n// tooling-axis fragments. The contract carries two tooling surfaces:\n//\n// - `agentCapabilities.tools` — catalog of agent-invoked tools the\n// contract references via `actionSpec[*].nextStep` and\n// `streamSpec[*].source.tool`. NO component-side hook surface.\n// - `clientCapabilities.gadgets` — catalog of browser-capability\n// gadget hooks (geolocation, mic, camera, clipboard, file picker,\n// notifications). The component imports + calls these hooks\n// from `@ggui-ai/gadgets` (or the third-party package named in\n// `entry.package`).\n//\n// The `wired` / `client` / `both` axis values map onto these surfaces\n// by what the contract carries; the prompt text steers the LLM toward\n// the correct patterns.\n\nimport type { HarnessFragment } from \"./types.js\";\n\nexport const toolingFragments: Record<string, HarnessFragment> = {\n none: {\n axis: \"tooling\",\n value: \"none\",\n cacheTier: \"axisDelta\",\n // Nothing to say — contract carries no agentCapabilities.tools /\n // clientCapabilities.gadgets.\n },\n wired: {\n axis: \"tooling\",\n value: \"wired\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Tooling: agent-side tools (catalog only)\\nThe contract declares `agentCapabilities.tools[X]` for tools the AGENT invokes — the component never calls these directly. References surface via `actionSpec[Y].nextStep = 'X'` (the agent's next-turn hint forwarded on action events) and `streamSpec[Z].source.tool = 'X'` (the runtime polls / subscribes the tool, deliveries land on the stream channel). Author UI controls fire actions via `useAction`; data feeds appear via `useStream`. The `useWiredTool` hook from before 2026-05-11 is RETIRED.\",\n },\n client: {\n axis: \"tooling\",\n value: \"client\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Tooling: gadgets (browser-capability hooks)\\nThe contract declares `clientCapabilities.gadgets[X]` for browser-capability gadget hooks the UI mounts. The boilerplate has pre-emitted a direct import per gadget package — `import { useFoo, useBar } from '<package>';` — above a `// DO NOT EDIT` banner (STDLIB hooks come from `@ggui-ai/gadgets`, third-party hooks from the package on `clientCapabilities.gadgets[*].package`). Call the imported hook inside the component (`const loc = useGeolocation();`) and trigger via `.start()` from a UI control. Read `.value` / `.status` to render. If the agent needs to observe the result, thread `.value` into a `contextSpec` slot or an `actionSpec` payload. Library hooks are UI-owned lifecycle; the agent never invokes them. **KEEP every pre-emitted gadget import — do NOT delete it and do NOT change its package**; self_check rejects the code with `gadget_preservation:<hook>` if a gadget import disappears. The pre-2026-05-11 `useClientTool(name, handler)` shape is RETIRED, as is the `@ggui-ai/client-tools` package name (renamed to `@ggui-ai/gadgets`).\",\n },\n both: {\n axis: \"tooling\",\n value: \"both\",\n cacheTier: \"axisDelta\",\n promptText:\n \"## Tooling: both surfaces present\\nThe contract declares BOTH `agentCapabilities.tools` (agent-invoked catalog; referenced via `actionSpec.nextStep` / `streamSpec.source.tool` — NO component hook) AND `clientCapabilities.gadgets` (browser-capability gadget hooks the component direct-imports — the boilerplate pre-emits `import { useCamera } from '@ggui-ai/gadgets';` (STDLIB) or `import { useFoo } from '<package>';` (third-party) above a `// DO NOT EDIT` banner). Don't conflate: agentCapabilities entries are catalog declarations the agent uses, NOT component hooks. clientCapabilities entries DO emit component-side hook calls (e.g., `const cam = useCamera();`).\",\n },\n};\n","// packages/ui-gen/src/fragments/index.ts\n//\n// Public barrel for the `@ggui-ai/ui-gen/fragments` subpath.\n//\n// Stable public surface (what external consumers should build against):\n// - `FRAGMENT_REGISTRY` — the axis-keyed registry\n// - `lookupFragment` — the one-call lookup helper\n// - fragment types — HarnessFragment, ComposedHarness, CacheTier, AxisKey\n//\n// Per-axis fragment records (renderFragments, stateFragments, etc.) are\n// intentionally NOT re-exported here — consumers who genuinely need one\n// can read `FRAGMENT_REGISTRY.render` / `.state` / etc. Keeping the public\n// surface to a single entry point preserves our freedom to reshape the\n// per-axis files without breaking semver.\n\nimport type { AxisKey, HarnessFragment } from \"./types.js\";\nimport { renderFragments } from \"./render.js\";\nimport { stateFragments } from \"./state.js\";\nimport { writeFragments, writeTriggerFragments } from \"./writes.js\";\nimport { realtimeFragments } from \"./realtime.js\";\nimport { fetchFragments } from \"./fetch.js\";\nimport { layoutFragments } from \"./layout.js\";\nimport { toolingFragments } from \"./tooling.js\";\n\nexport type { HarnessFragment, ComposedHarness, CacheTier, AxisKey } from \"./types.js\";\n\nexport const FRAGMENT_REGISTRY: Record<AxisKey, Record<string, HarnessFragment>> = {\n render: renderFragments,\n state: stateFragments,\n writes: writeFragments,\n writeTrigger: writeTriggerFragments,\n realtime: realtimeFragments,\n fetch: fetchFragments,\n layout: layoutFragments,\n tooling: toolingFragments,\n};\n\nexport function lookupFragment(axis: AxisKey, value: string): HarnessFragment | undefined {\n return FRAGMENT_REGISTRY[axis]?.[value];\n}\n"]}
@@ -1,138 +1,17 @@
1
1
  import { Harness } from './types-public.js';
2
2
  export { CheckLeg, CheckResult, CreateHarnessInput, DefaultTaskRunner, HarnessConstructionContext, HarnessId, HarnessMeta, HarnessName, HarnessOverrides, HarnessRevision, HowLeg, IterationRecord, LLMEvaluator, PatchFn, Phase, PhaseRunResult, PlannerDecision, PlannerFn, ProcessLeg, PromptBuilder, RetryPolicy, RunCheckInput, RunHarnessInput, RunHarnessReason, RunHarnessResult, RunWorkflowInput, Task, TaskContext, TaskRunner, TierCheck, WhatLeg, Workflow, WorkflowRunResult, createHarness, runCheck, runHarness, runWorkflow } from './types-public.js';
3
3
  export { applyLineRanges, defaultApplyPatch } from '../patch.js';
4
- import { JsonObject } from '@ggui-ai/protocol';
5
- import { LLMToolDef } from '../llm.js';
4
+ export { A as AgentConfig, L as LLMResponse, c as callLLM, a as createAgent } from '../llm-router-1TyI0_TV.js';
6
5
  export { WORKFLOWS, WorkflowId, pickWorkflow } from '../workflows.js';
7
6
  export { LlmTraceEvent, LlmTraceKind, LlmTraceProvider, LlmTraceSink, getLlmTraceSink, setLlmTraceSink } from './llm-trace-sink.js';
8
7
  export { ValidatorTraceEvent, ValidatorTraceSink, getValidatorTraceSink, setValidatorTraceSink } from './validator-trace-sink.js';
9
8
  export { ProcessMode } from '../policy.js';
10
9
  export { computeHarnessId, computeHarnessName, hashClassification } from '../hash.js';
10
+ import '@ggui-ai/protocol';
11
11
  import '../axes-CzLEMDeB.js';
12
12
  import '../types-public-C36k1q6s.js';
13
13
  import '../types-BOvHNG7K.js';
14
-
15
- interface AgentConfig {
16
- provider: 'anthropic' | 'openai' | 'google' | 'openrouter';
17
- model: string;
18
- /**
19
- * Sampling temperature. When defined it is threaded into the provider
20
- * request; when undefined the provider default is used (unchanged
21
- * behavior for all existing callers).
22
- */
23
- temperature?: number;
24
- }
25
- interface LLMResponse {
26
- text: string;
27
- inputTokens: number;
28
- outputTokens: number;
29
- }
30
- interface LLMTool {
31
- name: string;
32
- description: string;
33
- parameters: JsonObject;
34
- handler: (args: JsonObject) => Promise<{
35
- content: Array<{
36
- text: string;
37
- }>;
38
- isError?: boolean;
39
- }>;
40
- }
41
- interface LLMWithToolsResponse {
42
- text: string;
43
- inputTokens: number;
44
- outputTokens: number;
45
- turnsUsed: number;
46
- }
47
- interface LLMToolCall {
48
- /** Provider-specific call ID (for sendToolResult) */
49
- id?: string;
50
- name: string;
51
- input: JsonObject;
52
- }
53
- interface LLMToolCallResponse {
54
- toolCalls: LLMToolCall[];
55
- inputTokens: number;
56
- outputTokens: number;
57
- /**
58
- * Tokens read from / written to the prompt cache, when the provider
59
- * reports them. Optional because not every provider exposes
60
- * prompt-cache accounting — absent means "unreported", never zero.
61
- */
62
- cacheReadTokens?: number;
63
- cacheCreationTokens?: number;
64
- }
65
- /** Result of executing a tool — passed to sendToolResult to close the API contract. */
66
- interface LLMToolResult {
67
- /** Tool call ID from the response (for providers that need it) */
68
- callId?: string;
69
- /** Tool name */
70
- name: string;
71
- /** Text result of executing the tool */
72
- result: string;
73
- /** Whether the tool execution failed */
74
- isError?: boolean;
75
- }
76
- declare abstract class LLMAgent {
77
- abstract readonly provider: AgentConfig['provider'];
78
- private client;
79
- protected lastSessionId: string | undefined;
80
- protected abstract resolveModel(model: string): string;
81
- protected abstract createClient(): Promise<unknown>;
82
- protected getClient<T>(): Promise<T>;
83
- /** Text-only call — no tools */
84
- abstract callText(model: string, systemPrompt: string, userPrompt: string, maxTokens?: number, temperature?: number): Promise<LLMResponse>;
85
- /**
86
- * Single-turn function calling — returns tool calls without executing them.
87
- * Each provider uses its native function/tool calling:
88
- * - Anthropic: tool_use blocks
89
- * - OpenAI: function_call output items
90
- * - Google: functionCall parts
91
- * SDK handles JSON escaping — safe for code, diffs, and other content.
92
- */
93
- abstract callTools(model: string, systemPrompt: string, userPrompt: string, tools: LLMToolDef[], toolChoice?: 'required' | 'auto',
94
- /**
95
- * Optional scoped fallback tools. If the primary tools fail with a
96
- * transport-class error (e.g. `malformed_tool_call` on Gemini after
97
- * retry exhaustion), the provider may retry once with these narrower
98
- * tools before throwing. Universal signal — not provider-gated.
99
- */
100
- scopedTools?: LLMToolDef[]): Promise<LLMToolCallResponse>;
101
- /** Multi-turn agentic loop — executes tools internally */
102
- abstract callWithTools(model: string, systemPrompt: string, userPrompt: string, tools: LLMTool[], maxTurns: number): Promise<LLMWithToolsResponse>;
103
- /**
104
- * Pre-warm cache for repeated callTools() calls with the same system prompt + tools.
105
- * Override in providers that support server-side context caching (e.g., Google).
106
- * No-op by default (Anthropic/OpenAI handle caching automatically per-request).
107
- */
108
- warmCache(_model: string, _systemPrompt: string, _tools: LLMToolDef[], _toolChoice?: 'required' | 'auto'): Promise<void>;
109
- /** Cleanup any cached resources. Call after generation completes. No-op by default. */
110
- cleanup(): Promise<void>;
111
- /** Reset session state between independent generation runs. */
112
- resetSession(): void;
113
- /**
114
- * Send tool execution results back to the provider to close the API contract.
115
- * Call this after executing tools from callTools() and before the next callTools().
116
- *
117
- * For providers with server-side state (Google, OpenAI), this sends the
118
- * function results so the next callTools() can chain properly.
119
- * For stateless providers (Anthropic), this is a no-op.
120
- *
121
- * Override in providers that need it.
122
- */
123
- sendToolResult(_results: LLMToolResult[]): Promise<void>;
124
- /**
125
- * Execute an API call. No retry — if it fails, it fails.
126
- * Logs the error with provider context and re-throws.
127
- */
128
- protected apiCall<T>(fn: () => Promise<T>): Promise<T>;
129
- }
130
- /**
131
- * Create a fresh agent instance for the given provider.
132
- * Each call returns a new instance — no shared state between callers.
133
- */
134
- declare function createAgent(provider: AgentConfig['provider']): LLMAgent;
135
- declare function callLLM(config: AgentConfig, systemPrompt: string, userPrompt: string, maxTokens?: number): Promise<LLMResponse>;
14
+ import '../llm.js';
136
15
 
137
16
  /**
138
17
  * Fingerprint a materialized Harness. `h.id` is already a deterministic
@@ -142,4 +21,4 @@ declare function callLLM(config: AgentConfig, systemPrompt: string, userPrompt:
142
21
  */
143
22
  declare function hashHarness(h: Harness): string;
144
23
 
145
- export { type AgentConfig, Harness, type LLMResponse, callLLM, createAgent, hashHarness };
24
+ export { Harness, hashHarness };
@@ -517,11 +517,11 @@ var stateFragments = {
517
517
  axis: "state",
518
518
  value: "merge",
519
519
  cacheTier: "axisDelta",
520
- promptText: "## State: merge (live entity reconciliation)\n1. Seed `const [items, setItems] = useState(props.items ?? [])`.\n2. On stream updates: `setItems(prev => prev.map(it => it.id === event.id ? { ...it, ...event } : it))` \u2014 merge by id, do NOT append.\n3. Memoize derived views (grouping/sorting/filtering) with useMemo.\n4. Per-item actions pass `{ id, ... }` in the payload.\n5. Never push the stream payload into an append-only list unless realtime=append.",
520
+ promptText: "## State: merge (live entity reconciliation)\n1. Seed `const [items, setItems] = useState(props.items ?? [])` AND re-seed on prop change: `useEffect(() => setItems(props.items ?? []), [props.items])` \u2014 a `ggui_amend` repaint must move the list.\n2. On stream updates: `setItems(prev => prev.map(it => it.id === event.id ? { ...it, ...event } : it))` \u2014 merge by id, do NOT append.\n3. Memoize derived views (grouping/sorting/filtering) with useMemo.\n4. Per-item actions pass `{ id, ... }` in the payload.\n5. Never push the stream payload into an append-only list unless realtime=append.",
521
521
  boilerplateMarker: [
522
522
  "",
523
523
  " // \u2500\u2500 Live entity state (merge-by-id) \u2500\u2500",
524
- " // useState(props.items ?? []); merge stream events by item.id; never append.",
524
+ " // useState(props.items ?? []); re-seed via useEffect on [props.items]; merge stream events by item.id; never append.",
525
525
  ""
526
526
  ].join("\n")
527
527
  },
@@ -639,13 +639,13 @@ var realtimeFragments = {
639
639
  axis: "realtime",
640
640
  value: "merge",
641
641
  cacheTier: "axisDelta",
642
- promptText: "## Realtime: merge\nStream events carry an id; merge into LOCAL STATE. Subscribing alone is not enough \u2014 the local list must update on each event for the DOM to re-render.\n\n```tsx\nconst stream = useStream<UpdateT>('streamName');\nconst [items, setItems] = useState(props.items);\nuseEffect(() => {\n if (!stream.latest) return;\n setItems((prev) => prev.map((it) => it.id === stream.latest!.id ? { ...it, ...stream.latest } : it));\n}, [stream.latest]);\n// Render `items.map(...)` \u2014 NOT props.items, NOT stream.all.\n```\n\nNever append \u2014 stream=merge means the entity already exists locally."
642
+ promptText: "## Realtime: merge\nStream events carry an id; merge into LOCAL STATE. Subscribing alone is not enough \u2014 the local list must update on each event for the DOM to re-render.\n\n```tsx\nconst stream = useStream<UpdateT>('streamName');\nconst [items, setItems] = useState(props.items);\nuseEffect(() => setItems(props.items), [props.items]); // re-seed on ggui_amend repaint \u2014 props stay the source of truth\nuseEffect(() => {\n if (!stream.latest) return;\n setItems((prev) => prev.map((it) => it.id === stream.latest!.id ? { ...it, ...stream.latest } : it));\n}, [stream.latest]);\n// Render `items.map(...)` \u2014 NOT props.items, NOT stream.all.\n```\n\nNever append \u2014 stream=merge means the entity already exists locally."
643
643
  },
644
644
  append: {
645
645
  axis: "realtime",
646
646
  value: "append",
647
647
  cacheTier: "axisDelta",
648
- promptText: "## Realtime: append\nStream events are new entities; APPEND to local state. Subscribing alone is not enough \u2014 the local list must update for the DOM to re-render.\n\n```tsx\nconst stream = useStream<EventT>('streamName');\nconst [items, setItems] = useState(props.items);\nuseEffect(() => {\n if (!stream.latest) return;\n setItems((prev) => [...prev, stream.latest!]);\n}, [stream.latest]);\n// Render `items.map(...)` \u2014 NOT props.items, NOT stream.all (which accumulates only post-mount).\n```\n\nUse head for newest-first, tail for chat-style. Cap list length if needed. Do not dedupe unless the contract guarantees at-most-once."
648
+ promptText: "## Realtime: append\nStream events are new entities; APPEND to local state. Subscribing alone is not enough \u2014 the local list must update for the DOM to re-render.\n\n```tsx\nconst stream = useStream<EventT>('streamName');\nconst [items, setItems] = useState(props.items);\nuseEffect(() => setItems(props.items), [props.items]); // re-seed on ggui_amend repaint \u2014 props stay the source of truth\nuseEffect(() => {\n if (!stream.latest) return;\n setItems((prev) => [...prev, stream.latest!]);\n}, [stream.latest]);\n// Render `items.map(...)` \u2014 NOT props.items, NOT stream.all (which accumulates only post-mount).\n```\n\nUse head for newest-first, tail for chat-style. Cap list length if needed. Do not dedupe unless the contract guarantees at-most-once."
649
649
  },
650
650
  status: {
651
651
  axis: "realtime",
@@ -663,7 +663,7 @@ var realtimeFragments = {
663
663
  axis: "realtime",
664
664
  value: "mixed",
665
665
  cacheTier: "axisDelta",
666
- promptText: "## Realtime: mixed\nMultiple stream channels with DIFFERENT semantics. Each `streamKinds` entry in the contract maps to one of: merge, append, status, presence. Subscribe + sync to local state PER CHANNEL \u2014 subscribing alone never re-renders the DOM.\n\nExample (chat-interface \u2014 message=append, typing=presence):\n\n```tsx\nconst messages = useStream<MessageT>('message');\nconst typing = useStream<TypingT>('typing');\nconst [messageList, setMessageList] = useState(props.messages);\nconst [typingUsers, setTypingUsers] = useState<Set<string>>(new Set());\n\nuseEffect(() => {\n if (!messages.latest) return;\n setMessageList((prev) => [...prev, messages.latest!]);\n}, [messages.latest]);\n\nuseEffect(() => {\n if (!typing.latest) return;\n setTypingUsers((prev) => {\n const next = new Set(prev);\n if (typing.latest!.active) next.add(typing.latest!.sender);\n else next.delete(typing.latest!.sender);\n return next;\n });\n}, [typing.latest]);\n```\n\nDo NOT treat all events the same. Do NOT skip the local-state sync \u2014 `useStream` alone won't trigger DOM updates of your derived view.",
666
+ promptText: "## Realtime: mixed\nMultiple stream channels with DIFFERENT semantics. Each `streamKinds` entry in the contract maps to one of: merge, append, status, presence. Subscribe + sync to local state PER CHANNEL \u2014 subscribing alone never re-renders the DOM.\n\nExample (chat-interface \u2014 message=append, typing=presence):\n\n```tsx\nconst messages = useStream<MessageT>('message');\nconst typing = useStream<TypingT>('typing');\nconst [messageList, setMessageList] = useState(props.messages);\nconst [typingUsers, setTypingUsers] = useState<Set<string>>(new Set());\n\nuseEffect(() => setMessageList(props.messages), [props.messages]); // re-seed on ggui_amend repaint\n\nuseEffect(() => {\n if (!messages.latest) return;\n setMessageList((prev) => [...prev, messages.latest!]);\n}, [messages.latest]);\n\nuseEffect(() => {\n if (!typing.latest) return;\n setTypingUsers((prev) => {\n const next = new Set(prev);\n if (typing.latest!.active) next.add(typing.latest!.sender);\n else next.delete(typing.latest!.sender);\n return next;\n });\n}, [typing.latest]);\n```\n\nDo NOT treat all events the same. Do NOT skip the local-state sync \u2014 `useStream` alone won't trigger DOM updates of your derived view.",
667
667
  boilerplateMarker: [
668
668
  "",
669
669
  " // \u2500\u2500 Mixed stream handlers \u2500\u2500",
@@ -1785,13 +1785,14 @@ A \`DataContract\` declares everything a GguiSession exchanges with the outside
1785
1785
 
1786
1786
  ## Defensive coding for absent / late-arriving data
1787
1787
 
1788
- Props arrive via \`ggui_update\` and may be partial on first render. 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.**
1788
+ 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.**
1789
1789
 
1790
1790
  - **Array iteration**: always default to \`[]\` before \`.map\`/\`.filter\`/\`.length\`. Use \`(props.items ?? []).map(...)\` not \`props.items.map(...)\`. Same for stream.all, stream.latest, etc.
1791
1791
  - **Object access**: optional-chain through nested fields. \`props.user?.name ?? 'Anonymous'\` not \`props.user.name\`.
1792
1792
  - **Number ops**: default before arithmetic. \`(props.count ?? 0) + 1\` not \`props.count + 1\`.
1793
1793
  - **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 \u2014 guard before reading \`.foo\`.
1794
1794
  - **Stream reconciliation**: when a stream event carries an \`action\` discriminant (e.g. \`create | move | edit | delete\`), the channel is a CRUD feed \u2014 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 \u2014 and handle an event whose id is not yet present (a \`create\` for an unknown item) by inserting it, not ignoring it.
1795
+ - **Props re-sync on repaint**: props the agent owns are the source of truth for display \u2014 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 \u2014 \`useEffect(() => setCount(props.count ?? 0), [props.count])\` \u2014 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.
1795
1796
  - **Loading state**: while data is still absent, render \`<Skeleton>\` placeholders \u2014 never a blank screen. \`<Skeleton variant="text" />\` for a text line, \`variant="circle"\` for an avatar slot, default \`rect\` for a block.
1796
1797
  - **Empty state**: when a list or results array is empty, render \`<EmptyState title="\u2026" description="\u2026" />\` \u2014 a region that renders nothing when empty looks broken to the user.
1797
1798
 
@@ -1919,7 +1920,7 @@ Every value change is mirrored to the host LLM's context automatically (debounce
1919
1920
 
1920
1921
  **When NOT to use it.** Local UI state the contract did NOT declare \u2014 \`isDropdownOpen\`, hover flags, animation phase, ephemeral toggles. For those, use a plain \`useState\` directly. The runtime ignores undeclared state.
1921
1922
 
1922
- **\`contextSpec\` direction is one-way: client \u2192 agent.** The agent uses \`propsSpec\` (via \`ggui_update\`) and \`streamSpec\` (via the live channel) to push state TO the client. Don't try to write to the agent via \`contextSpec\` \u2014 there is no return path.
1923
+ **\`contextSpec\` direction is one-way: client \u2192 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\` \u2014 there is no return path.
1923
1924
 
1924
1925
  **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.
1925
1926
 
@@ -2822,6 +2823,16 @@ var LLMAgent = class {
2822
2823
  // Enables turn 2+ to skip re-sending system prompt + history.
2823
2824
  // Call resetSession() between independent generation runs.
2824
2825
  lastSessionId;
2826
+ /**
2827
+ * Explicit per-call routing override — see `AgentConfig.routeOverride`.
2828
+ * Absent when constructed via the bare `new XAgent()` form (every
2829
+ * existing call site), so `createClient()`/`resolveModel()` fall
2830
+ * back to `process.env` exactly as before.
2831
+ */
2832
+ routeOverride;
2833
+ constructor(routeOverride) {
2834
+ this.routeOverride = routeOverride;
2835
+ }
2825
2836
  async getClient() {
2826
2837
  if (!this.client) {
2827
2838
  this.client = await this.createClient();
@@ -2872,19 +2883,21 @@ var LLMAgent = class {
2872
2883
  var AnthropicAgent = class extends LLMAgent {
2873
2884
  provider = "anthropic";
2874
2885
  resolveModel(model) {
2875
- if (process.env.CLAUDE_CODE_USE_BEDROCK === "1") {
2886
+ const useBedrock = this.routeOverride?.useBedrock ?? process.env.CLAUDE_CODE_USE_BEDROCK === "1";
2887
+ if (useBedrock) {
2876
2888
  return getBedrockModelId(model);
2877
2889
  }
2878
2890
  return model.startsWith("anthropic/") ? model.slice("anthropic/".length) : model;
2879
2891
  }
2880
2892
  async createClient() {
2881
- if (process.env.CLAUDE_CODE_USE_BEDROCK === "1") {
2893
+ const useBedrock = this.routeOverride?.useBedrock ?? process.env.CLAUDE_CODE_USE_BEDROCK === "1";
2894
+ if (useBedrock) {
2882
2895
  const { AnthropicBedrock } = await import('@anthropic-ai/bedrock-sdk');
2883
2896
  return new AnthropicBedrock({
2884
2897
  awsRegion: process.env.AWS_REGION ?? process.env.AWS_DEFAULT_REGION ?? "us-east-1"
2885
2898
  });
2886
2899
  }
2887
- return createAnthropicClient(process.env.ANTHROPIC_API_KEY);
2900
+ return createAnthropicClient(this.routeOverride?.apiKey ?? process.env.ANTHROPIC_API_KEY);
2888
2901
  }
2889
2902
  async callText(model, systemPrompt, userPrompt, maxTokens, temperature) {
2890
2903
  const client = await this.getClient();
@@ -3155,7 +3168,7 @@ var OpenAIAgent = class extends LLMAgent {
3155
3168
  }
3156
3169
  async createClient() {
3157
3170
  const { default: OpenAISDK } = await import('openai');
3158
- return new OpenAISDK({ apiKey: process.env.OPENAI_API_KEY });
3171
+ return new OpenAISDK({ apiKey: this.routeOverride?.apiKey ?? process.env.OPENAI_API_KEY });
3159
3172
  }
3160
3173
  async callText(model, systemPrompt, userPrompt, maxTokens, temperature) {
3161
3174
  const client = await this.getClient();
@@ -3326,7 +3339,7 @@ var GoogleAgent = class extends LLMAgent {
3326
3339
  async createClient() {
3327
3340
  const { GoogleGenAI: GoogleGenAISDK } = await import('@google/genai');
3328
3341
  return new GoogleGenAISDK({
3329
- apiKey: process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY,
3342
+ apiKey: this.routeOverride?.apiKey ?? (process.env.GEMINI_API_KEY || process.env.GOOGLE_API_KEY),
3330
3343
  httpOptions: { timeout: 3e5 }
3331
3344
  // 5 min — Pro models can be slow
3332
3345
  });
@@ -3553,7 +3566,7 @@ var OpenRouterAgent = class extends LLMAgent {
3553
3566
  async createClient() {
3554
3567
  const { OpenRouterClient: OpenRouterClient2 } = await Promise.resolve().then(() => (init_client(), client_exports));
3555
3568
  return new OpenRouterClient2({
3556
- apiKey: process.env.OPENROUTER_API_KEY
3569
+ apiKey: this.routeOverride?.apiKey ?? process.env.OPENROUTER_API_KEY
3557
3570
  });
3558
3571
  }
3559
3572
  async callText(model, systemPrompt, userPrompt, maxTokens, temperature) {
@@ -3679,21 +3692,23 @@ var OpenRouterAgent = class extends LLMAgent {
3679
3692
  return { text: finalText, inputTokens: totalInput, outputTokens: totalOutput, turnsUsed };
3680
3693
  }
3681
3694
  };
3682
- function createAgent(provider) {
3695
+ function createAgent(providerOrConfig) {
3696
+ const provider = typeof providerOrConfig === "string" ? providerOrConfig : providerOrConfig.provider;
3697
+ const routeOverride = typeof providerOrConfig === "string" ? void 0 : providerOrConfig.routeOverride;
3683
3698
  switch (provider) {
3684
3699
  case "anthropic":
3685
- return new AnthropicAgent();
3700
+ return new AnthropicAgent(routeOverride);
3686
3701
  case "openai":
3687
- return new OpenAIAgent();
3702
+ return new OpenAIAgent(routeOverride);
3688
3703
  case "google":
3689
- return new GoogleAgent();
3704
+ return new GoogleAgent(routeOverride);
3690
3705
  case "openrouter":
3691
- return new OpenRouterAgent();
3706
+ return new OpenRouterAgent(routeOverride);
3692
3707
  }
3693
3708
  }
3694
3709
  async function callLLM(config, systemPrompt, userPrompt, maxTokens) {
3695
3710
  const start = Date.now();
3696
- const agent = createAgent(config.provider);
3711
+ const agent = createAgent(config);
3697
3712
  const result = await agent.callText(
3698
3713
  config.model,
3699
3714
  systemPrompt,