@ggui-ai/ui-gen 0.18.0 → 0.20.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/dist/adapters/generation-dispatch.d.ts +1 -1
  2. package/dist/adapters/generation-dispatch.js +248 -88
  3. package/dist/adapters/generation-dispatch.js.map +1 -1
  4. package/dist/adapters/index.js +39 -16
  5. package/dist/adapters/index.js.map +1 -1
  6. package/dist/adapters/types.d.ts +1 -1
  7. package/dist/adapters/types.js.map +1 -1
  8. package/dist/advanced/index.d.ts +2 -2
  9. package/dist/advanced/index.js +217 -34
  10. package/dist/advanced/index.js.map +1 -1
  11. package/dist/blueprint-validator.js +5 -2
  12. package/dist/blueprint-validator.js.map +1 -1
  13. package/dist/boilerplate.js +2 -2
  14. package/dist/boilerplate.js.map +1 -1
  15. package/dist/check/index.d.ts +1 -1
  16. package/dist/check/index.js.map +1 -1
  17. package/dist/{create-ui-generator-C8Ki9Wz1.d.ts → create-ui-generator-CxPvudbs.d.ts} +1 -1
  18. package/dist/{dispatch-D2htzi9h.d.ts → dispatch-N8wdNOX-.d.ts} +4 -2
  19. package/dist/evaluation/axis-checks/index.d.ts +3 -3
  20. package/dist/evaluation/axis-checks/index.js +143 -3
  21. package/dist/evaluation/axis-checks/index.js.map +1 -1
  22. package/dist/evaluation/axis-checks/registry.d.ts +1 -1
  23. package/dist/evaluation/axis-checks/registry.js +138 -1
  24. package/dist/evaluation/axis-checks/registry.js.map +1 -1
  25. package/dist/evaluation/index.d.ts +3 -3
  26. package/dist/evaluation/index.js +213 -84
  27. package/dist/evaluation/index.js.map +1 -1
  28. package/dist/harness/check/runtime-render/index.d.ts +1 -1
  29. package/dist/harness/check/runtime-render/index.js +5 -2
  30. package/dist/harness/check/runtime-render/index.js.map +1 -1
  31. package/dist/harness/check/runtime-render/render-check-worker.js +5 -2
  32. package/dist/harness/check/runtime-render/render-check-worker.js.map +1 -1
  33. package/dist/harness/index.d.ts +1 -1
  34. package/dist/harness/index.js +64 -61
  35. package/dist/harness/index.js.map +1 -1
  36. package/dist/harness/prompts.js +1 -1
  37. package/dist/harness/prompts.js.map +1 -1
  38. package/dist/harness/result-types.d.ts +1 -1
  39. package/dist/harness/runtime.d.ts +1 -1
  40. package/dist/harness/runtime.js +4 -4
  41. package/dist/harness/runtime.js.map +1 -1
  42. package/dist/harness/types-public.d.ts +8 -1
  43. package/dist/harness/types-public.js +64 -61
  44. package/dist/harness/types-public.js.map +1 -1
  45. package/dist/harness/validator-trace-sink.d.ts +1 -1
  46. package/dist/index.d.ts +2 -2
  47. package/dist/index.js +217 -34
  48. package/dist/index.js.map +1 -1
  49. package/dist/patch.d.ts +1 -1
  50. package/dist/{types-public-DVSssdc-.d.ts → types-public-DKR_h70H.d.ts} +9 -0
  51. package/dist/validation/index.d.ts +1 -1
  52. package/dist/validation/index.js +1 -1
  53. package/dist/validation/index.js.map +1 -1
  54. package/dist/workflows.d.ts +1 -1
  55. package/package.json +12 -12
  56. package/src/adapters/google/sdk.ts +11 -7
  57. package/src/adapters/openai/raw.ts +11 -5
  58. package/src/adapters/openai/sdk.ts +8 -5
  59. package/src/adapters/openai/tokens.ts +49 -0
  60. package/src/adapters/types.ts +1 -1
  61. package/src/boilerplate/hard-sections.ts +1 -1
  62. package/src/boilerplate/system-prompt.ts +1 -1
  63. package/src/coding-agent/tools.ts +58 -6
  64. package/src/contract-context.ts +1 -1
  65. package/src/create-harness.ts +1 -0
  66. package/src/evaluation/axis-checks/checks/universal.ts +207 -1
  67. package/src/evaluation/axis-checks/dispatch.ts +8 -1
  68. package/src/evaluation/types-public.ts +9 -0
  69. package/src/harness/check/runtime-render/render-check.ts +15 -0
  70. package/src/harness/coding/generate-task-runner.ts +1 -0
  71. package/src/harness/coding/run-coding-turn.ts +14 -0
  72. package/src/harness/types-public.ts +7 -0
  73. package/src/run-check.ts +2 -0
  74. package/src/tools/get-wire.ts +1 -1
  75. package/src/validation/primitives.ts +1 -1
