@ggui-ai/ui-gen 0.16.0 → 0.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/adapters/generation-dispatch.d.ts +6 -3
- package/dist/adapters/generation-dispatch.js +4944 -4239
- package/dist/adapters/generation-dispatch.js.map +1 -1
- package/dist/adapters/index.js +8 -5
- package/dist/adapters/index.js.map +1 -1
- package/dist/advanced/index.d.ts +12 -8
- package/dist/advanced/index.js +4462 -3726
- package/dist/advanced/index.js.map +1 -1
- package/dist/blueprint-validator.js +8 -12
- package/dist/blueprint-validator.js.map +1 -1
- package/dist/boilerplate.d.ts +5 -2
- package/dist/boilerplate.js +95 -12
- package/dist/boilerplate.js.map +1 -1
- package/dist/check/index.d.ts +2 -2
- package/dist/check/index.js +2 -0
- package/dist/check/index.js.map +1 -1
- package/dist/compose.js +1 -1
- package/dist/compose.js.map +1 -1
- package/dist/{create-ui-generator-CCIeQaEe.d.ts → create-ui-generator-C8Ki9Wz1.d.ts} +18 -2
- package/dist/{design-mode-BHEE7flj.d.ts → design-mode-DLufhrNp.d.ts} +0 -6
- package/dist/{dispatch-BA5dJcg-.d.ts → dispatch-D2htzi9h.d.ts} +2 -2
- package/dist/evaluation/axis-checks/index.d.ts +4 -4
- package/dist/evaluation/axis-checks/index.js +297 -13
- package/dist/evaluation/axis-checks/index.js.map +1 -1
- package/dist/evaluation/axis-checks/registry.d.ts +2 -2
- package/dist/evaluation/axis-checks/registry.js +252 -1
- package/dist/evaluation/axis-checks/registry.js.map +1 -1
- package/dist/evaluation/index.d.ts +160 -8
- package/dist/evaluation/index.js +702 -81
- package/dist/evaluation/index.js.map +1 -1
- package/dist/fragments/index.js +1 -1
- package/dist/fragments/index.js.map +1 -1
- package/dist/{generate-8L_bc2iP.d.ts → generate-9IeNT4ZY.d.ts} +1 -1
- package/dist/harness/check/runtime-render/index.d.ts +3 -2
- package/dist/harness/check/runtime-render/index.js +8 -12
- package/dist/harness/check/runtime-render/index.js.map +1 -1
- package/dist/harness/index.d.ts +3 -2
- package/dist/harness/index.js +385 -254
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/prompts.d.ts +2 -2
- package/dist/harness/prompts.js +3 -3
- package/dist/harness/prompts.js.map +1 -1
- package/dist/harness/result-types.d.ts +3 -3
- package/dist/harness/runtime.d.ts +13 -4
- package/dist/harness/runtime.js +106 -19
- package/dist/harness/runtime.js.map +1 -1
- package/dist/harness/types-public.d.ts +13 -2
- package/dist/harness/types-public.js +385 -254
- package/dist/harness/types-public.js.map +1 -1
- package/dist/harness/validator-trace-sink.d.ts +2 -2
- package/dist/index.d.ts +29 -7
- package/dist/index.js +4465 -3727
- package/dist/index.js.map +1 -1
- package/dist/patch.d.ts +3 -2
- package/dist/{prompts-k6m6reqe.d.ts → prompts-YU9piOgs.d.ts} +12 -46
- package/dist/styling-profile-CxfvV48g.d.ts +35 -0
- package/dist/tools/get-primitives-ts.d.ts +1 -1
- package/dist/tools/get-primitives-ts.js +1 -1
- package/dist/tools/get-primitives-ts.js.map +1 -1
- package/dist/{types-public-D24JLJgl.d.ts → types-public-DVSssdc-.d.ts} +39 -3
- package/dist/validation/index.d.ts +1 -1
- package/dist/validation/index.js +1 -1
- package/dist/validation/index.js.map +1 -1
- package/dist/workflows.d.ts +3 -2
- package/package.json +12 -12
- package/src/adapters/generation-dispatch.ts +6 -1
- package/src/boilerplate/free-design-prompt.ts +7 -0
- package/src/boilerplate/hard-sections.ts +16 -1
- package/src/boilerplate/styling-profile.ts +166 -0
- package/src/boilerplate/system-prompt.ts +20 -8
- package/src/check/run-tier0.ts +2 -0
- package/src/contract-context.ts +15 -14
- package/src/create-harness.ts +4 -0
- package/src/create-ui-generator.ts +41 -2
- package/src/design-mode.ts +11 -1
- package/src/design-system-docs.ts +7 -4
- package/src/effort.ts +48 -0
- package/src/evaluation/axis-checks/checks/grid.ts +148 -0
- package/src/evaluation/axis-checks/checks/universal.ts +244 -0
- package/src/evaluation/axis-checks/dispatch.ts +78 -13
- package/src/evaluation/axis-checks/helpers.ts +12 -0
- package/src/evaluation/axis-checks/registry.ts +2 -0
- package/src/evaluation/design-identity.ts +96 -0
- package/src/evaluation/index.ts +17 -0
- package/src/evaluation/llm-evaluator.ts +13 -1
- package/src/evaluation/theme-css.ts +14 -0
- package/src/evaluation/types-public.ts +35 -1
- package/src/evaluation/visual-evaluator.ts +478 -45
- package/src/fragments/render.ts +1 -1
- package/src/harness/check/runtime-render/prepare-mockup.ts +18 -11
- package/src/harness/coding/init-session.ts +1 -0
- package/src/harness/coding/run-coding-turn.ts +6 -14
- package/src/harness/coding/run-eval-round.ts +31 -3
- package/src/harness/runtime.ts +10 -0
- package/src/harness/types-public.ts +11 -0
- package/src/index.ts +2 -0
- package/src/pin-fixtures.ts +98 -0
- package/src/run-check.ts +10 -10
- package/src/tools/get-primitives-ts.ts +1 -1
- package/src/tools.ts +2 -1
- package/src/validation/primitives.ts +1 -1
package/dist/compose.js
CHANGED
|
@@ -18,7 +18,7 @@ var renderFragments = {
|
|
|
18
18
|
axis: "render",
|
|
19
19
|
value: "grid",
|
|
20
20
|
cacheTier: "axisDelta",
|
|
21
|
-
promptText:
|
|
21
|
+
promptText: '## Render: grid\nTwo shapes share this axis \u2014 read the contract:\n- A TILE grid (a gallery, a stat grid, items that may carry row/col fields): `<Grid columns={N} gap="md">` (or `<CardGrid>`); if items carry row/col fields, position each tile at (row, col).\n- A BOARD (a columns / lanes / stages array whose items are cards \u2014 a kanban): the columns sit SIDE BY SIDE at md and wider \u2014 `<Grid columns={{ base: 1, md: columns.length }} gap="md">` wrapping the columns map, each column its own `<Stack>` of `<Card>`s with its header and count; NEVER a `<Stack>` of column sections (that is a list \u2014 tier-0 fails it).\nNever hand-roll `display: grid`. Do not scroll horizontally.'
|
|
22
22
|
},
|
|
23
23
|
spatial: {
|
|
24
24
|
axis: "render",
|
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 ?? [])` 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"]}
|
|
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\\nTwo shapes share this axis — read the contract:\\n- A TILE grid (a gallery, a stat grid, items that may carry row/col fields): `<Grid columns={N} gap=\\\"md\\\">` (or `<CardGrid>`); if items carry row/col fields, position each tile at (row, col).\\n- A BOARD (a columns / lanes / stages array whose items are cards — a kanban): the columns sit SIDE BY SIDE at md and wider — `<Grid columns={{ base: 1, md: columns.length }} gap=\\\"md\\\">` wrapping the columns map, each column its own `<Stack>` of `<Card>`s with its header and count; NEVER a `<Stack>` of column sections (that is a list — tier-0 fails it).\\nNever hand-roll `display: grid`. 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"]}
|
|
@@ -1,9 +1,11 @@
|
|
|
1
|
+
import { SingleComponentParams } from './harness/runtime.js';
|
|
1
2
|
import { ModelRef } from '@ggui-ai/protocol';
|
|
2
3
|
import { GadgetCatalogAdapter } from '@ggui-ai/gadgets';
|
|
3
4
|
import { GeneratorTier, UiGenerator } from '@ggui-ai/mcp-server-core';
|
|
4
5
|
import { GenerationResult } from './harness/result-types.js';
|
|
5
|
-
import { D as DesignMode } from './design-mode-
|
|
6
|
-
import { Q as QualityConfig } from './types-public-
|
|
6
|
+
import { D as DesignMode } from './design-mode-DLufhrNp.js';
|
|
7
|
+
import { Q as QualityConfig } from './types-public-DVSssdc-.js';
|
|
8
|
+
import { EvaluationConfig } from './evaluation/types.js';
|
|
7
9
|
import { A as AgentConfig } from './llm-router-D7fjBhtr.js';
|
|
8
10
|
|
|
9
11
|
/**
|
|
@@ -56,6 +58,20 @@ interface CreateUiGeneratorOptions {
|
|
|
56
58
|
readonly maxAttempts?: number;
|
|
57
59
|
/** Maximum evaluation rounds. */
|
|
58
60
|
readonly maxEvalRounds?: number;
|
|
61
|
+
/**
|
|
62
|
+
* In-loop LLM evaluation config (ggui#1001). When set, every generation
|
|
63
|
+
* runs the evaluator tiers after the coding leg (`maxRounds` defaults to
|
|
64
|
+
* `maxEvalRounds`) and the harness result carries `evalResult` — the
|
|
65
|
+
* probe verdict a downstream judge reads. Default: none, as today —
|
|
66
|
+
* a serving deployment leaves it unset; offline minting jobs set a round.
|
|
67
|
+
*/
|
|
68
|
+
readonly evaluation?: EvaluationConfig;
|
|
69
|
+
/**
|
|
70
|
+
* In-loop visual round config (per-canvas judging, a sample, the
|
|
71
|
+
* stylesheet to render under). Forwarded verbatim; never part of the
|
|
72
|
+
* prompt or the identity. Default: none — the round runs as today.
|
|
73
|
+
*/
|
|
74
|
+
readonly visualEvaluation?: SingleComponentParams['visualEvaluation'];
|
|
59
75
|
/** Quality config controlling eval tiers + improvement behavior. */
|
|
60
76
|
readonly qualityConfig?: QualityConfig;
|
|
61
77
|
/**
|
|
@@ -17,12 +17,6 @@ declare const CANVAS_VIEWPORTS: Readonly<Record<CanvasClass, CanvasViewport>>;
|
|
|
17
17
|
* there the width decides the class.
|
|
18
18
|
*/
|
|
19
19
|
type CanvasDisplayMode = "inline" | "fullscreen";
|
|
20
|
-
/**
|
|
21
|
-
* Map a shell name (`chat` / `fullscreen` / `spatial` / `partial`) or an
|
|
22
|
-
* MCP Apps display mode (`inline` / `fullscreen` / `pip`) onto the
|
|
23
|
-
* canvas display mode. Only `chat` and `inline` are the inline card;
|
|
24
|
-
* everything else is width-governed.
|
|
25
|
-
*/
|
|
26
20
|
declare function canvasDisplayModeForShell(shell: string | undefined): CanvasDisplayMode;
|
|
27
21
|
/** Fullscreen-class breakpoints (inclusive lower bounds), CSS px. */
|
|
28
22
|
declare const CANVAS_BREAKPOINTS: {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { DataContract } from '@ggui-ai/protocol';
|
|
2
2
|
import { C as Classification } from './axes-CzLEMDeB.js';
|
|
3
|
-
import { E as EvalIssue } from './types-public-
|
|
4
|
-
import { D as DesignMode } from './design-mode-
|
|
3
|
+
import { E as EvalIssue } from './types-public-DVSssdc-.js';
|
|
4
|
+
import { D as DesignMode } from './design-mode-DLufhrNp.js';
|
|
5
5
|
|
|
6
6
|
interface RunAxisChecksInput {
|
|
7
7
|
sourceCode: string;
|
|
@@ -1,10 +1,10 @@
|
|
|
1
|
-
export { R as RunAxisChecksInput, r as runAxisChecks } from '../../dispatch-
|
|
1
|
+
export { R as RunAxisChecksInput, r as runAxisChecks } from '../../dispatch-D2htzi9h.js';
|
|
2
2
|
export { REGISTRY } from './registry.js';
|
|
3
|
-
import { A as AxisCheck } from '../../types-public-
|
|
4
|
-
export { a as AxisCheckInput, m as matches } from '../../types-public-
|
|
3
|
+
import { A as AxisCheck } from '../../types-public-DVSssdc-.js';
|
|
4
|
+
export { a as AxisCheckInput, m as matches } from '../../types-public-DVSssdc-.js';
|
|
5
5
|
import '@ggui-ai/protocol';
|
|
6
6
|
import '../../axes-CzLEMDeB.js';
|
|
7
|
-
import '../../design-mode-
|
|
7
|
+
import '../../design-mode-DLufhrNp.js';
|
|
8
8
|
import '../../types-BOvHNG7K.js';
|
|
9
9
|
|
|
10
10
|
declare const EXTRA_CHECKS: readonly AxisCheck[];
|
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { LUCIDE_ICON_NAMES } from '@ggui-ai/design';
|
|
1
2
|
import { listContractGadgets, HOOK_NAME_RE, STDLIB_GADGETS_PACKAGE } from '@ggui-ai/protocol';
|
|
3
|
+
import { createHash } from 'crypto';
|
|
2
4
|
|
|
3
|
-
// src/evaluation/axis-checks/
|
|
5
|
+
// src/evaluation/axis-checks/checks/universal.ts
|
|
4
6
|
var ID_FIELD_CANDIDATES = ["id", "uuid", "symbol", "key", "slug", "code"];
|
|
5
7
|
function getItemsProperties(p) {
|
|
6
8
|
return p.items?.properties ?? p.schema?.items?.properties;
|
|
@@ -157,6 +159,9 @@ function mkIssue(subcategory, description, fix, result = "fail") {
|
|
|
157
159
|
function cap(s) {
|
|
158
160
|
return s.charAt(0).toUpperCase() + s.slice(1);
|
|
159
161
|
}
|
|
162
|
+
function axisCheckTraceEnabled() {
|
|
163
|
+
return process.env["GGUI_AXIS_CHECK_TRACE"] === "1";
|
|
164
|
+
}
|
|
160
165
|
|
|
161
166
|
// src/evaluation/axis-checks/checks/universal.ts
|
|
162
167
|
var ALL_RENDER_VALUES = [
|
|
@@ -260,7 +265,172 @@ function runPropSeedNoResync(input) {
|
|
|
260
265
|
}
|
|
261
266
|
return issues;
|
|
262
267
|
}
|
|
268
|
+
var KNOWN_ICON_KEYS = new Set(
|
|
269
|
+
LUCIDE_ICON_NAMES.map((n) => n.replace(/[-_]/g, "").toLowerCase())
|
|
270
|
+
);
|
|
271
|
+
function isKnownIconName(name) {
|
|
272
|
+
return KNOWN_ICON_KEYS.has(name.replace(/[-_]/g, "").toLowerCase());
|
|
273
|
+
}
|
|
274
|
+
var ICON_NAME_LITERAL_RX = /<Icon\b[^>]*?\bname\s*=\s*(?:"([^"]*)"|'([^']*)'|\{\s*"([^"]*)"\s*\}|\{\s*'([^']*)'\s*\})/g;
|
|
275
|
+
function findUnknownIconNames(sourceCode) {
|
|
276
|
+
const unknown = /* @__PURE__ */ new Set();
|
|
277
|
+
for (const m of sourceCode.matchAll(ICON_NAME_LITERAL_RX)) {
|
|
278
|
+
const name = m[1] ?? m[2] ?? m[3] ?? m[4] ?? "";
|
|
279
|
+
if (name.length === 0) continue;
|
|
280
|
+
if (/[^\x20-\x7e]/.test(name)) continue;
|
|
281
|
+
if (!isKnownIconName(name)) unknown.add(name);
|
|
282
|
+
}
|
|
283
|
+
return [...unknown];
|
|
284
|
+
}
|
|
285
|
+
function runIconNameKnown(input) {
|
|
286
|
+
if (input.compiledCode === null) return [];
|
|
287
|
+
const unknown = findUnknownIconNames(input.sourceCode);
|
|
288
|
+
if (unknown.length === 0) return [];
|
|
289
|
+
const list = unknown.map((n) => `"${n}"`).join(", ");
|
|
290
|
+
return [
|
|
291
|
+
mkIssue(
|
|
292
|
+
"universal.icon_name_known",
|
|
293
|
+
`<Icon name=\u2026> uses ${unknown.length} name(s) outside the design system's Lucide subset: ${list} \u2014 an unknown name renders an empty box.`,
|
|
294
|
+
"Call get_available_icons and pick a listed name (kebab-case), or use an emoji directly."
|
|
295
|
+
)
|
|
296
|
+
];
|
|
297
|
+
}
|
|
298
|
+
var LADDER_STOP_AS_TEXT_RX = /(?<![\w-])color\s*(?:=\s*\{?\s*|:\s*)["'`]?\s*var\(--ggui-color-primary-(\d{2,3})\s*[,)]/g;
|
|
299
|
+
var TEXT_INK_STOP_MIN = 300;
|
|
300
|
+
var TEXT_INK_STOP_MAX = 600;
|
|
301
|
+
function findLadderStopsAsText(sourceCode) {
|
|
302
|
+
const found = /* @__PURE__ */ new Set();
|
|
303
|
+
for (const m of sourceCode.matchAll(LADDER_STOP_AS_TEXT_RX)) {
|
|
304
|
+
const digits = m[1] ?? "";
|
|
305
|
+
const stop = Number(digits);
|
|
306
|
+
if (stop >= TEXT_INK_STOP_MIN && stop <= TEXT_INK_STOP_MAX) found.add(`primary-${digits}`);
|
|
307
|
+
}
|
|
308
|
+
return [...found];
|
|
309
|
+
}
|
|
310
|
+
function runAccentTextInk(input) {
|
|
311
|
+
if (input.compiledCode === null) return [];
|
|
312
|
+
const stops = findLadderStopsAsText(input.sourceCode);
|
|
313
|
+
if (stops.length === 0) return [];
|
|
314
|
+
const list = stops.map((s) => `\`${s}\``).join(", ");
|
|
315
|
+
return [
|
|
316
|
+
mkIssue(
|
|
317
|
+
"universal.accent_text_ink",
|
|
318
|
+
`Text is painted with a brand-ladder fill stop as its color (${list}) \u2014 on a low-saturation brand these stops are mid-grey on the surface and the text fails readability.`,
|
|
319
|
+
'Accent text (eyebrows, taglines, links, labels, inline arrows) takes var(--ggui-color-link) or <Text tone="emphasized">; body text takes var(--ggui-color-onContainer); keep primary-* stops on fills and borders.'
|
|
320
|
+
)
|
|
321
|
+
];
|
|
322
|
+
}
|
|
323
|
+
var SCOPED_SURFACE_TAG_RX = /<(Card|Box)\b([^<>]*?)surface\s*=\s*["'](inverted|hero)["']([^<>]*?)>/g;
|
|
324
|
+
var STYLE_BACKGROUND_RX = /\bstyle\s*=\s*\{\{[^}]*\b(background|backgroundColor|backgroundImage)\s*:/;
|
|
325
|
+
function findScopedSurfaceBackgrounds(sourceCode) {
|
|
326
|
+
const hits = [];
|
|
327
|
+
for (const m of sourceCode.matchAll(SCOPED_SURFACE_TAG_RX)) {
|
|
328
|
+
const attrs = `${m[2] ?? ""} ${m[4] ?? ""}`;
|
|
329
|
+
if (STYLE_BACKGROUND_RX.test(attrs)) hits.push([m[1] ?? "", m[3] ?? ""]);
|
|
330
|
+
}
|
|
331
|
+
return hits;
|
|
332
|
+
}
|
|
333
|
+
function runScopedSurfaceOwnsGround(input) {
|
|
334
|
+
if (input.compiledCode === null) return [];
|
|
335
|
+
if (input.designMode === "free") return [];
|
|
336
|
+
const hits = findScopedSurfaceBackgrounds(input.sourceCode);
|
|
337
|
+
if (hits.length === 0) return [];
|
|
338
|
+
const [tag, surface] = hits[0];
|
|
339
|
+
return [
|
|
340
|
+
mkIssue(
|
|
341
|
+
"universal.scoped_surface_owns_ground",
|
|
342
|
+
`<${tag} surface="${surface}"> sets its own background in style \u2014 a scoped surface owns its ground; repainting it puts the scope's swapped inks over the wrong ground (ink on ink).`,
|
|
343
|
+
`Remove the background from that ${tag}'s style \u2014 surface="${surface}" paints the ground; pick another surface if a different ground is wanted.`
|
|
344
|
+
)
|
|
345
|
+
];
|
|
346
|
+
}
|
|
347
|
+
var CAPS_TEXT_RX = /<Text\b(?=[^>]*\bcaps\b)[^>]*>([\s\S]*?)<\/Text>/g;
|
|
348
|
+
function findInventedCapsLabels(sourceCode) {
|
|
349
|
+
const out = [];
|
|
350
|
+
for (const m of sourceCode.matchAll(CAPS_TEXT_RX)) {
|
|
351
|
+
const inner = (m[1] ?? "").trim();
|
|
352
|
+
if (inner.length === 0) continue;
|
|
353
|
+
if (inner.includes("{")) continue;
|
|
354
|
+
out.push(inner.replace(/\s+/g, " ").slice(0, 60));
|
|
355
|
+
}
|
|
356
|
+
return out;
|
|
357
|
+
}
|
|
358
|
+
function runCapsLabelInvented(input) {
|
|
359
|
+
if (input.compiledCode === null) return [];
|
|
360
|
+
const invented = findInventedCapsLabels(input.sourceCode);
|
|
361
|
+
if (invented.length === 0) return [];
|
|
362
|
+
const list = invented.map((t) => `"${t}"`).join(", ");
|
|
363
|
+
return [
|
|
364
|
+
mkIssue(
|
|
365
|
+
"universal.caps_label_invented",
|
|
366
|
+
`${invented.length} caps label(s) carry invented copy the props do not supply: ${list} \u2014 an eyebrow, kicker or overline the agent cannot change is chatter on every render.`,
|
|
367
|
+
"Remove the label, or bind its text to a prop (`<Text caps>{props.section}</Text>`); copy comes from the contract, never from the component."
|
|
368
|
+
)
|
|
369
|
+
];
|
|
370
|
+
}
|
|
371
|
+
var VIEWPORT_UNIT = String.raw`(?:calc\(\s*)?100(?:vh|dvh|svh|lvh)\b(?:[^)"'\x60;\n]*\))?`;
|
|
372
|
+
var VIEWPORT_STYLE_OBJECT_RX = new RegExp(
|
|
373
|
+
String.raw`\b(?:minHeight|height|minBlockSize|blockSize)\s*:\s*["'\x60]\s*${VIEWPORT_UNIT}["'\x60]?`,
|
|
374
|
+
"g"
|
|
375
|
+
);
|
|
376
|
+
var VIEWPORT_CSS_RX = new RegExp(
|
|
377
|
+
String.raw`(?<![\w-])(?:min-)?(?:height|block-size)\s*:\s*${VIEWPORT_UNIT}`,
|
|
378
|
+
"g"
|
|
379
|
+
);
|
|
380
|
+
var VIEWPORT_CLASS_RX = /(?<![\w-])(?:min-)?h-(?:screen|dvh|svh|lvh)\b/g;
|
|
381
|
+
function findViewportSizing(sourceCode) {
|
|
382
|
+
const hits = [];
|
|
383
|
+
for (const rx of [VIEWPORT_STYLE_OBJECT_RX, VIEWPORT_CSS_RX, VIEWPORT_CLASS_RX]) {
|
|
384
|
+
for (const m of sourceCode.matchAll(rx)) {
|
|
385
|
+
hits.push({ at: m.index ?? 0, text: m[0].replace(/\s+/g, " ").trim().slice(0, 60) });
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
return hits.sort((a, b) => a.at - b.at).map((h) => h.text);
|
|
389
|
+
}
|
|
390
|
+
function runViewportSized(input) {
|
|
391
|
+
if (input.compiledCode === null) return [];
|
|
392
|
+
const sized = findViewportSizing(input.sourceCode);
|
|
393
|
+
if (sized.length === 0) return [];
|
|
394
|
+
const list = sized.map((t) => "`" + t + "`").join(", ");
|
|
395
|
+
return [
|
|
396
|
+
mkIssue(
|
|
397
|
+
"universal.viewport_sized",
|
|
398
|
+
`${sized.length} element(s) are sized to the viewport: ${list} \u2014 the frame owns the height; a content-sized frame is measured from the content, so a viewport-sized root never shrinks and grows the frame on every re-measure.`,
|
|
399
|
+
"Remove the viewport height \u2014 size to content, never to the viewport. Under fullscreen the frame stretches your root already (centre inside it with `display: flex; flex-direction: column; justify-content: center`); to fill a parent that has a height use `height: 100%` or `flex: 1`."
|
|
400
|
+
)
|
|
401
|
+
];
|
|
402
|
+
}
|
|
263
403
|
var UNIVERSAL_CHECKS = [
|
|
404
|
+
{
|
|
405
|
+
id: "universal.icon_name_known",
|
|
406
|
+
axis: "render",
|
|
407
|
+
values: ALL_RENDER_VALUES,
|
|
408
|
+
run: runIconNameKnown
|
|
409
|
+
},
|
|
410
|
+
{
|
|
411
|
+
id: "universal.caps_label_invented",
|
|
412
|
+
axis: "render",
|
|
413
|
+
values: ALL_RENDER_VALUES,
|
|
414
|
+
run: runCapsLabelInvented
|
|
415
|
+
},
|
|
416
|
+
{
|
|
417
|
+
id: "universal.viewport_sized",
|
|
418
|
+
axis: "render",
|
|
419
|
+
values: ALL_RENDER_VALUES,
|
|
420
|
+
run: runViewportSized
|
|
421
|
+
},
|
|
422
|
+
{
|
|
423
|
+
id: "universal.accent_text_ink",
|
|
424
|
+
axis: "render",
|
|
425
|
+
values: ALL_RENDER_VALUES,
|
|
426
|
+
run: runAccentTextInk
|
|
427
|
+
},
|
|
428
|
+
{
|
|
429
|
+
id: "universal.scoped_surface_owns_ground",
|
|
430
|
+
axis: "render",
|
|
431
|
+
values: ALL_RENDER_VALUES,
|
|
432
|
+
run: runScopedSurfaceOwnsGround
|
|
433
|
+
},
|
|
264
434
|
{
|
|
265
435
|
id: "universal.prop_coverage",
|
|
266
436
|
axis: "render",
|
|
@@ -1063,6 +1233,87 @@ var TOOLING_CHECKS = [
|
|
|
1063
1233
|
}
|
|
1064
1234
|
];
|
|
1065
1235
|
|
|
1236
|
+
// src/evaluation/axis-checks/checks/grid.ts
|
|
1237
|
+
var BOARD_PROP_RX = /^(columns?|lanes?|stages?|lists?|buckets?|swimlanes?)$/i;
|
|
1238
|
+
var BOARD_PROMPT_RX = /\b(kanban|board)\b/i;
|
|
1239
|
+
var BOARD_NAMES = ["columns", "lanes", "stages", "lists", "buckets", "swimlanes"];
|
|
1240
|
+
var LAYOUT_TAG_RX = /<(\/?)(Grid|Row|Stack|Card|Box|Container|ScrollArea|div|section)\b([^<>]*?)>/g;
|
|
1241
|
+
var SIDE_BY_SIDE = /* @__PURE__ */ new Set(["Grid", "Row"]);
|
|
1242
|
+
function boardPropNames(contract) {
|
|
1243
|
+
const propsField = contract?.propsSpec;
|
|
1244
|
+
const properties = propsField?.properties ?? {};
|
|
1245
|
+
return Object.entries(properties).filter(([name, p]) => BOARD_PROP_RX.test(name) && (p?.type === "array" || p?.schema?.type === "array")).map(([name]) => name);
|
|
1246
|
+
}
|
|
1247
|
+
function jsxMapPositions(sourceCode, name) {
|
|
1248
|
+
const rx = new RegExp(
|
|
1249
|
+
`\\b${name}(?:\\?\\.)?\\.map\\(\\s*\\(?\\s*\\w+(?:\\s*,\\s*\\w+)?\\s*\\)?\\s*=>\\s*(?:\\(?\\s*<|\\{[^{}]{0,600}?\\breturn\\s*\\(?\\s*<)`,
|
|
1250
|
+
"g"
|
|
1251
|
+
);
|
|
1252
|
+
return [...sourceCode.matchAll(rx)].map((m) => m.index ?? 0);
|
|
1253
|
+
}
|
|
1254
|
+
function enclosingLayoutTag(sourceCode, pos) {
|
|
1255
|
+
const stack = [];
|
|
1256
|
+
for (const m of sourceCode.slice(0, pos).matchAll(LAYOUT_TAG_RX)) {
|
|
1257
|
+
const closing = m[1] === "/";
|
|
1258
|
+
const name = m[2] ?? "";
|
|
1259
|
+
const attrs = m[3] ?? "";
|
|
1260
|
+
if (closing) {
|
|
1261
|
+
if (stack[stack.length - 1] === name) stack.pop();
|
|
1262
|
+
continue;
|
|
1263
|
+
}
|
|
1264
|
+
if (attrs.trimEnd().endsWith("/")) continue;
|
|
1265
|
+
stack.push(name);
|
|
1266
|
+
}
|
|
1267
|
+
return stack[stack.length - 1];
|
|
1268
|
+
}
|
|
1269
|
+
function describeBoardRead(input) {
|
|
1270
|
+
const declared = boardPropNames(input.contract);
|
|
1271
|
+
const names = declared.length > 0 ? declared : BOARD_PROMPT_RX.test(input.originalPrompt) ? [...BOARD_NAMES] : BOARD_NAMES.filter((n) => jsxMapPositions(input.sourceCode, n).length > 0);
|
|
1272
|
+
const perName = names.map((name) => {
|
|
1273
|
+
const positions = jsxMapPositions(input.sourceCode, name);
|
|
1274
|
+
return { name, maps: positions.length, enclosing: positions.map((pos) => enclosingLayoutTag(input.sourceCode, pos) ?? "nothing") };
|
|
1275
|
+
});
|
|
1276
|
+
return { declared, names, perName };
|
|
1277
|
+
}
|
|
1278
|
+
function findStackedBoardColumns(input) {
|
|
1279
|
+
const stacked = [];
|
|
1280
|
+
for (const { name, enclosing } of describeBoardRead(input).perName) {
|
|
1281
|
+
for (const tag of enclosing) if (!SIDE_BY_SIDE.has(tag)) stacked.push([name, tag]);
|
|
1282
|
+
}
|
|
1283
|
+
return stacked;
|
|
1284
|
+
}
|
|
1285
|
+
function runBoardColumnsSideBySide(input) {
|
|
1286
|
+
if (input.compiledCode === null) return [];
|
|
1287
|
+
const standDown = input.designMode === "free";
|
|
1288
|
+
const stacked = standDown ? [] : findStackedBoardColumns(input);
|
|
1289
|
+
if (axisCheckTraceEnabled()) {
|
|
1290
|
+
const line = {
|
|
1291
|
+
...describeBoardRead(input),
|
|
1292
|
+
designMode: input.designMode ?? "constrained",
|
|
1293
|
+
standDown,
|
|
1294
|
+
result: standDown ? "stand-down" : stacked.length > 0 ? "fail" : "pass"
|
|
1295
|
+
};
|
|
1296
|
+
console.log(JSON.stringify({ boardColumnsTrace: line }));
|
|
1297
|
+
}
|
|
1298
|
+
if (stacked.length === 0) return [];
|
|
1299
|
+
const [name, tag] = stacked[0];
|
|
1300
|
+
return [
|
|
1301
|
+
mkIssue(
|
|
1302
|
+
"grid.board_columns_side_by_side",
|
|
1303
|
+
`The board's \`${name}\` are composed under <${tag}> \u2014 a vertical stack of column sections; at md and wider the columns must sit side by side.`,
|
|
1304
|
+
`Wrap the ${name} map in <Grid columns={{ base: 1, md: ${name}.length }} gap="md"> (each column its own <Stack> of cards); never a <Stack> of column sections.`
|
|
1305
|
+
)
|
|
1306
|
+
];
|
|
1307
|
+
}
|
|
1308
|
+
var GRID_CHECKS = [
|
|
1309
|
+
{
|
|
1310
|
+
id: "grid.board_columns_side_by_side",
|
|
1311
|
+
axis: "render",
|
|
1312
|
+
values: ["grid"],
|
|
1313
|
+
run: runBoardColumnsSideBySide
|
|
1314
|
+
}
|
|
1315
|
+
];
|
|
1316
|
+
|
|
1066
1317
|
// src/evaluation/axis-checks/extras.ts
|
|
1067
1318
|
function mkIssue2(subcategory, description, fix, result = "warn") {
|
|
1068
1319
|
return { tier: 0, result, category: "mode", subcategory, description, fix };
|
|
@@ -1202,6 +1453,7 @@ var REGISTRY = [
|
|
|
1202
1453
|
...WRITES_CHECKS,
|
|
1203
1454
|
...STATE_PAYLOAD_CHECKS,
|
|
1204
1455
|
...TOOLING_CHECKS,
|
|
1456
|
+
...GRID_CHECKS,
|
|
1205
1457
|
...EXTRA_CHECKS
|
|
1206
1458
|
];
|
|
1207
1459
|
|
|
@@ -1215,27 +1467,59 @@ function matches(vector, check) {
|
|
|
1215
1467
|
}
|
|
1216
1468
|
return true;
|
|
1217
1469
|
}
|
|
1218
|
-
|
|
1219
|
-
// src/evaluation/axis-checks/dispatch.ts
|
|
1220
1470
|
function runAxisChecks(classification, input) {
|
|
1221
|
-
|
|
1222
|
-
const axisInput = {
|
|
1471
|
+
const facts = {
|
|
1223
1472
|
sourceCode: input.sourceCode,
|
|
1224
|
-
compiledCode: input.compiledCode,
|
|
1225
1473
|
...input.contract !== void 0 ? { contract: input.contract } : {},
|
|
1226
1474
|
originalPrompt: input.originalPrompt,
|
|
1227
1475
|
classification,
|
|
1228
1476
|
...input.designMode !== void 0 ? { designMode: input.designMode } : {}
|
|
1229
1477
|
};
|
|
1478
|
+
if (input.compiledCode === null) {
|
|
1479
|
+
traceAxisChecksSkipped(facts, "compiledCode null \u2014 no check ran");
|
|
1480
|
+
return [];
|
|
1481
|
+
}
|
|
1482
|
+
const axisInput = { ...facts, compiledCode: input.compiledCode };
|
|
1483
|
+
return runGatedAxisChecks(
|
|
1484
|
+
REGISTRY.filter((check) => matches(classification.vector, check)),
|
|
1485
|
+
axisInput
|
|
1486
|
+
).issues;
|
|
1487
|
+
}
|
|
1488
|
+
function runGatedAxisChecks(checks, input) {
|
|
1230
1489
|
const issues = [];
|
|
1231
|
-
const firedIds =
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
if (
|
|
1235
|
-
|
|
1236
|
-
|
|
1490
|
+
const firedIds = [];
|
|
1491
|
+
const seen = /* @__PURE__ */ new Set();
|
|
1492
|
+
for (const check of checks) {
|
|
1493
|
+
if (seen.has(check.id)) continue;
|
|
1494
|
+
seen.add(check.id);
|
|
1495
|
+
firedIds.push(check.id);
|
|
1496
|
+
issues.push(...check.run(input));
|
|
1237
1497
|
}
|
|
1238
|
-
|
|
1498
|
+
if (axisCheckTraceEnabled()) {
|
|
1499
|
+
const line = {
|
|
1500
|
+
...traceFacts(input),
|
|
1501
|
+
matched: [...firedIds],
|
|
1502
|
+
issues: issues.map((i) => ({ id: i.subcategory ?? i.category, result: i.result }))
|
|
1503
|
+
};
|
|
1504
|
+
console.log(JSON.stringify({ axisCheckTrace: line }));
|
|
1505
|
+
}
|
|
1506
|
+
return { issues, firedIds };
|
|
1507
|
+
}
|
|
1508
|
+
function traceAxisChecksSkipped(facts, note) {
|
|
1509
|
+
if (!axisCheckTraceEnabled()) return;
|
|
1510
|
+
const line = { ...traceFacts(facts), matched: [], issues: [], note };
|
|
1511
|
+
console.log(JSON.stringify({ axisCheckTrace: line }));
|
|
1512
|
+
}
|
|
1513
|
+
function traceFacts(input) {
|
|
1514
|
+
const propsSpec = input.contract?.propsSpec;
|
|
1515
|
+
return {
|
|
1516
|
+
sourceSha256: createHash("sha256").update(input.sourceCode, "utf8").digest("hex"),
|
|
1517
|
+
originalPrompt: input.originalPrompt,
|
|
1518
|
+
designMode: input.designMode ?? "constrained",
|
|
1519
|
+
classification: input.classification.vector,
|
|
1520
|
+
propsSpecKeys: Object.keys(propsSpec ?? {}),
|
|
1521
|
+
propsSpecPropertyKeys: Object.keys(propsSpec?.properties ?? {})
|
|
1522
|
+
};
|
|
1239
1523
|
}
|
|
1240
1524
|
|
|
1241
1525
|
export { EXTRA_CHECKS, REGISTRY, matches, runAxisChecks };
|