@ggui-ai/ui-gen 0.8.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.
- package/dist/adapters/generation-dispatch.d.ts +10 -0
- package/dist/adapters/generation-dispatch.js +172 -1392
- package/dist/adapters/generation-dispatch.js.map +1 -1
- package/dist/advanced/index.d.ts +1 -1
- package/dist/advanced/index.js +191 -1403
- package/dist/advanced/index.js.map +1 -1
- package/dist/boilerplate.js +3 -2
- package/dist/boilerplate.js.map +1 -1
- package/dist/compose.js +5 -5
- package/dist/compose.js.map +1 -1
- package/dist/{create-ui-generator-GX7tvbyH.d.ts → create-ui-generator-B9kV4U9h.d.ts} +13 -0
- package/dist/evaluation/axis-checks/index.js +32 -1
- package/dist/evaluation/axis-checks/index.js.map +1 -1
- package/dist/evaluation/axis-checks/registry.js +32 -1
- package/dist/evaluation/axis-checks/registry.js.map +1 -1
- package/dist/evaluation/index.js +57 -12
- package/dist/evaluation/index.js.map +1 -1
- package/dist/fragments/index.js +5 -5
- package/dist/fragments/index.js.map +1 -1
- package/dist/harness/index.d.ts +4 -125
- package/dist/harness/index.js +34 -19
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/runtime.d.ts +9 -0
- package/dist/harness/runtime.js +3 -2
- package/dist/harness/runtime.js.map +1 -1
- package/dist/harness/types-public.js +8 -7
- package/dist/harness/types-public.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +191 -1403
- package/dist/index.js.map +1 -1
- package/dist/llm-router-1TyI0_TV.d.ts +156 -0
- package/dist/providers/index.d.ts +35 -93
- package/dist/providers/index.js +13 -8
- package/dist/providers/index.js.map +1 -1
- package/dist/validation/index.js +2 -2
- package/dist/validation/index.js.map +1 -1
- package/package.json +18 -18
- package/src/adapters/generation-dispatch.ts +19 -2
- package/src/boilerplate/system-prompt.ts +3 -2
- package/src/create-ui-generator.ts +44 -11
- package/src/evaluation/axis-checks/checks/state-merge.ts +1 -1
- package/src/evaluation/axis-checks/checks/universal.ts +39 -0
- package/src/evaluation/llm-evaluator.ts +13 -3
- package/src/evaluation/visual-evaluator.ts +5 -1
- package/src/fragments/realtime.ts +3 -3
- package/src/fragments/state.ts +2 -2
- package/src/harness/coding/init-session.ts +4 -1
- package/src/harness/coding/run-eval-round.ts +6 -0
- package/src/harness/llm-router.ts +59 -14
- package/src/harness/runtime.ts +8 -0
- package/src/providers/bedrock.ts +84 -22
- package/src/validation/ui-compiler.ts +2 -2
package/dist/compose.js
CHANGED
|
@@ -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 ?? [])
|
|
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",
|
package/dist/compose.js.map
CHANGED
|
@@ -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","../src/compose.ts"],"names":[],"mappings":"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","file":"compose.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","// packages/ui-gen/src/compose.ts\n//\n// Materializes a Classification into prompt sections + boilerplate markers.\n// Sits between the stable prefix (design system docs, pitfalls) and the\n// volatile feedback (eval issues) in the system prompt's cache tiering.\n//\n// Consumes the `./classifier` and `./fragments` subpaths — it's the\n// bridge between an axis vector and the prompt/boilerplate the LLM\n// actually sees.\n\nimport type { Classification } from \"./classifier/axes.js\";\nimport {\n lookupFragment,\n type AxisKey,\n type ComposedHarness,\n type HarnessFragment,\n} from \"./fragments/index.js\";\n\n// Axis processing order inside axisDelta. Puts the most behaviourally\n// consequential axes first so the LLM reads them before scanning details.\nconst AXIS_ORDER: AxisKey[] = [\n \"render\",\n \"layout\",\n \"state\",\n \"writes\",\n \"writeTrigger\",\n \"realtime\",\n \"fetch\",\n \"tooling\",\n];\n\nexport function compose(classification: Classification): ComposedHarness {\n const v = classification.vector;\n const matched: HarnessFragment[] = [];\n\n for (const axis of AXIS_ORDER) {\n const value = v[axis] as string | undefined;\n if (!value) continue;\n const frag = lookupFragment(axis, value);\n if (frag) matched.push(frag);\n }\n\n const promptParts = matched\n .filter((f) => f.promptText && f.promptText.trim().length > 0)\n .map((f) => f.promptText!.trim());\n\n const boilerplateParts = matched\n .filter((f) => f.boilerplateMarker && f.boilerplateMarker.trim().length > 0)\n .map((f) => f.boilerplateMarker!);\n\n return {\n promptText: promptParts.join(\"\\n\\n\"),\n boilerplateSections: boilerplateParts.join(\"\"),\n fragments: matched,\n };\n}\n\nexport type { ComposedHarness };\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","../src/compose.ts"],"names":[],"mappings":"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","file":"compose.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","// packages/ui-gen/src/compose.ts\n//\n// Materializes a Classification into prompt sections + boilerplate markers.\n// Sits between the stable prefix (design system docs, pitfalls) and the\n// volatile feedback (eval issues) in the system prompt's cache tiering.\n//\n// Consumes the `./classifier` and `./fragments` subpaths — it's the\n// bridge between an axis vector and the prompt/boilerplate the LLM\n// actually sees.\n\nimport type { Classification } from \"./classifier/axes.js\";\nimport {\n lookupFragment,\n type AxisKey,\n type ComposedHarness,\n type HarnessFragment,\n} from \"./fragments/index.js\";\n\n// Axis processing order inside axisDelta. Puts the most behaviourally\n// consequential axes first so the LLM reads them before scanning details.\nconst AXIS_ORDER: AxisKey[] = [\n \"render\",\n \"layout\",\n \"state\",\n \"writes\",\n \"writeTrigger\",\n \"realtime\",\n \"fetch\",\n \"tooling\",\n];\n\nexport function compose(classification: Classification): ComposedHarness {\n const v = classification.vector;\n const matched: HarnessFragment[] = [];\n\n for (const axis of AXIS_ORDER) {\n const value = v[axis] as string | undefined;\n if (!value) continue;\n const frag = lookupFragment(axis, value);\n if (frag) matched.push(frag);\n }\n\n const promptParts = matched\n .filter((f) => f.promptText && f.promptText.trim().length > 0)\n .map((f) => f.promptText!.trim());\n\n const boilerplateParts = matched\n .filter((f) => f.boilerplateMarker && f.boilerplateMarker.trim().length > 0)\n .map((f) => f.boilerplateMarker!);\n\n return {\n promptText: promptParts.join(\"\\n\\n\"),\n boilerplateSections: boilerplateParts.join(\"\"),\n fragments: matched,\n };\n}\n\nexport type { ComposedHarness };\n"]}
|
|
@@ -74,6 +74,19 @@ interface CreateUiGeneratorOptions {
|
|
|
74
74
|
* `appGadgets` themselves; the legacy handler path stays unchanged.
|
|
75
75
|
*/
|
|
76
76
|
readonly gadgetCatalog?: GadgetCatalogAdapter;
|
|
77
|
+
/**
|
|
78
|
+
* When `true`, `generate()` never mutates `process.env` — the
|
|
79
|
+
* resolved provider route is threaded structurally into the
|
|
80
|
+
* dispatch call instead of being written to and restored from the
|
|
81
|
+
* process environment. Use this when running concurrent
|
|
82
|
+
* generations in one process (the default backup/mutate/restore
|
|
83
|
+
* dance is NOT concurrency-safe: two simultaneous calls race on
|
|
84
|
+
* the shared `process.env`).
|
|
85
|
+
*
|
|
86
|
+
* Default `false` — preserves the original env-mutation behavior
|
|
87
|
+
* for callers that don't need concurrent generations in-process.
|
|
88
|
+
*/
|
|
89
|
+
readonly disableEnvMutation?: boolean;
|
|
77
90
|
}
|
|
78
91
|
declare function createUiGenerator(options?: CreateUiGeneratorOptions): UiGenerator;
|
|
79
92
|
declare function extractComponentCode(raw: string): string;
|
|
@@ -235,6 +235,31 @@ function runNoPhantomUseState(input) {
|
|
|
235
235
|
}
|
|
236
236
|
return issues;
|
|
237
237
|
}
|
|
238
|
+
function runPropSeedNoResync(input) {
|
|
239
|
+
if (input.compiledCode === null) return [];
|
|
240
|
+
const src = input.sourceCode;
|
|
241
|
+
const issues = [];
|
|
242
|
+
const re = /const\s*\[\s*(\w+)\s*,\s*(\w+)\s*\]\s*=\s*useState(?:<[^>]*>)?\s*\(\s*props\??\.(\w+)/g;
|
|
243
|
+
for (const m of src.matchAll(re)) {
|
|
244
|
+
const [full, stateVar, setter, propName] = m;
|
|
245
|
+
const idx = m.index ?? 0;
|
|
246
|
+
const after = src.slice(idx + full.length);
|
|
247
|
+
if (!new RegExp(`\\b${setter}\\s*\\(`).test(after)) continue;
|
|
248
|
+
const resync = new RegExp(
|
|
249
|
+
`\\[[^\\[\\]]*props\\??\\.${propName}\\b[^\\[\\]]*\\]`
|
|
250
|
+
);
|
|
251
|
+
if (resync.test(src)) continue;
|
|
252
|
+
issues.push(
|
|
253
|
+
mkIssue(
|
|
254
|
+
"universal.prop_seed_no_resync",
|
|
255
|
+
`useState(props.${propName}) seeds "${stateVar}" and mutates it, but no effect depends on [props.${propName}] \u2014 a ggui_amend repaint pushes new props that never reach this display (#480).`,
|
|
256
|
+
`Add \`useEffect(() => ${setter}(props.${propName}), [props.${propName}]);\` so agent repaints re-seed the state, or derive the display directly from props.${propName}.`,
|
|
257
|
+
"warn"
|
|
258
|
+
)
|
|
259
|
+
);
|
|
260
|
+
}
|
|
261
|
+
return issues;
|
|
262
|
+
}
|
|
238
263
|
var UNIVERSAL_CHECKS = [
|
|
239
264
|
{
|
|
240
265
|
id: "universal.prop_coverage",
|
|
@@ -253,6 +278,12 @@ var UNIVERSAL_CHECKS = [
|
|
|
253
278
|
axis: "render",
|
|
254
279
|
values: ALL_RENDER_VALUES,
|
|
255
280
|
run: runNoPhantomUseState
|
|
281
|
+
},
|
|
282
|
+
{
|
|
283
|
+
id: "universal.prop_seed_no_resync",
|
|
284
|
+
axis: "render",
|
|
285
|
+
values: ALL_RENDER_VALUES,
|
|
286
|
+
run: runPropSeedNoResync
|
|
256
287
|
}
|
|
257
288
|
];
|
|
258
289
|
|
|
@@ -272,7 +303,7 @@ function runStateSeededFromProps(input) {
|
|
|
272
303
|
mkIssue(
|
|
273
304
|
"state.merge.seeded_from_props",
|
|
274
305
|
`Entity collection "${e.name}" is not seeded from props \u2014 no useState initializer reads props.${e.name}.`,
|
|
275
|
-
`Add \`const [${e.name}, set${cap(e.name)}] = useState(props.${e.name});\` so stream/action updates
|
|
306
|
+
`Add \`const [${e.name}, set${cap(e.name)}] = useState(props.${e.name});\` paired with \`useEffect(() => set${cap(e.name)}(props.${e.name}), [props.${e.name}]);\` so stream/action updates merge into live state AND agent repaints (ggui_amend) re-seed it (#480).`
|
|
276
307
|
)
|
|
277
308
|
);
|
|
278
309
|
}
|