@ggui-ai/iframe-runtime 0.1.0-rc.3 → 0.2.0-alpha.1

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 (83) hide show
  1. package/README.md +7 -6
  2. package/dist/channel-transport.d.ts +15 -15
  3. package/dist/channel-transport.js +32 -35
  4. package/dist/channels/channel-payload.d.ts +2 -2
  5. package/dist/channels/channel-payload.js +1 -1
  6. package/dist/channels/data.d.ts +14 -19
  7. package/dist/channels/data.js +13 -31
  8. package/dist/channels/drain-ack.d.ts +2 -2
  9. package/dist/channels/drain-ack.js +1 -1
  10. package/dist/channels/feedback.d.ts +1 -1
  11. package/dist/channels/index.d.ts +1 -1
  12. package/dist/channels/index.js +1 -1
  13. package/dist/channels/props-update.d.ts +33 -37
  14. package/dist/channels/props-update.js +30 -106
  15. package/dist/channels/render-frame.d.ts +52 -0
  16. package/dist/channels/render-frame.js +53 -0
  17. package/dist/channels/system.d.ts +1 -1
  18. package/dist/compiled-validators.d.ts +15 -0
  19. package/dist/compiled-validators.js +31 -0
  20. package/dist/context-observer.d.ts +68 -8
  21. package/dist/context-observer.js +25 -45
  22. package/dist/events-polling.d.ts +95 -0
  23. package/dist/events-polling.js +143 -0
  24. package/dist/gadget-loader.d.ts +1 -1
  25. package/dist/globals.d.ts +4 -4
  26. package/dist/host-context-emitter.d.ts +84 -20
  27. package/dist/host-context-emitter.js +159 -29
  28. package/dist/iframe-runtime.js +98 -59
  29. package/dist/index.d.ts +18 -13
  30. package/dist/index.js +1 -1
  31. package/dist/lifecycle.d.ts +2 -2
  32. package/dist/lifecycle.js +5 -5
  33. package/dist/mcp-app-iframe-host.d.ts +9 -9
  34. package/dist/mcp-app-iframe-host.js +16 -17
  35. package/dist/meta-parse.d.ts +44 -0
  36. package/dist/meta-parse.js +390 -0
  37. package/dist/observability.d.ts +6 -6
  38. package/dist/protocol-error.d.ts +7 -7
  39. package/dist/provisional-renderer.d.ts +1 -1
  40. package/dist/provisional-renderer.js +1 -1
  41. package/dist/react-renderer.d.ts +10 -11
  42. package/dist/react-renderer.js +8 -8
  43. package/dist/registry-subscribe.d.ts +27 -9
  44. package/dist/registry-subscribe.js +32 -27
  45. package/dist/render-item.d.ts +83 -0
  46. package/dist/{stack-item-renderer.js → render-item.js} +106 -106
  47. package/dist/runtime.d.ts +183 -131
  48. package/dist/runtime.js +1264 -1330
  49. package/dist/status-dom.d.ts +52 -47
  50. package/dist/status-dom.js +51 -34
  51. package/dist/system-cards/NoCredentialsCard.d.ts +1 -1
  52. package/dist/system-cards/NoCredentialsCard.js +1 -1
  53. package/dist/system-cards/ProtocolProbeCard.js +2 -2
  54. package/dist/system-cards/index.d.ts +4 -4
  55. package/dist/system-cards/index.js +4 -4
  56. package/dist/types.d.ts +70 -34
  57. package/dist/types.js +1 -1
  58. package/dist/validation.d.ts +2 -2
  59. package/dist/validation.js +2 -2
  60. package/dist/wire-config.d.ts +54 -82
  61. package/dist/wire-config.js +61 -93
  62. package/package.json +9 -8
  63. package/dist/bootstrap.d.ts +0 -72
  64. package/dist/bootstrap.js +0 -500
  65. package/dist/canvas/animator/animator-host.d.ts +0 -63
  66. package/dist/canvas/animator/animator-host.js +0 -105
  67. package/dist/canvas/animator/animator-pill.d.ts +0 -42
  68. package/dist/canvas/animator/animator-pill.js +0 -119
  69. package/dist/canvas/animator/state-machine.d.ts +0 -106
  70. package/dist/canvas/animator/state-machine.js +0 -150
  71. package/dist/canvas/canvas-shell.d.ts +0 -77
  72. package/dist/canvas/canvas-shell.js +0 -117
  73. package/dist/canvas/display-mode-policy.d.ts +0 -104
  74. package/dist/canvas/display-mode-policy.js +0 -98
  75. package/dist/canvas/mount.d.ts +0 -150
  76. package/dist/canvas/mount.js +0 -202
  77. package/dist/canvas/nav-stack.d.ts +0 -133
  78. package/dist/canvas/nav-stack.js +0 -172
  79. package/dist/channels/push.d.ts +0 -47
  80. package/dist/channels/push.js +0 -54
  81. package/dist/stack-item-renderer.d.ts +0 -103
  82. package/dist/stack.d.ts +0 -90
  83. package/dist/stack.js +0 -118
