@ggui-ai/ui-gen 0.8.0 → 0.10.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 +18 -1
- package/dist/adapters/generation-dispatch.js +819 -1577
- package/dist/adapters/generation-dispatch.js.map +1 -1
- package/dist/adapters/index.js +28 -11
- package/dist/adapters/index.js.map +1 -1
- package/dist/advanced/index.d.ts +5 -3
- package/dist/advanced/index.js +838 -1579
- package/dist/advanced/index.js.map +1 -1
- package/dist/blueprint-validator.js +109 -3
- package/dist/blueprint-validator.js.map +1 -1
- package/dist/boilerplate.js +17 -4
- package/dist/boilerplate.js.map +1 -1
- package/dist/check/index.d.ts +1 -1
- package/dist/check/index.js +11 -1
- package/dist/check/index.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-CF0EL0Li.d.ts} +26 -1
- package/dist/{dispatch-Deo9qzrG.d.ts → dispatch-D75M-OfZ.d.ts} +1 -1
- package/dist/evaluation/axis-checks/index.d.ts +3 -3
- package/dist/evaluation/axis-checks/index.js +133 -1
- package/dist/evaluation/axis-checks/index.js.map +1 -1
- package/dist/evaluation/axis-checks/registry.d.ts +1 -1
- package/dist/evaluation/axis-checks/registry.js +133 -1
- package/dist/evaluation/axis-checks/registry.js.map +1 -1
- package/dist/evaluation/index.d.ts +46 -3
- package/dist/evaluation/index.js +410 -24
- 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/check/runtime-render/index.d.ts +2 -2
- package/dist/harness/check/runtime-render/index.js +110 -3
- package/dist/harness/check/runtime-render/index.js.map +1 -1
- package/dist/harness/index.d.ts +5 -126
- package/dist/harness/index.js +292 -32
- package/dist/harness/index.js.map +1 -1
- package/dist/harness/llm-trace-sink.d.ts +1 -1
- package/dist/harness/llm-trace-sink.js.map +1 -1
- package/dist/harness/prompts.js +17 -3
- package/dist/harness/prompts.js.map +1 -1
- package/dist/harness/result-types.d.ts +1 -1
- package/dist/harness/runtime.d.ts +17 -1
- package/dist/harness/runtime.js +23 -8
- package/dist/harness/runtime.js.map +1 -1
- package/dist/harness/types-public.d.ts +1 -1
- package/dist/harness/types-public.js +22 -9
- package/dist/harness/types-public.js.map +1 -1
- package/dist/harness/validator-trace-sink.d.ts +1 -1
- package/dist/index.d.ts +5 -3
- package/dist/index.js +838 -1579
- package/dist/index.js.map +1 -1
- package/dist/llm-router-BEVtbAb9.d.ts +213 -0
- package/dist/patch.d.ts +1 -1
- package/dist/provider-adapter.d.ts +16 -6
- package/dist/provider-adapter.js.map +1 -1
- package/dist/providers/index.d.ts +54 -99
- package/dist/providers/index.js +13 -8
- package/dist/providers/index.js.map +1 -1
- package/dist/{render-check-CNZjHD5g.d.ts → render-check-BNYGVDJd.d.ts} +1 -1
- package/dist/{types-public-C36k1q6s.d.ts → types-public-DIpjC-OA.d.ts} +1 -1
- package/dist/validation/index.js +2 -2
- package/dist/validation/index.js.map +1 -1
- package/dist/workflows.d.ts +1 -1
- package/package.json +18 -18
- package/src/adapters/generation-dispatch.ts +28 -2
- package/src/adapters/openrouter/client.ts +2 -2
- package/src/adapters/openrouter/types.ts +10 -0
- package/src/boilerplate/generate.ts +10 -2
- package/src/boilerplate/json-schema-ts.ts +22 -2
- package/src/boilerplate/system-prompt.ts +4 -2
- package/src/compile.ts +1 -1
- package/src/contract-context.ts +10 -2
- package/src/create-ui-generator.ts +58 -11
- package/src/design-system-docs.ts +6 -4
- package/src/evaluation/axis-checks/checks/state-merge.ts +1 -1
- package/src/evaluation/axis-checks/checks/state-ui-affordance.ts +162 -0
- package/src/evaluation/axis-checks/checks/universal.ts +39 -0
- package/src/evaluation/axis-checks/registry.ts +2 -0
- package/src/evaluation/index.ts +11 -0
- package/src/evaluation/llm-evaluator.ts +26 -3
- package/src/evaluation/visual-evaluator.ts +49 -66
- package/src/fragments/realtime.ts +3 -3
- package/src/fragments/state.ts +2 -2
- package/src/harness/check/runtime-render/adapter.ts +18 -0
- package/src/harness/check/runtime-render/index.ts +8 -0
- package/src/harness/check/runtime-render/prepare-mockup.ts +14 -0
- package/src/harness/check/runtime-render/production-act-shim.ts +158 -0
- package/src/harness/check/runtime-render/render-check.ts +72 -1
- package/src/harness/coding/init-session.ts +4 -1
- package/src/harness/coding/run-eval-round.ts +67 -5
- package/src/harness/index.ts +1 -1
- package/src/harness/llm-router.ts +451 -27
- package/src/harness/llm-trace-sink.ts +2 -1
- package/src/harness/runtime.ts +15 -0
- package/src/index.ts +5 -5
- package/src/provider-adapter.ts +16 -6
- package/src/providers/bedrock.ts +89 -26
- package/src/providers/index.ts +16 -4
- package/src/tools/render-check.ts +19 -7
- package/src/validation/ui-compiler.ts +2 -2
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as _ggui_ai_protocol from '@ggui-ai/protocol';
|
|
2
2
|
import { JsonValue, DataContract, JsonObject, GadgetDescriptor } from '@ggui-ai/protocol';
|
|
3
3
|
import { C as Classification } from '../axes-CzLEMDeB.js';
|
|
4
|
-
import { E as EvalIssue, R as RuntimeProbeStatus, A as AxisCheck,
|
|
4
|
+
import { E as EvalIssue, R as RuntimeProbeStatus, A as AxisCheck, c as EvalTier } from '../types-public-DIpjC-OA.js';
|
|
5
5
|
import { LLMToolDef } from '../llm.js';
|
|
6
6
|
import { H as HarnessFragment, a as CacheTier } from '../types-BOvHNG7K.js';
|
|
7
7
|
import { ProcessMode, HarnessPolicy } from '../policy.js';
|
|
@@ -345,11 +345,11 @@ var stateFragments = {
|
|
|
345
345
|
axis: "state",
|
|
346
346
|
value: "merge",
|
|
347
347
|
cacheTier: "axisDelta",
|
|
348
|
-
promptText: "## State: merge (live entity reconciliation)\n1. Seed `const [items, setItems] = useState(props.items ?? [])
|
|
348
|
+
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.",
|
|
349
349
|
boilerplateMarker: [
|
|
350
350
|
"",
|
|
351
351
|
" // \u2500\u2500 Live entity state (merge-by-id) \u2500\u2500",
|
|
352
|
-
" // useState(props.items ?? []); merge stream events by item.id; never append.",
|
|
352
|
+
" // useState(props.items ?? []); re-seed via useEffect on [props.items]; merge stream events by item.id; never append.",
|
|
353
353
|
""
|
|
354
354
|
].join("\n")
|
|
355
355
|
},
|
|
@@ -467,13 +467,13 @@ var realtimeFragments = {
|
|
|
467
467
|
axis: "realtime",
|
|
468
468
|
value: "merge",
|
|
469
469
|
cacheTier: "axisDelta",
|
|
470
|
-
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."
|
|
470
|
+
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."
|
|
471
471
|
},
|
|
472
472
|
append: {
|
|
473
473
|
axis: "realtime",
|
|
474
474
|
value: "append",
|
|
475
475
|
cacheTier: "axisDelta",
|
|
476
|
-
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."
|
|
476
|
+
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."
|
|
477
477
|
},
|
|
478
478
|
status: {
|
|
479
479
|
axis: "realtime",
|
|
@@ -491,7 +491,7 @@ var realtimeFragments = {
|
|
|
491
491
|
axis: "realtime",
|
|
492
492
|
value: "mixed",
|
|
493
493
|
cacheTier: "axisDelta",
|
|
494
|
-
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.",
|
|
494
|
+
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.",
|
|
495
495
|
boilerplateMarker: [
|
|
496
496
|
"",
|
|
497
497
|
" // \u2500\u2500 Mixed stream handlers \u2500\u2500",
|
|
@@ -711,6 +711,15 @@ function jsonSchemaTypeToTs(schema) {
|
|
|
711
711
|
if (schema.enum?.length) {
|
|
712
712
|
return schema.enum.map((v) => typeof v === "string" ? `'${v}'` : String(v)).join(" | ");
|
|
713
713
|
}
|
|
714
|
+
if (Array.isArray(schema.type)) {
|
|
715
|
+
const { nullable: _nullable, ...rest } = schema;
|
|
716
|
+
const members = [...new Set(schema.type)].map(
|
|
717
|
+
(member) => jsonSchemaTypeToTs({ ...rest, type: member })
|
|
718
|
+
);
|
|
719
|
+
const unique = [...new Set(members)];
|
|
720
|
+
if (schema.nullable && !unique.includes("null")) unique.push("null");
|
|
721
|
+
return unique.join(" | ");
|
|
722
|
+
}
|
|
714
723
|
let result;
|
|
715
724
|
switch (schema.type) {
|
|
716
725
|
case "string":
|
|
@@ -728,7 +737,8 @@ function jsonSchemaTypeToTs(schema) {
|
|
|
728
737
|
case "array": {
|
|
729
738
|
if (schema.items) {
|
|
730
739
|
const itemType = jsonSchemaTypeToTs(schema.items);
|
|
731
|
-
|
|
740
|
+
const itemsDeclareObject = Array.isArray(schema.items.type) ? schema.items.type.includes("object") : schema.items.type === "object";
|
|
741
|
+
result = itemsDeclareObject && schema.items.properties ? `Array<${itemType}>` : itemType.includes("|") ? `(${itemType})[]` : `${itemType}[]`;
|
|
732
742
|
} else {
|
|
733
743
|
result = "unknown[]";
|
|
734
744
|
}
|
|
@@ -861,13 +871,14 @@ function generateBoilerplate(_userPrompt, contract, shellType, screen, composedS
|
|
|
861
871
|
const propsFields = [];
|
|
862
872
|
const propsProperties = contract?.propsSpec?.properties ?? {};
|
|
863
873
|
for (const [key, entry] of Object.entries(propsProperties)) {
|
|
864
|
-
const required = entry.required
|
|
874
|
+
const required = entry.required === true;
|
|
865
875
|
const nullable = entry.schema.nullable === true;
|
|
866
876
|
const tsType = jsonSchemaTypeToTs(entry.schema);
|
|
867
877
|
const fullType = nullable ? `${tsType} | null` : tsType;
|
|
868
878
|
const parts = [];
|
|
869
879
|
if (entry.description) parts.push(entry.description);
|
|
870
880
|
if (entry.default !== void 0) parts.push(`(default: ${JSON.stringify(entry.default)})`);
|
|
881
|
+
if (!required) parts.push("(optional \u2014 the agent may omit it; guard or fall back)");
|
|
871
882
|
const desc = parts.length > 0 ? ` // ${parts.join(" ")}` : "";
|
|
872
883
|
propsFields.push(` ${key}${required ? "" : "?"}: ${fullType};${desc}`);
|
|
873
884
|
}
|
|
@@ -1613,13 +1624,15 @@ A \`DataContract\` declares everything a GguiSession exchanges with the outside
|
|
|
1613
1624
|
|
|
1614
1625
|
## Defensive coding for absent / late-arriving data
|
|
1615
1626
|
|
|
1616
|
-
Props arrive
|
|
1627
|
+
Props arrive at mount and may be partial on first render; later prop changes reach the SAME mounted component via \`ggui_amend\` (in-place repaint) on the live channel. Stream channels start empty and fill over time. Context slots start at their declared default (often \`null\`). **Never assume a field exists before you read it.**
|
|
1617
1628
|
|
|
1629
|
+
- **Optional contract props**: a \`Props\` field marked \`?\` in the boilerplate may be omitted by the agent ENTIRELY \u2014 the wire gate only enforces \`required: true\` entries, and the harness verifies your component with those props stripped. Guard them (\`?? default\`, \`?.\`, or an empty-state branch) from the FIRST render.
|
|
1618
1630
|
- **Array iteration**: always default to \`[]\` before \`.map\`/\`.filter\`/\`.length\`. Use \`(props.items ?? []).map(...)\` not \`props.items.map(...)\`. Same for stream.all, stream.latest, etc.
|
|
1619
1631
|
- **Object access**: optional-chain through nested fields. \`props.user?.name ?? 'Anonymous'\` not \`props.user.name\`.
|
|
1620
1632
|
- **Number ops**: default before arithmetic. \`(props.count ?? 0) + 1\` not \`props.count + 1\`.
|
|
1621
1633
|
- **Stream latest**: \`useStream\` returns \`{latest: T | null, all: T[], isComplete: boolean}\`. The default \`all\` is \`[]\` so it's safe to map; \`latest\` is null until the first frame arrives \u2014 guard before reading \`.foo\`.
|
|
1622
1634
|
- **Stream reconciliation**: when a stream event carries an \`action\` discriminant (e.g. \`create | move | edit | delete\`), the channel is a CRUD feed \u2014 your handler MUST branch on EVERY value: append on \`create\`, drop on \`delete\`, replace-by-id on \`move\` / \`edit\`. Merging only the "edit" case silently loses created and deleted items. Reconcile into the SAME state that seeds from \`props\` (e.g. \`useState(() => props.tasks ?? [])\`) so the seed data and the live feed render as one list \u2014 and handle an event whose id is not yet present (a \`create\` for an unknown item) by inserting it, not ignoring it.
|
|
1635
|
+
- **Props re-sync on repaint**: props the agent owns are the source of truth for display \u2014 prefer rendering DIRECTLY from \`props\` (controlled, no local copy). When local optimistic state genuinely needs a prop seed (a click bumps a count before the agent confirms), it MUST re-sync on prop change \u2014 \`useEffect(() => setCount(props.count ?? 0), [props.count])\` \u2014 or a \`ggui_amend\` repaint applies the new props while the display keeps the stale local value. BAD: \`const [count, setCount] = useState(props.count ?? 0)\` alone. GOOD: the same seed + the one-line re-sync effect.
|
|
1623
1636
|
- **Loading state**: while data is still absent, render \`<Skeleton>\` placeholders \u2014 never a blank screen. \`<Skeleton variant="text" />\` for a text line, \`variant="circle"\` for an avatar slot, default \`rect\` for a block.
|
|
1624
1637
|
- **Empty state**: when a list or results array is empty, render \`<EmptyState title="\u2026" description="\u2026" />\` \u2014 a region that renders nothing when empty looks broken to the user.
|
|
1625
1638
|
|
|
@@ -1747,7 +1760,7 @@ Every value change is mirrored to the host LLM's context automatically (debounce
|
|
|
1747
1760
|
|
|
1748
1761
|
**When NOT to use it.** Local UI state the contract did NOT declare \u2014 \`isDropdownOpen\`, hover flags, animation phase, ephemeral toggles. For those, use a plain \`useState\` directly. The runtime ignores undeclared state.
|
|
1749
1762
|
|
|
1750
|
-
**\`contextSpec\` direction is one-way: client \u2192 agent.** The agent uses \`propsSpec\` (via \`ggui_update\`) and \`streamSpec\` (via the live channel) to push state TO the client. Don't try to write to the agent via \`contextSpec\` \u2014 there is no return path.
|
|
1763
|
+
**\`contextSpec\` direction is one-way: client \u2192 agent.** The agent uses \`propsSpec\` (via \`ggui_amend\` for in-place repaints of this mount, or \`ggui_update\` when it mints a new history card) and \`streamSpec\` (via the live channel) to push state TO the client. Don't try to write to the agent via \`contextSpec\` \u2014 there is no return path.
|
|
1751
1764
|
|
|
1752
1765
|
**Schema mismatches drop silently.** If you set a value that doesn't match the slot's schema (e.g. a string into a \`{type: 'number'}\` slot), the runtime logs a dev \`console.warn\` and skips the post. Make sure your setter calls produce values that match the declared shape.
|
|
1753
1766
|
|