@@ -9,7 +9,7 @@ import { REGISTRY } from "./registry.js";
9
9
  import { matches, type AxisCheck, type AxisCheckInput } from "./types.js";
10
10
  import { axisCheckTraceEnabled } from "./helpers.js";
11
11
  import { createHash } from "node:crypto";
12
- import type { DesignMode } from "../../design-mode.js";
12
+ import type { CanvasClass, DesignMode } from "../../design-mode.js";
13
13
 
14
14
  export interface RunAxisChecksInput {
15
15
  sourceCode: string;
@@ -18,6 +18,8 @@ export interface RunAxisChecksInput {
18
18
  originalPrompt: string;
19
19
  /** Which triad produced the source — see `AxisCheckInput.designMode`. */
20
20
  designMode?: DesignMode;
21
+ /** The canvas the source is judged for — see `AxisCheckInput.canvas` (ggui#1117); absent = no rendering context. */
22
+ canvas?: CanvasClass;
21
23
  }
22
24
 
23
25
  export function runAxisChecks(
@@ -30,6 +32,7 @@ export function runAxisChecks(
30
32
  originalPrompt: input.originalPrompt,
31
33
  classification,
32
34
  ...(input.designMode !== undefined ? { designMode: input.designMode } : {}),
35
+ ...(input.canvas !== undefined ? { canvas: input.canvas } : {}),
33
36
  };
34
37
  if (input.compiledCode === null) {
35
38
  traceAxisChecksSkipped(facts, "compiledCode null — no check ran");
@@ -93,6 +96,8 @@ export interface AxisCheckTrace {
93
96
  readonly classification: Classification["vector"];
94
97
  readonly propsSpecKeys: readonly string[];
95
98
  readonly propsSpecPropertyKeys: readonly string[];
99
+ /** The canvas the verdict was built for (ggui#1117) — present only when the input carried one. */
100
+ readonly canvas?: CanvasClass;
96
101
  readonly matched: readonly string[];
97
102
  readonly issues: ReadonlyArray<{ readonly id: string; readonly result: EvalIssue["result"] }>;
98
103
  readonly note?: string;
@@ -107,5 +112,7 @@ function traceFacts(input: AxisTraceFacts): Omit<AxisCheckTrace, "matched" | "is
107
112
  classification: input.classification.vector,
108
113
  propsSpecKeys: Object.keys(propsSpec ?? {}),
109
114
  propsSpecPropertyKeys: Object.keys(propsSpec?.properties ?? {}),
115
+ // No default here, unlike designMode: an absent canvas is a fact about the caller, not a guess to paper over.
116
+ ...(input.canvas !== undefined ? { canvas: input.canvas } : {}),
110
117
  };
111
118
  }
@@ -280,6 +280,15 @@ export interface AxisCheckInput {
280
280
  * means `constrained`.
281
281
  */
282
282
  designMode?: DesignMode;
283
+ /**
284
+ * The rendering canvas the source is judged FOR (ggui#1117). A width cap on
285
+ * the outermost element is a defect on a fullscreen canvas and a no-op in a
286
+ * chat bubble, so a check that reads width must know the surface. Populated
287
+ * where the harness is built (`Harness.canvas`); ABSENT for callers with no
288
+ * rendering context — a check handles `undefined`, it never assumes a
289
+ * default (a silent `lg` would be the same guess with a nicer face).
290
+ */
291
+ canvas?: CanvasClass;
283
292
  }
284
293
 
285
294
  /**
@@ -45,6 +45,7 @@ import {
45
45
  type ParentLink,
46
46
  } from "./host-boundary.js";
47
47
  import { hostGlobals, openRecord } from "../../../internal/open-record.js";
48
+ import { primeInputs } from "@ggui-ai/ui-visual-tester/prime-inputs";
48
49
 
49
50
  /**
50
51
  * Module-namespace-shaped value injected into `loadComponent`'s
@@ -504,6 +505,20 @@ export async function runRenderCheckInProcess(
504
505
  let streamsChecked = 0;
505
506
 
506
507
  try {
508
+ // ggui#1187: prime form inputs before the action-wiring probe. A wired
509
+ // Send/Submit gated on a non-empty input (the chat-interface shape) is a
510
+ // no-op on empty, so an un-primed synthetic click never dispatches and
511
+ // the probe false-warns "did not dispatch it". Shared with the visual
512
+ // tester's primeInputs (#1021/#1040) so both prime identically. Priming
513
+ // sets input VALUES (not textContent) and fires input/change before the
514
+ // per-candidate fire-log window is opened, so it neither perturbs the
515
+ // prop-sensitivity text baseline nor false-positives the wiring probe.
516
+ // Best-effort: a priming failure must never abort the check.
517
+ try {
518
+ primeInputs(renderResult.container);
519
+ } catch {
520
+ // Un-primeable DOM falls back to the prior (un-primed) behavior.
521
+ }
507
522
  // ── Check 2: Action wiring (BLOCK / unverified-as-warn) ───────────
508
523
  if (input.contract?.actionSpec) {
509
524
  const actionSpec = input.contract.actionSpec;
@@ -178,6 +178,7 @@ export function createGenerateTaskRunner(input: CreateGenerateRunnerInput): Task
178
178
  systemPrompt: session.systemPrompt,
179
179
  harness: session.harness,
180
180
  contract: params.contract,
181
+ originalPrompt: params.userPrompt,
181
182
  commitMeta: session.commitMeta,
182
183
  originalProps: session.originalProps,
183
184
  costTracker: session.costTracker,
@@ -155,6 +155,13 @@ export interface CodingTurnContext {
155
155
  readonly systemPrompt: string;
156
156
  readonly harness: Harness;
157
157
  readonly contract: DataContract | undefined;
158
+ /**
159
+ * The ORIGINAL request (ggui#1122) — what the eval round has always judged
160
+ * against, now handed to auto-commit so the axis checks run on the serve
161
+ * path with the same input. Required: a caller that forgot it would silently
162
+ * run no axis checks on every served generation.
163
+ */
164
+ readonly originalPrompt: string;
158
165
  readonly commitMeta: Map<string, CommitMetadata>;
159
166
  readonly originalProps: string | undefined;
160
167
  readonly costTracker: CostTracker | null;
@@ -669,6 +676,13 @@ ${closingInstruction}`;
669
676
  // Which tier-0 legs fire — the design-vocabulary legs stand down in
670
677
  // `free` mode; every contract leg runs in both.
671
678
  harness.designMode,
679
+ // ggui#1122: the harness's pre-filtered axis checks run at auto-commit — the serve path.
680
+ {
681
+ checks: harness.check.axisChecks,
682
+ classification: harness.classification,
683
+ originalPrompt: ctx.originalPrompt,
684
+ ...(harness.canvas !== undefined ? { canvas: harness.canvas } : {}),
685
+ },
672
686
  );
673
687
  const toolMs = Date.now() - toolStart;
674
688
 
@@ -303,6 +303,13 @@ export interface Harness {
303
303
  * into `free`.
304
304
  */
305
305
  readonly designMode: DesignMode;
306
+ /**
307
+ * The rendering canvas this harness was built for (ggui#1117) — threaded
308
+ * from `CreateHarnessInput.canvas` into the HOW leg (the `free` prompt
309
+ * states it) and, via `run-check.ts`, onto every axis check's input.
310
+ * ABSENT when the caller had no rendering context; never defaulted.
311
+ */
312
+ readonly canvas?: CanvasClass;
306
313
  /** The app's generation profile in force for this harness (#991), if any. */
307
314
  readonly profile?: GenerationProfileInput;
308
315
 
package/src/run-check.ts CHANGED
@@ -119,6 +119,8 @@ export async function runCheck(input: RunCheckInput): Promise<CheckResult> {
119
119
  originalPrompt: prompt,
120
120
  classification: harness.classification,
121
121
  designMode: harness.designMode,
122
+ // ggui#1117: the canvas the harness was built for, when it has one — never a default.
123
+ ...(harness.canvas !== undefined ? { canvas: harness.canvas } : {}),
122
124
  };
123
125
  // The one runner `runAxisChecks` shares (ggui#1046 trace prints here, on the served path).
124
126
  const axisRun = runGatedAxisChecks(check.axisChecks, axisInput);
@@ -2,4 +2,4 @@
2
2
  * AUTO-GENERATED from @ggui-ai/wire JSDoc.
3
3
  * Do not edit manually. Run: pnpm --filter @ggui-ai/wire generate:docs
4
4
  */
5
- export const WIRE_DOCUMENTATION = "# ggui Wire Hooks Reference\n\n> Wire hooks connect generated UI components to agent communication.\n> They are pre-imported in the boilerplate — use them directly.\n> All hooks must be called inside a GguiWireProvider (handled automatically by the renderer).\n\nImport: `import { useAction, useStream } from '@ggui-ai/wire'`\n\n## Communication Hooks\n\n\n\nThese are the wire primitives for component-agent communication. `useWiredTool` retired 2026-05-11 — agentCapabilities.tools is a catalog the AGENT invokes, not a component hook surface; user gestures use `useAction(name)` and the optional `nextStep` field on the action entry names the tool the agent SHOULD invoke next.\n\n### useAction\n\nFire an action to the agent. Fire-and-forget — no response, no pending state.\n\nEvery action is an event: it lands on the GguiSession's consume buffer and\nthe agent receives it on its next turn (via `ggui_consume`). The action\nentry's optional `nextStep` names the tool the agent SHOULD invoke in\nresponse — advisory only; the agent owns the call decision. Component code\nis identical either way: call `useAction(name)(payload)` and treat\n`nextStep` as informational (use it to inform button labels, icons, copy).\n\nRUNTIME DEDUP (backstop, not a feature). Same-`(name, payload)` calls within\none event-loop task are coalesced — the first wins, subsequent duplicates\nare suppressed. This is the structural defense against LLM-generated\nnested-interactive components where a Checkbox `onChange` and an outer\n`Card as={Clickable}` `onClick` both wire to the same `useAction` binding;\nthe inner gesture bubbles to the outer handler, so without dedup one user\nclick would fire the action twice (a toggle would run back-to-back and the\nuser's change would disappear). **Do not rely on this as a feature** — wire\neach `useAction` callback to exactly ONE interactive surface; the dedup\nexists only because the LLM's source code can be wrong in subtle ways and\nthe runtime is the only place that can see the actual event-bubble path.\nSee `dispatch-dedup.ts` for the full failure-mode rationale.\n\nNEVER SILENT. When the dedup fires, a `console.warn` is emitted in BOTH dev\nand prod with the full diagnostic. The suppression is always visible in\nbrowser DevTools; operators investigating a \"the second click does nothing\"\nreport see the warning immediately. A structured observer also exists as\n`WireConfig.onDispatchSuppressed`, but the first-party renderers never set\nit — it is reachable only from a hand-built `WireConfig` (see its\ndocstring), so the console signal is what operators get in practice.\n\n**Signature:** `useAction<T = unknown>(actionName: string): (data: T) => void`\n\n**Parameters:**\n\n| Param | Type | Description |\n|-------|------|-------------|\n| actionName | `string` | Action name from the action contract |\n\n**Returns:** `(data: T) => void`\n\n**Example:**\n```tsx\nconst submitForm = useAction<{name: string; email: string}>('formSubmit');\n\n// In JSX:\n<Button onClick={() => submitForm({ name, email })}>Submit</Button>\n```\n\n\n### useStream\n\nSubscribe to deliveries on a named stream channel.\n\nHonors the channel's per-delivery `mode` ('append' vs 'replace')\nand the optional `complete` terminal marker. Channels declared\n`mode: 'replace'` on the spec typically emit every delivery with\n`mode: 'replace'` — this hook folds them into a single-latest\nvalue without accumulating history.\n\n**Signature:** `useStream<T = unknown>(channelName: string): StreamResult<T>`\n\n**Parameters:**\n\n| Param | Type | Description |\n|-------|------|-------------|\n| channelName | `string` | Channel name from the GguiSession's streamSpec |\n\n**Returns:** `StreamResult<T>`\n\n| Property | Type | Description |\n|----------|------|-------------|\n| latest | `T \\| null` | Most recent payload delivered on this channel, or null if none received yet. |\n| all | `T[]` | All payloads accumulated on this channel. - `mode: 'append'` deliveries are pushed to the tail (continuous stream). - `mode: 'replace'` deliveries collapse `all` to a single-element array containing the latest payload — matching the channel's \"full replacement\" semantics. |\n| isComplete | `boolean` | Truthy after the channel has delivered an envelope with `complete: true`. Subscribers flip into a \"channel closed\" rendering state based on this signal; further deliveries on a completed channel are still accumulated, since the underlying wire doesn't enforce quiescence. |\n\n**Example:**\n```tsx\nconst progress = useStream<{ percent: number; message: string }>('progress');\n\n// In JSX:\n{progress.latest && (\n <Progress value={progress.latest.percent} />\n)}\n<Text>{progress.all.length} updates received</Text>\n```\n\n\n## Context Hooks\n\n\n\nRead-only access to render, app, and auth context.\n\n### useAuth\n\nRead-only auth context. Token excluded — auth is added server-side.\n\n**Signature:** `useAuth(): AuthInfo`\n\n**Returns:** `AuthInfo`\n\n| Property | Type | Description |\n|----------|------|-------------|\n| userId | `string` | userId |\n| isAuthenticated | `boolean` | isAuthenticated |\n\n**Example:**\n```tsx\nconst auth = useAuth();\n\n// In JSX:\n{auth.isAuthenticated\n ? <Text>Welcome, user {auth.userId}</Text>\n : <Text>Please sign in</Text>\n}\n```\n\n\n### useApp\n\nRead-only app metadata.\n\n**Signature:** `useApp(): AppInfo`\n\n**Returns:** `AppInfo`\n\n| Property | Type | Description |\n|----------|------|-------------|\n| appId | `string` | appId |\n| appName | `string` | appName |\n| appDescription | `string` | appDescription |\n| appIcon | `string` | appIcon |\n\n**Example:**\n```tsx\nconst app = useApp();\n\n// In JSX:\n<Heading>{app.appName}</Heading>\n{app.appDescription && <Text>{app.appDescription}</Text>}\n```\n\n\n### useRender\n\nRead-only render context with connection status.\n\n**Signature:** `useRender(): GguiSessionInfo`\n\n**Returns:** `GguiSessionInfo`\n\n| Property | Type | Description |\n|----------|------|-------------|\n| sessionId | `string` | sessionId |\n| isConnected | `boolean` | isConnected |\n\n**Example:**\n```tsx\nconst render = useRender();\n\n// In JSX:\n<Badge variant={render.isConnected ? 'success' : 'error'}>\n {render.isConnected ? 'Connected' : 'Disconnected'}\n</Badge>\n```\n\n\n## Provider\n\n\n\nThe provider is set up automatically by the renderer. Generated components do not need to wrap themselves in a provider.\n\n### GguiWireProvider\n\nReact context provider that injects WireConfig for all wire hooks.\n\n**Props:**\n\n| Prop | Type | Description |\n|------|------|-------------|\n| config | `WireConfig` | config |\n| children | `ReactNode` | children |\n\n**Example:**\n```tsx\n<GguiWireProvider config={wireConfig}>\n <YourComponent />\n</GguiWireProvider>\n```\n\n\n## Internal: WireConfig\n\n\n\nThis is the configuration object passed to GguiWireProvider. Generated components do not interact with this directly — it is provided by the renderer.\n\n### WireConfig\n\nConfiguration injected by the provider — the renderer inside the\niframe.\n\nEvery method is typed against the contract generic `T` so typed\ncallers get compile-time enforcement:\n - `dispatch(name, data)` — `name` MUST be a declared actionSpec\n key; `data` MUST satisfy that action's schema.\n - `subscribe(channel, handler)` — same discipline for streamSpec.\n\nUntyped callers (`T = DataContract` default) degrade to the broad\nshape via the conditional `WireDispatchData` / `WireStreamPayload`\naliases — no call-site break.\n\nThe contract's `agentCapabilities.tools` catalog declares tools the AGENT\ninvokes (not the component); user gestures fire via\n`dispatch(name, data)` and the optional `nextStep` field on the\naction entry names the tool the agent SHOULD invoke next.\n\nThe renderer mounts exactly ONE GguiSession per iframe — `render.sessionId`\nis the stable identity the WireProvider was constructed with. No\nper-item scoping factory; with a one-GguiSession-per-mount lifecycle,\n\"scope\" collapses to identity.\n\n| Property | Type | Description |\n|----------|------|-------------|\n| app | `{ readonly appId: string; readonly appName: string; readonly appDescription?: string; readonly appIcon?: string; }` | app |\n| render | `{ readonly sessionId: string; readonly isConnected: boolean; }` | render |\n| auth | `{ readonly userId?: string; readonly isAuthenticated: boolean; }` | auth |\n| dispatch | `<N extends string>( actionName: N, data: WireDispatchData<T, N>, ) => void` | Fire an action to the agent (fire-and-forget over WS). Typed callers get compile-time checked `name` + `data`; untyped callers (`T = DataContract`) keep the broad shape. |\n| subscribe | `<N extends string>( channelName: N, handler: (delivery: StreamDelivery<WireStreamPayload<T, N>>) => void, ) => () => void` | Subscribe to deliveries on a named stream channel. |\n| onDispatchSuppressed | `(info: DispatchSuppressedInfo) => void` | Optional structured observability for `useAction`'s task-scoped duplicate-dispatch suppression. Fires alongside the always-on `console.warn` whenever the runtime coalesces a same-(name, payload) re-dispatch within one event-loop task — the nested- interactive double-fire backstop. Reachability constraint: the first-party renderers do NOT set this field — `buildRootWireConfig` (`@ggui-ai/iframe-runtime`) and `<GguiRender>`'s internal config both omit it. It fires only when a host hand-builds a complete `WireConfig` and mounts `GguiWireProvider` itself. On every first-party render path the `console.warn` is the sole suppression signal. |\n";
5
+ export const WIRE_DOCUMENTATION = "# ggui Wire Hooks Reference\n\n> Wire hooks connect generated UI components to agent communication.\n> They are pre-imported in the boilerplate — use them directly.\n> All hooks must be called inside a GguiWireProvider (handled automatically by the renderer).\n\nImport: `import { useAction, useStream } from '@ggui-ai/wire'`\n\n## Communication Hooks\n\n\n\nThese are the wire primitives for component-agent communication. `useWiredTool` retired 2026-05-11 — agentCapabilities.tools is a catalog the AGENT invokes, not a component hook surface; user gestures use `useAction(name)` and the optional `nextStep` field on the action entry names the tool the agent SHOULD invoke next.\n\n### useAction\n\nFire an action to the agent. Fire-and-forget — no response, no pending state.\n\nEvery action is an event: it lands on the GguiSession's consume buffer and\nthe agent receives it on its next turn (via `ggui_consume`). The action\nentry's optional `nextStep` names the tool the agent SHOULD invoke in\nresponse — advisory only; the agent owns the call decision. Component code\nis identical either way: call `useAction(name)(payload)` and treat\n`nextStep` as informational (use it to inform button labels, icons, copy).\n\nRUNTIME DEDUP (backstop, not a feature). Same-`(name, payload)` calls within\none event-loop task are coalesced — the first wins, subsequent duplicates\nare suppressed. This is the structural defense against LLM-generated\nnested-interactive components where a Checkbox `onChange` and an outer\n`Card as={Clickable}` `onClick` both wire to the same `useAction` binding;\nthe inner gesture bubbles to the outer handler, so without dedup one user\nclick would fire the action twice (a toggle would run back-to-back and the\nuser's change would disappear). **Do not rely on this as a feature** — wire\neach `useAction` callback to exactly ONE interactive surface; the dedup\nexists only because the LLM's source code can be wrong in subtle ways and\nthe runtime is the only place that can see the actual event-bubble path.\nSee `dispatch-dedup.ts` for the full failure-mode rationale.\n\nNEVER SILENT. When the dedup fires, a `console.warn` is emitted in BOTH dev\nand prod with the full diagnostic. The suppression is always visible in\nbrowser DevTools; operators investigating a \"the second click does nothing\"\nreport see the warning immediately. A structured observer also exists as\n`WireConfig.onDispatchSuppressed`, but the first-party renderers never set\nit — it is reachable only from a hand-built `WireConfig` (see its\ndocstring), so the console signal is what operators get in practice.\n\n**Signature:** `useAction<T = unknown>(actionName: string): (data: T) => void`\n\n**Parameters:**\n\n| Param | Type | Description |\n|-------|------|-------------|\n| actionName | `string` | Action name from the action contract |\n\n**Returns:** `(data: T) => void`\n\n**Example:**\n```tsx\nconst submitForm = useAction<{name: string; email: string}>('formSubmit');\n\n// In JSX:\n<Button onClick={() => submitForm({ name, email })}>Submit</Button>\n```\n\n\n### useStream\n\nSubscribe to deliveries on a named stream channel.\n\nHonors the channel's per-delivery `mode` ('append' vs 'replace')\nand the optional `complete` terminal marker. Channels declared\n`mode: 'replace'` on the spec typically emit every delivery with\n`mode: 'replace'` — this hook folds them into a single-latest\nvalue without accumulating history.\n\n**Signature:** `useStream<T = unknown>(channelName: string): StreamResult<T>`\n\n**Parameters:**\n\n| Param | Type | Description |\n|-------|------|-------------|\n| channelName | `string` | Channel name from the GguiSession's streamSpec |\n\n**Returns:** `StreamResult<T>`\n\n| Property | Type | Description |\n|----------|------|-------------|\n| latest | `T \\| null` | Most recent payload delivered on this channel, or null if none received yet. |\n| all | `T[]` | All payloads accumulated on this channel. - `mode: 'append'` deliveries are pushed to the tail (continuous stream). - `mode: 'replace'` deliveries collapse `all` to a single-element array containing the latest payload — matching the channel's \"full replacement\" semantics. |\n| isComplete | `boolean` | Truthy after the channel has delivered an envelope with `complete: true`. Subscribers flip into a \"channel closed\" rendering state based on this signal; further deliveries on a completed channel are still accumulated, since the underlying wire doesn't enforce quiescence. |\n\n**Example:**\n```tsx\nconst progress = useStream<{ percent: number; message: string }>('progress');\n\n// In JSX:\n{progress.latest && (\n <Progress value={progress.latest.percent} />\n)}\n<Text>{progress.all.length} updates received</Text>\n```\n\n\n## Context Hooks\n\n\n\nRead-only access to render, app, and auth context.\n\n### useAuth\n\nRead-only auth context. Token excluded — auth is added server-side.\n\n**Signature:** `useAuth(): AuthInfo`\n\n**Returns:** `AuthInfo`\n\n| Property | Type | Description |\n|----------|------|-------------|\n| userId | `string` | userId |\n| isAuthenticated | `boolean` | isAuthenticated |\n\n**Example:**\n```tsx\nconst auth = useAuth();\n\n// In JSX:\n{auth.isAuthenticated\n ? <Text>Welcome, user {auth.userId}</Text>\n : <Text>Please sign in</Text>\n}\n```\n\n\n### useApp\n\nRead-only app metadata.\n\n**Signature:** `useApp(): AppInfo`\n\n**Returns:** `AppInfo`\n\n| Property | Type | Description |\n|----------|------|-------------|\n| appId | `string` | appId |\n| appName | `string` | appName |\n| appDescription | `string` | appDescription |\n| appIcon | `string` | appIcon |\n\n**Example:**\n```tsx\nconst app = useApp();\n\n// In JSX:\n<Heading>{app.appName}</Heading>\n{app.appDescription && <Text>{app.appDescription}</Text>}\n```\n\n\n### useRender\n\nRead-only render context with connection status.\n\n**Signature:** `useRender(): GguiSessionInfo`\n\n**Returns:** `GguiSessionInfo`\n\n| Property | Type | Description |\n|----------|------|-------------|\n| sessionId | `string` | sessionId |\n| isConnected | `boolean` | isConnected |\n\n**Example:**\n```tsx\nconst render = useRender();\n\n// In JSX:\n<Badge variant={render.isConnected ? 'success' : 'error'}>\n {render.isConnected ? 'Connected' : 'Disconnected'}\n</Badge>\n```\n\n\n## Provider\n\n\n\nThe provider is set up automatically by the renderer. Generated components do not need to wrap themselves in a provider.\n\n### GguiWireProvider\n\nReact context provider that injects WireConfig for all wire hooks.\n\n**Props:**\n\n| Prop | Type | Description |\n|------|------|-------------|\n| config | `WireConfig` | config |\n| children | `ReactNode` | children |\n\n**Example:**\n```tsx\n<GguiWireProvider config={wireConfig}>\n <YourComponent />\n</GguiWireProvider>\n```\n\n\n## Internal: WireConfig\n\n\n\nThis is the configuration object passed to GguiWireProvider. Generated components do not interact with this directly — it is provided by the renderer.\n\n### WireConfig\n\nConfiguration injected by the provider — the renderer inside the\niframe.\n\nEvery method is typed against the contract generic `T` so typed\ncallers get compile-time enforcement:\n - `dispatch(name, data)` — `name` MUST be a declared actionSpec\n key; `data` MUST satisfy that action's schema.\n - `subscribe(channel, handler)` — same discipline for streamSpec.\n\nUntyped callers (`T = DataContract` default) degrade to the broad\nshape via the conditional `WireDispatchData` / `WireStreamPayload`\naliases — no call-site break.\n\nThe contract's `agentCapabilities.tools` catalog declares tools the AGENT\ninvokes (not the component); user gestures fire via\n`dispatch(name, data)` and the optional `nextStep` field on the\naction entry names the tool the agent SHOULD invoke next.\n\nThe renderer mounts exactly ONE GguiSession per iframe — `render.sessionId`\nis the stable identity the WireProvider was constructed with. No\nper-item scoping factory; with a one-GguiSession-per-mount lifecycle,\n\"scope\" collapses to identity.\n\n| Property | Type | Description |\n|----------|------|-------------|\n| app | `{ readonly appId: string; readonly appName: string; readonly appDescription?: string; readonly appIcon?: string; }` | app |\n| render | `{ readonly sessionId: string; readonly isConnected: boolean; }` | render |\n| auth | `{ readonly userId?: string; readonly isAuthenticated: boolean; }` | auth |\n| dispatch | `<N extends string>( actionName: N, data: WireDispatchData<T, N>, ) => void` | Fire an action to the agent (fire-and-forget over WS). Typed callers get compile-time checked `name` + `data`; untyped callers (`T = DataContract`) keep the broad shape. |\n| subscribe | `<N extends string>( channelName: N, handler: (delivery: StreamDelivery<WireStreamPayload<T, N>>) => void, ) => () => void` | Subscribe to deliveries on a named stream channel. |\n| onDispatchSuppressed | `(info: DispatchSuppressedInfo) => void` | Optional structured observability for the runtime's two dispatch- suppression invariants — the task-scoped duplicate backstop (`reason: 'duplicate-dispatch'`, fired by `useAction`) and the render-lifetime one-shot guard (`reason: 'one-shot-spent'`, fired by the config's own `dispatch` when a declared `oneShot` action is gestured a second time, ggui#1108). Both fire ALONGSIDE the always-on `console.warn`; neither reaches the agent. Reachability constraint: the first-party renderers do NOT set this field — `buildRootWireConfig` (`@ggui-ai/iframe-runtime`) and `<GguiRender>`'s internal config both omit it. It fires only when a host hand-builds a complete `WireConfig` (or passes `onDispatchSuppressed` to `buildWireConfig`). On every first-party render path the `console.warn` is the sole suppression signal — which is what the `oneShot` contract requires as its minimum trace. |\n";