@@ -1,172 +0,0 @@
1
- /**
2
- * NavStackModel.
3
- *
4
- * Canvas-local navigation stack. **Distinct from the server's
5
- * `StackModel`**:
6
- *
7
- * - Server's `StackModel` (`@ggui-ai/iframe-runtime/stack.ts`)
8
- * tracks every stack item the agent has pushed in this session.
9
- * The durable shape; mutated only by server-side push/pop/replace
10
- * deliveries.
11
- * - NavStackModel tracks the USER'S NAVIGATION HISTORY through
12
- * those items. When the user back-navigates, the popped item
13
- * leaves navStack but stays in server's StackModel.
14
- *
15
- * These can diverge:
16
- *
17
- * ```
18
- * server StackModel: [a, b, c] ← all items the agent has pushed
19
- * navStack: [a, b] ← user has back-navigated away from c
20
- * activeItem: b ← top of navStack; the mounted screen
21
- * ```
22
- *
23
- * The server-side `activeStackItemId` field (on AckPayload +
24
- * SessionRecord) follows navStack-top via `canvas_navigated` outbound
25
- * envelopes, so `ggui_consume` returns actions for the active item
26
- * regardless of what the agent has pushed since.
27
- *
28
- * Pure model — no DOM, no React. Methods return the new state (or
29
- * mutate in-place; v1 mutates for simplicity since the canvas shell
30
- * holds the only reference). The renderer consumes the model via a
31
- * version counter (bumped on every mutation) so React picks up
32
- * changes without deep equality.
33
- *
34
- * Two listener seams (distinct semantics):
35
- *
36
- * - `onMutation` fires on EVERY state mutation including in-place
37
- * replace (ggui_update patch). The canvas shell uses this to
38
- * re-read the version counter and re-render — without it, a
39
- * props-only update on the active item bumps the version but
40
- * React never schedules a render and the screen shows stale props.
41
- * - `onNavChange` fires only when the active item actually changes
42
- * (new push, pop, reset that lands on a different top). The
43
- * live-channel `canvas_navigated` envelope is emitted from here, so
44
- * an in-place replace doesn't spuriously tell the server the
45
- * user back-navigated.
46
- */
47
- export class NavStackModel {
48
- items = [];
49
- mutationListener = null;
50
- navChangeListener = null;
51
- versionCounter = 0;
52
- /**
53
- * Replace the entire nav stack. Used on subscribe ack to seed
54
- * navStack from the server's `activeStackItemId` + the snapshot's
55
- * stack array.
56
- */
57
- reset(items) {
58
- const previous = this.peek();
59
- this.items = [...items];
60
- this.versionCounter += 1;
61
- this.mutationListener?.();
62
- this.navChangeListener?.({
63
- direction: 'forward',
64
- activeItemId: this.peek()?.id ?? null,
65
- previousActiveItemId: previous?.id ?? null,
66
- });
67
- }
68
- /**
69
- * Append an item to the top. Called when a `stack_item_appended`
70
- * envelope arrives (server pushed a new item).
71
- *
72
- * If an item with the same id already exists, the existing entry is
73
- * REPLACED IN PLACE (preserves nav position) — matches the server-
74
- * side StackModel's upsert semantics so `ggui_update` patches don't
75
- * shift the user's nav state. The mutation listener still fires (so
76
- * React re-renders with the new props) but the nav-change listener
77
- * is suppressed (active item hasn't changed).
78
- */
79
- push(item) {
80
- const existingIdx = this.items.findIndex((i) => i.id === item.id);
81
- if (existingIdx >= 0) {
82
- this.items[existingIdx] = item;
83
- this.versionCounter += 1;
84
- this.mutationListener?.();
85
- return;
86
- }
87
- const previous = this.peek();
88
- this.items.push(item);
89
- this.versionCounter += 1;
90
- this.mutationListener?.();
91
- this.navChangeListener?.({
92
- direction: 'forward',
93
- activeItemId: item.id,
94
- previousActiveItemId: previous?.id ?? null,
95
- });
96
- }
97
- /**
98
- * Pop the top item. Returns the new active item (or null if the
99
- * stack is now empty). No-op if already empty.
100
- */
101
- pop() {
102
- if (this.items.length === 0)
103
- return null;
104
- const popped = this.items.pop() ?? null;
105
- this.versionCounter += 1;
106
- const active = this.peek();
107
- this.mutationListener?.();
108
- this.navChangeListener?.({
109
- direction: 'back',
110
- activeItemId: active?.id ?? null,
111
- previousActiveItemId: popped?.id ?? null,
112
- });
113
- return active;
114
- }
115
- /** Top of the nav stack (the currently-mounted item), or null when empty. */
116
- peek() {
117
- return this.items[this.items.length - 1] ?? null;
118
- }
119
- /** Read-only snapshot of the full nav stack in order (root → top). */
120
- snapshot() {
121
- return this.items;
122
- }
123
- /** Item count. */
124
- size() {
125
- return this.items.length;
126
- }
127
- /**
128
- * Monotonic mutation counter — bumped on every push/pop/reset.
129
- * React consumers can use this as the `useSyncExternalStore`
130
- * snapshot value: comparing version numbers is O(1).
131
- */
132
- version() {
133
- return this.versionCounter;
134
- }
135
- /**
136
- * Install a listener that fires on every state mutation (push, pop,
137
- * reset, in-place replace). React consumers bridge this to a
138
- * version-counter `useState` so the component re-renders for every
139
- * change — including in-place replaces from `ggui_update` patches
140
- * where the active item is unchanged but its props have moved.
141
- *
142
- * Single-subscriber model — the canvas shell is the only caller.
143
- * Returns an unsubscribe function.
144
- */
145
- onMutation(listener) {
146
- this.mutationListener = listener;
147
- return () => {
148
- if (this.mutationListener === listener)
149
- this.mutationListener = null;
150
- };
151
- }
152
- /**
153
- * Install a listener that fires only on nav-changing mutations
154
- * (push of new id, pop, reset). Used by the canvas shell to emit
155
- * the live-channel `canvas_navigated` envelope so the server can
156
- * update `activeStackItemId` and abort in-flight cold-gen for the
157
- * previous active item.
158
- *
159
- * In-place replaces from `ggui_update` patches do NOT fire this
160
- * listener — the active item hasn't moved. Use {@link onMutation}
161
- * for React re-render signaling.
162
- *
163
- * Single-subscriber model. Returns an unsubscribe function.
164
- */
165
- onNavChange(listener) {
166
- this.navChangeListener = listener;
167
- return () => {
168
- if (this.navChangeListener === listener)
169
- this.navChangeListener = null;
170
- };
171
- }
172
- }
@@ -1,47 +0,0 @@
1
- /**
2
- * `push` channel handler — folds an inbound `push` frame into the
3
- * stack model, re-renders the placeholder DOM, refreshes the status
4
- * line, then (when triad wiring is present) applies the new stack
5
- * through the React renderer + activates the channel-transport router
6
- * against the new top of stack.
7
- *
8
- * The placeholder side-effects (stack model upsert + DOM render +
9
- * status update) are unconditional — they're load-bearing for the
10
- * C7a-era specs that boot WITHOUT triad wiring (boot.test.ts in
11
- * particular). Triad-only consumers (production runtime) pass the
12
- * `getStackRenderer` + `getChannelTransport` thunks; tests that
13
- * exercise the placeholder path only leave them off.
14
- *
15
- * Pre-B3b: the placeholder fold lived in a separate
16
- * `handleServerMessage` call in `runtime.ts`, fired before triad
17
- * dispatch. B3b absorbed both into this single handler so the
18
- * registry-owned WS transport is the sole dispatch site.
19
- */
20
- import type { ChannelHandler } from '@ggui-ai/channel-client';
21
- import type { PushPayload } from '@ggui-ai/protocol';
22
- import type { ChannelTransportRouter } from '../channel-transport.js';
23
- import type { StackRenderer } from '../stack-item-renderer.js';
24
- import type { StackModel } from '../stack.js';
25
- import { type StatusRefs } from '../status-dom.js';
26
- export interface PushHandlerDeps {
27
- readonly stackModel: StackModel;
28
- /**
29
- * Status DOM refs the placeholder fold updates. Required — even
30
- * triad-mode consumers want the status text to reflect the live
31
- * count for operator-visible boot debugging.
32
- */
33
- readonly statusRefs: StatusRefs;
34
- /**
35
- * Triad-mode hook — returns the React stack renderer. Absent in
36
- * placeholder-only mode (tests, and callers that don't mount the
37
- * React stack).
38
- */
39
- readonly getStackRenderer?: () => StackRenderer;
40
- /**
41
- * Triad-mode hook — returns the per-channel transport router so the
42
- * push handler can activate / tear down `source.tool` channels on
43
- * the new top of stack. Absent in placeholder-only mode.
44
- */
45
- readonly getChannelTransport?: () => ChannelTransportRouter;
46
- }
47
- export declare function createPushHandler(deps: PushHandlerDeps): ChannelHandler<PushPayload>;
@@ -1,54 +0,0 @@
1
- /**
2
- * `push` channel handler — folds an inbound `push` frame into the
3
- * stack model, re-renders the placeholder DOM, refreshes the status
4
- * line, then (when triad wiring is present) applies the new stack
5
- * through the React renderer + activates the channel-transport router
6
- * against the new top of stack.
7
- *
8
- * The placeholder side-effects (stack model upsert + DOM render +
9
- * status update) are unconditional — they're load-bearing for the
10
- * C7a-era specs that boot WITHOUT triad wiring (boot.test.ts in
11
- * particular). Triad-only consumers (production runtime) pass the
12
- * `getStackRenderer` + `getChannelTransport` thunks; tests that
13
- * exercise the placeholder path only leave them off.
14
- *
15
- * Pre-B3b: the placeholder fold lived in a separate
16
- * `handleServerMessage` call in `runtime.ts`, fired before triad
17
- * dispatch. B3b absorbed both into this single handler so the
18
- * registry-owned WS transport is the sole dispatch site.
19
- */
20
- import { refreshStackDom, setConnectedStatus, } from '../status-dom.js';
21
- export function createPushHandler(deps) {
22
- return {
23
- type: 'push',
24
- onMessage: async (payload) => {
25
- // 1. Placeholder fold — unconditional. Mirrors the pre-B3b
26
- // handleServerMessage path so non-triad consumers see the
27
- // correct stack count + placeholder DOM.
28
- deps.stackModel.upsert(payload.stackItem);
29
- refreshStackDom(deps.statusRefs, deps.stackModel);
30
- setConnectedStatus(deps.statusRefs, deps.stackModel);
31
- // 2. Triad-mode work — only fires when the production runtime
32
- // supplied the renderer + transport thunks.
33
- const stackRenderer = deps.getStackRenderer?.();
34
- const channelTransport = deps.getChannelTransport?.();
35
- if (stackRenderer === undefined)
36
- return;
37
- const snapshot = deps.stackModel.snapshot();
38
- await stackRenderer.applyStack(snapshot);
39
- if (channelTransport === undefined)
40
- return;
41
- const top = snapshot[snapshot.length - 1];
42
- if (top !== undefined &&
43
- top.type !== 'mcpApps' &&
44
- top.type !== 'system') {
45
- channelTransport.applyStackItem({
46
- stackItemId: top.id,
47
- ...(top.streamSpec !== undefined
48
- ? { streamSpec: top.streamSpec }
49
- : {}),
50
- });
51
- }
52
- },
53
- };
54
- }
@@ -1,103 +0,0 @@
1
- import type { ReactNode } from 'react';
2
- import type { SessionStackEntry } from '@ggui-ai/protocol';
3
- import { type WireConfig } from '@ggui-ai/wire';
4
- import type { StreamBus } from './wire-config.js';
5
- export interface StackItemRendererOptions {
6
- /** The stack item to render. */
7
- readonly stackItem: SessionStackEntry;
8
- /** Wire config for this item — pre-scoped via the renderer's
9
- * `buildScopedConfig(item)` (see
10
- * `wire-config.ts::RootWireConfigBundle`). */
11
- readonly scopedWireConfig: WireConfig | null;
12
- /** Shared stream bus — provisional renderer subscribes to
13
- * `_ggui:preview` for this item. */
14
- readonly streamBus: StreamBus;
15
- /** Session id — used by the mcp-apps iframe host for proxy URLs. */
16
- readonly sessionId: string;
17
- /** ggui-server base URL (for mcp-apps proxy). Empty = same-origin. */
18
- readonly mcpAppsServerBaseUrl?: string;
19
- /** Theme id passed to ReactComponentRenderer for scoped CSS. */
20
- readonly themeId?: string;
21
- /**
22
- * Theme color mode. Resolves the dark variant of `themeId` when set
23
- * to `'dark'`. Falls through to `'light'` if absent — matches the
24
- * design registry's `getTheme(id, mode?)` default.
25
- */
26
- readonly themeMode?: 'light' | 'dark';
27
- readonly cssOverrides?: string;
28
- readonly onError?: (err: Error) => void;
29
- readonly onRequestRepair?: (err: Error) => void;
30
- /**
31
- * Optional outer wrapper applied AROUND the GguiWireProvider — used
32
- * by `bootProduction` to install the `<ContextStateHost>` provider
33
- * tree. Composition order:
34
- *
35
- * wrapOuter(<GguiWireProvider><userComponent/></GguiWireProvider>)
36
- *
37
- * `wrapOuter` returns the wrapped node; passing `undefined` is
38
- * equivalent to the identity function (no extra wrapping). The
39
- * self-contained boot path inlines its own ContextStateHost via the
40
- * top-level `renderWrapper`; this seam exists for the WS-driven
41
- * triad path where the per-item React mount is the only mount
42
- * surface.
43
- */
44
- readonly wrapOuter?: (mountedTree: ReactNode) => ReactNode;
45
- }
46
- /**
47
- * Discriminator for WHICH renderer is currently mounted under this
48
- * stack-item handle. Transitions: 'provisional' → 'react' when
49
- * componentCode lands; 'react' → 'react' on code/prop updates;
50
- * 'mcpApps' is terminal for its lifetime (mcp-apps items never
51
- * become components + vice versa).
52
- */
53
- type MountedKind = 'provisional' | 'react' | 'mcpApps' | 'none';
54
- export interface StackItemHandle {
55
- /**
56
- * Apply an update to the stack-item handle. Kind changes (e.g.
57
- * provisional → react when componentCode lands) tear down the old
58
- * mount and stand up the replacement; same-kind updates route to
59
- * the inner renderer's `update()`.
60
- */
61
- update(next: StackItemRendererOptions): Promise<void>;
62
- /** Tear down the active mount + unsubscribe from the StreamBus. */
63
- unmount(): void;
64
- /** Current render kind. Test-only — not load-bearing. */
65
- readonly kind: MountedKind;
66
- }
67
- export declare function renderStackItem(container: HTMLElement, opts: StackItemRendererOptions): Promise<StackItemHandle>;
68
- export interface StackRenderContext {
69
- readonly containerFor: (stackItemId: string) => HTMLElement;
70
- readonly getScopedWireConfig: (item: SessionStackEntry) => WireConfig | null;
71
- readonly streamBus: StreamBus;
72
- readonly sessionId: string;
73
- readonly mcpAppsServerBaseUrl?: string;
74
- readonly themeId?: string;
75
- /** Theme color mode — see {@link StackItemRendererOptions.themeMode}. */
76
- readonly themeMode?: 'light' | 'dark';
77
- readonly onError?: (err: Error) => void;
78
- readonly onRequestRepair?: (err: Error) => void;
79
- /**
80
- * Optional outer wrapper for each stack-item React mount — see
81
- * {@link StackItemRendererOptions.wrapOuter}. `bootProduction` uses
82
- * this to inject `<ContextStateHost>` around every per-item React
83
- * root, mirroring the wrap `bootSelfContained` performs at its
84
- * single React root. Returning the input unchanged is equivalent
85
- * to passing `undefined`.
86
- */
87
- readonly getOuterWrapper?: (item: SessionStackEntry) => ((mountedTree: ReactNode) => ReactNode) | undefined;
88
- }
89
- /**
90
- * Minimal stack-level orchestrator — keyed by stackItem.id, mounts
91
- * new items, updates existing ones, unmounts removed ones.
92
- *
93
- * The production renderer tree uses this; tests can drive the
94
- * individual `renderStackItem` entry point directly.
95
- */
96
- export declare class StackRenderer {
97
- private readonly ctx;
98
- private handles;
99
- constructor(ctx: StackRenderContext);
100
- applyStack(stack: readonly SessionStackEntry[]): Promise<void>;
101
- unmountAll(): void;
102
- }
103
- export {};
package/dist/stack.d.ts DELETED
@@ -1,90 +0,0 @@
1
- /**
2
- * Stack state + structural-placeholder DOM rendering.
3
- *
4
- * This module owns the placeholder render path: each stack item is
5
- * drawn as a structural placeholder showing id + description/prompt.
6
- * The React mount path that executes `componentCode` lives elsewhere
7
- * and swaps in over the same DOM seam.
8
- *
9
- * The model + the DOM render are split so:
10
- * - `StackModel` is testable in isolation (no DOM needed for unit
11
- * tests around upsert / replace / clear semantics);
12
- * - `renderStackInto(el, model)` is the only place that touches DOM,
13
- * making it the seam the React mount path swaps out without
14
- * disturbing the WS message handler.
15
- *
16
- * Stack ordering follows the wire shape: the ack's initial `stack` is
17
- * displayed in array order; each `push` either replaces an item with
18
- * the same id (preserving its position) or appends at the tail.
19
- */
20
- import type { SessionStackEntry } from '@ggui-ai/protocol';
21
- /**
22
- * Construction options for {@link StackModel}.
23
- *
24
- * @public
25
- */
26
- export interface StackModelOptions {
27
- /**
28
- * When present, the model renders only the single stack entry with
29
- * this `id` — the rest of the session stack is filtered out on
30
- * `setAll` and `upsert` ignores every entry whose id ≠ this value.
31
- * Enables single-item renderer mode, driven by the
32
- * `bootstrap.stackItemId` field set on a per-item session
33
- * resource.
34
- *
35
- * Absent → multi-item mode: the model renders the entire stack as
36
- * the server delivers it.
37
- *
38
- * The filter is immutable for a model's lifetime (no setter).
39
- * Consumers that need a different filter MUST construct a new
40
- * model — matches the "model is the truth" doctrine below and
41
- * makes filter-change intent explicit in code review.
42
- */
43
- readonly filterToItemId?: string;
44
- }
45
- /**
46
- * In-memory model of the visible stack. Owned by the runtime; mutated
47
- * via the methods (no setter — keeps the "model is the truth" doctrine
48
- * obvious in code reviews).
49
- */
50
- export declare class StackModel {
51
- private items;
52
- private readonly filterToItemId;
53
- constructor(options?: StackModelOptions);
54
- /**
55
- * Replace the entire stack — used after the subscribe ack lands.
56
- *
57
- * Under `filterToItemId` (single-item mode), the incoming list is
58
- * filtered to just the matching entry. If no entry matches, the
59
- * model ends up empty — the server no longer includes the pinned
60
- * item and the renderer's empty-state is the correct surface.
61
- */
62
- setAll(items: readonly SessionStackEntry[]): void;
63
- /**
64
- * Upsert a stack item. If an entry with the same id already exists
65
- * its slot is replaced in place (preserves ordering); otherwise the
66
- * item is appended at the tail. Mirrors `GguiSession.tsx`'s push
67
- * reducer.
68
- *
69
- * Under `filterToItemId` (single-item mode): entries whose id does
70
- * not match the filter are ignored. The renderer's live-channel
71
- * subscription still delivers every push for the session (subscribe
72
- * remains session-scoped); this filter is the client-side pin that
73
- * keeps the per-card iframe bound to its one stack entry.
74
- */
75
- upsert(item: SessionStackEntry): void;
76
- /** Read-only view — callers MUST NOT mutate. */
77
- snapshot(): readonly SessionStackEntry[];
78
- /** Item count — convenience for status-line rendering. */
79
- size(): number;
80
- }
81
- /**
82
- * Render the stack model into the target list element as structural
83
- * `<li data-ggui-stack-item="<id>">` placeholders. Idempotent — every
84
- * call replaces the element's children based on the current model.
85
- *
86
- * The DOM shape (data attributes + classnames) is intentionally
87
- * stable: E2E tests and the React-mount path both need to address
88
- * stack-item rows by `data-ggui-stack-item` selector.
89
- */
90
- export declare function renderStackInto(target: HTMLElement, model: StackModel): void;
package/dist/stack.js DELETED
@@ -1,118 +0,0 @@
1
- /**
2
- * In-memory model of the visible stack. Owned by the runtime; mutated
3
- * via the methods (no setter — keeps the "model is the truth" doctrine
4
- * obvious in code reviews).
5
- */
6
- export class StackModel {
7
- items = [];
8
- filterToItemId;
9
- constructor(options = {}) {
10
- this.filterToItemId = options.filterToItemId;
11
- }
12
- /**
13
- * Replace the entire stack — used after the subscribe ack lands.
14
- *
15
- * Under `filterToItemId` (single-item mode), the incoming list is
16
- * filtered to just the matching entry. If no entry matches, the
17
- * model ends up empty — the server no longer includes the pinned
18
- * item and the renderer's empty-state is the correct surface.
19
- */
20
- setAll(items) {
21
- if (this.filterToItemId !== undefined) {
22
- const target = this.filterToItemId;
23
- this.items = items.filter((item) => item.id === target);
24
- return;
25
- }
26
- this.items = [...items];
27
- }
28
- /**
29
- * Upsert a stack item. If an entry with the same id already exists
30
- * its slot is replaced in place (preserves ordering); otherwise the
31
- * item is appended at the tail. Mirrors `GguiSession.tsx`'s push
32
- * reducer.
33
- *
34
- * Under `filterToItemId` (single-item mode): entries whose id does
35
- * not match the filter are ignored. The renderer's live-channel
36
- * subscription still delivers every push for the session (subscribe
37
- * remains session-scoped); this filter is the client-side pin that
38
- * keeps the per-card iframe bound to its one stack entry.
39
- */
40
- upsert(item) {
41
- if (this.filterToItemId !== undefined && item.id !== this.filterToItemId) {
42
- return;
43
- }
44
- const idx = this.items.findIndex((existing) => existing.id === item.id);
45
- if (idx >= 0) {
46
- const next = [...this.items];
47
- next[idx] = item;
48
- this.items = next;
49
- return;
50
- }
51
- this.items = [...this.items, item];
52
- }
53
- /** Read-only view — callers MUST NOT mutate. */
54
- snapshot() {
55
- return this.items;
56
- }
57
- /** Item count — convenience for status-line rendering. */
58
- size() {
59
- return this.items.length;
60
- }
61
- }
62
- /**
63
- * Best-effort textual label for a stack item — title-ish for the
64
- * placeholder UI. Tries `description` → `prompt` → a generic fallback.
65
- *
66
- * The wire shape keeps these fields optional on every entry kind
67
- * (generated, McpApps), so the absence-handling has to be honest.
68
- */
69
- function labelFor(item) {
70
- if (typeof item.description === 'string' && item.description.length > 0) {
71
- return item.description;
72
- }
73
- // Only generated component items carry `prompt`. McpApps + system
74
- // variants surface their own description (or a generic fallback).
75
- if (item.type !== 'mcpApps' && item.type !== 'system') {
76
- if (typeof item.prompt === 'string' && item.prompt.length > 0) {
77
- return item.prompt;
78
- }
79
- }
80
- return '(untitled stack item)';
81
- }
82
- /**
83
- * Render the stack model into the target list element as structural
84
- * `<li data-ggui-stack-item="<id>">` placeholders. Idempotent — every
85
- * call replaces the element's children based on the current model.
86
- *
87
- * The DOM shape (data attributes + classnames) is intentionally
88
- * stable: E2E tests and the React-mount path both need to address
89
- * stack-item rows by `data-ggui-stack-item` selector.
90
- */
91
- export function renderStackInto(target, model) {
92
- // Replace children rather than mutate-in-place — the placeholder
93
- // doesn't need diffing, and the wire-frame burst rate is low enough
94
- // (push events arrive roughly per agent turn) that full-replace is
95
- // cheap.
96
- target.replaceChildren();
97
- const items = model.snapshot();
98
- if (items.length === 0) {
99
- const empty = target.ownerDocument.createElement('li');
100
- empty.textContent = '(no stack items yet)';
101
- empty.setAttribute('data-ggui-empty', 'true');
102
- target.appendChild(empty);
103
- return;
104
- }
105
- for (const item of items) {
106
- const li = target.ownerDocument.createElement('li');
107
- li.setAttribute('data-ggui-stack-item', item.id);
108
- const idEl = target.ownerDocument.createElement('span');
109
- idEl.className = 'id';
110
- idEl.textContent = item.id;
111
- li.appendChild(idEl);
112
- const labelEl = target.ownerDocument.createElement('span');
113
- labelEl.className = 'label';
114
- labelEl.textContent = labelFor(item);
115
- li.appendChild(labelEl);
116
- target.appendChild(li);
117
- }
118
- }