@runtypelabs/persona 4.17.1 → 4.18.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 (158) hide show
  1. package/dist/chunk-3I2S3I5P.js +1 -0
  2. package/dist/chunk-76IELU7F.js +1 -0
  3. package/dist/chunk-EFJBUH5F.js +1 -0
  4. package/dist/chunk-PDPVORUF.js +1 -0
  5. package/dist/chunk-Q735PSZL.js +1 -0
  6. package/dist/chunk-VK6JOIYJ.js +1 -0
  7. package/dist/codegen.cjs +4 -4
  8. package/dist/codegen.js +4 -4
  9. package/dist/{context-mentions-BTSTIBFC.js → context-mentions-MJL6S6P2.js} +1 -1
  10. package/dist/context-mentions.cjs +9 -8
  11. package/dist/context-mentions.d.cts +1212 -14
  12. package/dist/context-mentions.d.ts +1212 -14
  13. package/dist/context-mentions.js +9 -8
  14. package/dist/event-stream-view-4EZ7BGFK.js +1 -0
  15. package/dist/event-stream-view.cjs +129 -0
  16. package/dist/event-stream-view.d.cts +8384 -0
  17. package/dist/event-stream-view.d.ts +8384 -0
  18. package/dist/event-stream-view.js +129 -0
  19. package/dist/history-view-BD6OXLNA.js +898 -0
  20. package/dist/history-view.cjs +898 -0
  21. package/dist/history-view.d.cts +1461 -0
  22. package/dist/history-view.d.ts +1461 -0
  23. package/dist/history-view.js +898 -0
  24. package/dist/index.cjs +77 -77
  25. package/dist/index.cjs.map +1 -1
  26. package/dist/index.d.cts +1987 -17
  27. package/dist/index.d.ts +1987 -17
  28. package/dist/index.global.js +67 -66
  29. package/dist/index.global.js.map +1 -1
  30. package/dist/index.js +77 -77
  31. package/dist/index.js.map +1 -1
  32. package/dist/launcher.global.js +5 -4
  33. package/dist/launcher.global.js.map +1 -1
  34. package/dist/plugin-kit.cjs +1 -1
  35. package/dist/plugin-kit.d.cts +0 -8
  36. package/dist/plugin-kit.d.ts +0 -8
  37. package/dist/plugin-kit.js +1 -1
  38. package/dist/smart-dom-reader.d.cts +1212 -14
  39. package/dist/smart-dom-reader.d.ts +1212 -14
  40. package/dist/theme-editor-preview.cjs +970 -73
  41. package/dist/theme-editor-preview.d.cts +1331 -15
  42. package/dist/theme-editor-preview.d.ts +1331 -15
  43. package/dist/theme-editor-preview.js +81 -81
  44. package/dist/theme-editor.cjs +7 -7
  45. package/dist/theme-editor.d.cts +1215 -15
  46. package/dist/theme-editor.d.ts +1215 -15
  47. package/dist/theme-editor.js +9 -9
  48. package/dist/theme-reference.cjs +1 -1
  49. package/dist/theme-reference.d.cts +226 -7
  50. package/dist/theme-reference.d.ts +226 -7
  51. package/dist/theme-reference.js +1 -1
  52. package/dist/widget.css +1 -1
  53. package/package.json +16 -4
  54. package/src/client-history-rest.test.ts +1034 -0
  55. package/src/client-visitor-history.test.ts +596 -0
  56. package/src/client.ts +1040 -24
  57. package/src/components/artifact-inline.ts +8 -1
  58. package/src/components/artifact-pane.test.ts +278 -1
  59. package/src/components/artifact-pane.ts +135 -67
  60. package/src/components/artifact-preview.test.ts +14 -1
  61. package/src/components/artifact-preview.ts +9 -0
  62. package/src/components/header-builder.ts +3 -0
  63. package/src/components/header-layouts.test.ts +171 -7
  64. package/src/components/header-layouts.ts +83 -53
  65. package/src/components/header-parts.test.ts +154 -1
  66. package/src/components/header-parts.ts +266 -138
  67. package/src/components/history-confirm-dialog.ts +177 -0
  68. package/src/components/history-shell-copy.ts +99 -0
  69. package/src/components/history-view/announcer.ts +40 -0
  70. package/src/components/history-view/copy.ts +105 -0
  71. package/src/components/history-view/css.ts +1078 -0
  72. package/src/components/history-view/grouping.test.ts +142 -0
  73. package/src/components/history-view/grouping.ts +142 -0
  74. package/src/components/history-view/icons.ts +73 -0
  75. package/src/components/history-view/rows.ts +341 -0
  76. package/src/components/history-view/state.ts +309 -0
  77. package/src/components/history-view.test.ts +2660 -0
  78. package/src/components/history-view.ts +1891 -0
  79. package/src/components/launcher-surface.test.ts +133 -0
  80. package/src/components/launcher.test.ts +18 -0
  81. package/src/components/launcher.ts +7 -7
  82. package/src/components/panel.ts +17 -7
  83. package/src/components/welcome.ts +72 -2
  84. package/src/defaults.ts +36 -3
  85. package/src/event-stream-view-bundle.test.ts +69 -0
  86. package/src/event-stream-view-entry.ts +19 -0
  87. package/src/event-stream-view-loader.ts +31 -0
  88. package/src/event-stream-view.ts +19 -0
  89. package/src/generated/runtype-openapi-contract.ts +1 -0
  90. package/src/history-render.ts +325 -0
  91. package/src/history-view-bundle.test.ts +78 -0
  92. package/src/history-view-entry.ts +41 -0
  93. package/src/history-view-loader.test.ts +30 -0
  94. package/src/history-view-loader.ts +32 -0
  95. package/src/history-view.ts +28 -0
  96. package/src/index-core.ts +50 -2
  97. package/src/index-global.ts +55 -0
  98. package/src/internal/demo-history-provider.test.ts +402 -0
  99. package/src/internal/demo-history-provider.ts +689 -0
  100. package/src/internal/history-provider-registry.ts +25 -0
  101. package/src/internal/history-provider.ts +150 -0
  102. package/src/internal/runtype-history-provider.test.ts +313 -0
  103. package/src/internal/runtype-history-provider.ts +240 -0
  104. package/src/plugin-kit.test.ts +16 -0
  105. package/src/plugin-kit.ts +13 -5
  106. package/src/plugins/types.ts +177 -1
  107. package/src/session-history.test.ts +1397 -0
  108. package/src/session.ts +1200 -26
  109. package/src/styles/widget-styles.test.ts +133 -0
  110. package/src/styles/widget.css +227 -8
  111. package/src/theme-editor/preview-utils.test.ts +19 -0
  112. package/src/theme-editor/preview-utils.ts +15 -1
  113. package/src/theme-editor/preview.ts +7 -1
  114. package/src/theme-editor/role-mappings.ts +4 -1
  115. package/src/theme-editor/sections.test.ts +168 -1
  116. package/src/theme-editor/sections.ts +136 -7
  117. package/src/theme-editor/webmcp/summary.ts +16 -2
  118. package/src/theme-reference.ts +20 -6
  119. package/src/types/theme.ts +213 -7
  120. package/src/types.ts +927 -7
  121. package/src/ui.artifact-rail-resize.test.ts +235 -0
  122. package/src/ui.attachments-drop.test.ts +1 -0
  123. package/src/ui.header-update-stability.test.ts +2 -2
  124. package/src/ui.history-chunk.test.ts +72 -0
  125. package/src/ui.history-config-provider.test.ts +318 -0
  126. package/src/ui.history-controller.test.ts +711 -0
  127. package/src/ui.history-lifecycle.test.ts +456 -0
  128. package/src/ui.history-render-hooks.test.ts +904 -0
  129. package/src/ui.history-shell.test.ts +1887 -0
  130. package/src/ui.rail-overlay.test.ts +573 -0
  131. package/src/ui.rail-resize.test.ts +284 -0
  132. package/src/ui.rebuild-composer.test.ts +21 -0
  133. package/src/ui.render-welcome.test.ts +42 -0
  134. package/src/ui.shortcuts.test.ts +166 -0
  135. package/src/ui.ts +3071 -240
  136. package/src/utils/__fixtures__/client-conversation-history-wire.json +108 -0
  137. package/src/utils/artifact-file.ts +15 -0
  138. package/src/utils/artifact-gate.test.ts +4 -0
  139. package/src/utils/artifact-gate.ts +13 -17
  140. package/src/utils/artifact-resize.test.ts +28 -0
  141. package/src/utils/artifact-resize.ts +9 -2
  142. package/src/utils/buttons.ts +188 -1
  143. package/src/utils/config-merge.test.ts +91 -0
  144. package/src/utils/dropdown.ts +10 -1
  145. package/src/utils/history-messages.test.ts +284 -0
  146. package/src/utils/history-messages.ts +284 -0
  147. package/src/utils/icons.ts +3 -0
  148. package/src/utils/shortcuts.test.ts +288 -0
  149. package/src/utils/shortcuts.ts +221 -0
  150. package/src/utils/theme.test.ts +501 -4
  151. package/src/utils/theme.ts +42 -3
  152. package/src/utils/tokens.ts +153 -15
  153. package/src/utils/tooltip-timing.ts +4 -0
  154. package/src/utils/tooltip.test.ts +249 -7
  155. package/src/utils/tooltip.ts +146 -4
  156. package/src/utils/visitor-store.test.ts +425 -0
  157. package/src/utils/visitor-store.ts +337 -0
  158. package/dist/chunk-QJ4LV2KW.js +0 -1
@@ -0,0 +1,325 @@
1
+ /**
2
+ * History render-hook arbitration (plan D7, "Public rendering customization
3
+ * contract").
4
+ *
5
+ * Lives in CORE, not in the lazy view chunk: plugins must arbitrate before any
6
+ * chunk-owned DOM exists, and the hook contexts are part of the public package
7
+ * surface. Only type imports reach into `history-view-entry`, so nothing here
8
+ * pulls the chunk into the main bundles.
9
+ *
10
+ * Arbitration:
11
+ * - plugins run in priority order; the first non-null `renderHistoryView` wins
12
+ * - null falls back to the default full view, which then applies the first
13
+ * non-null header/row/state hook at each slot
14
+ * - the full hook's `defaultRenderer()` bypasses only that hook: the default
15
+ * view is built and its lower slots still arbitrate, including the same
16
+ * plugin's. Hook-kind separation is what prevents recursion.
17
+ * - a thrown hook is reported and skipped; the default renderer stands in and
18
+ * no history state is lost.
19
+ *
20
+ * Contexts are frozen, field-by-field snapshots. The provider, credentials,
21
+ * proofs, visitor ids, raw responses, and prepared commit/discard never appear:
22
+ * every action routes back through the view's own operations, which are the
23
+ * same ones the default DOM invokes.
24
+ */
25
+
26
+ import type { AgentWidgetPlugin } from "./plugins/types";
27
+ import type {
28
+ AgentWidgetConfig,
29
+ AgentWidgetHistoryRenderActions,
30
+ AgentWidgetRenderHistoryConversationContext,
31
+ AgentWidgetRenderHistoryHeaderContext,
32
+ AgentWidgetRenderHistoryStateContext,
33
+ AgentWidgetRenderHistoryViewContext,
34
+ HistoryConversationSummary,
35
+ HistoryPendingAction,
36
+ HistoryReturnSurface,
37
+ ResolvedHistoryPresentation,
38
+ } from "./types";
39
+ import type {
40
+ HistoryRailSection,
41
+ HistoryViewHandle,
42
+ HistoryViewModel,
43
+ HistoryViewSlotRenderers,
44
+ } from "./history-view-entry";
45
+
46
+ export interface HistoryRenderSurfaceOptions {
47
+ plugins: AgentWidgetPlugin[];
48
+ config: AgentWidgetConfig;
49
+ getPresentation: () => ResolvedHistoryPresentation;
50
+ getReturnSurface: () => HistoryReturnSurface;
51
+ /** Shell close: records the return surface and restores focus. */
52
+ close: () => void;
53
+ /**
54
+ * Builds the default view with the core-owned slots wired in. Invoked once;
55
+ * `renderDom` starts false so no slot runs before the full hook arbitrates.
56
+ */
57
+ createView: (options: {
58
+ slots: HistoryViewSlotRenderers;
59
+ renderDom: boolean;
60
+ onModelChange: () => void;
61
+ /** Plugin rail sections, for the core to merge behind the config ones. */
62
+ railSections: HistoryRailSection[];
63
+ }) => HistoryViewHandle;
64
+ /** Mount (previous === null) or swap the arbitrated element. */
65
+ onElementChanged: (next: HTMLElement, previous: HTMLElement | null) => void;
66
+ }
67
+
68
+ export interface HistoryRenderSurface {
69
+ readonly view: HistoryViewHandle;
70
+ /** Currently arbitrated element: the default view, or a plugin's. */
71
+ readonly element: HTMLElement;
72
+ /** Re-run arbitration with the current snapshot. Inert after dispose. */
73
+ requestRender(): void;
74
+ /** Runs pending cleanups and makes `requestRender()` inert. */
75
+ dispose(): void;
76
+ }
77
+
78
+ const warn = (hook: string, error: unknown): void => {
79
+ // eslint-disable-next-line no-console
80
+ console.warn(`[persona] ${hook} threw`, error);
81
+ };
82
+
83
+ /** Frozen shallow copy: a plugin can neither mutate nor retain view state. */
84
+ const frozen = <T extends object>(value: T): T => Object.freeze({ ...value });
85
+
86
+ /** Field-by-field copy: an unknown provider field can never reach a plugin. */
87
+ const snapshotConversation = (
88
+ conversation: HistoryConversationSummary
89
+ ): HistoryConversationSummary =>
90
+ Object.freeze({
91
+ id: conversation.id,
92
+ title: conversation.title,
93
+ targetId: conversation.targetId,
94
+ preview: conversation.preview,
95
+ messageCount: conversation.messageCount,
96
+ createdAt: conversation.createdAt,
97
+ updatedAt: conversation.updatedAt,
98
+ });
99
+
100
+ const snapshotPending = (pending: HistoryPendingAction): HistoryPendingAction =>
101
+ pending ? frozen(pending) : null;
102
+
103
+ export function createHistoryRenderSurface(
104
+ options: HistoryRenderSurfaceOptions
105
+ ): HistoryRenderSurface {
106
+ const { plugins, config } = options;
107
+ let disposed = false;
108
+ let arbitrating = false;
109
+ /** The view constructor emits a model signal; nothing may arbitrate yet. */
110
+ let ready = false;
111
+ /** Lower-level slots run only when the default view owns the surface. */
112
+ let slotsActive = false;
113
+ let element: HTMLElement | null = null;
114
+ const cleanups: Array<() => void> = [];
115
+
116
+ const runCleanups = (): void => {
117
+ for (const callback of cleanups.splice(0)) {
118
+ try {
119
+ callback();
120
+ } catch (error) {
121
+ warn("renderHistoryView cleanup", error);
122
+ }
123
+ }
124
+ };
125
+
126
+ type SlotHook =
127
+ | "renderHistoryHeader"
128
+ | "renderHistoryConversation"
129
+ | "renderHistoryState";
130
+
131
+ /** One first-non-null pass per slot; a throw is reported and skipped. */
132
+ const arbitrateSlot = <C>(hook: SlotHook, context: C): HTMLElement | null => {
133
+ for (const plugin of plugins) {
134
+ const render = plugin[hook] as
135
+ | ((context: C) => HTMLElement | null)
136
+ | undefined;
137
+ if (!render) continue;
138
+ try {
139
+ const custom = render(context);
140
+ if (custom) return custom;
141
+ } catch (error) {
142
+ warn(hook, error);
143
+ }
144
+ }
145
+ return null;
146
+ };
147
+
148
+ /** Fields every slot context carries. */
149
+ const slotBase = (): {
150
+ presentation: ResolvedHistoryPresentation;
151
+ config: AgentWidgetConfig;
152
+ } => ({ presentation: options.getPresentation(), config });
153
+
154
+ const slots: HistoryViewSlotRenderers = {
155
+ header: (slot) =>
156
+ slotsActive
157
+ ? arbitrateSlot<AgentWidgetRenderHistoryHeaderContext>(
158
+ "renderHistoryHeader",
159
+ Object.freeze({
160
+ ...slotBase(),
161
+ returnSurface: options.getReturnSurface(),
162
+ identityStatus: frozen(slot.identityStatus),
163
+ pendingAction: snapshotPending(slot.pendingAction),
164
+ copy: frozen(slot.copy),
165
+ actions: Object.freeze({
166
+ close: actions.close,
167
+ startNewConversation: actions.startNewConversation,
168
+ }),
169
+ defaultRenderer: slot.defaultRenderer,
170
+ })
171
+ )
172
+ : null,
173
+ conversation: (slot) =>
174
+ slotsActive
175
+ ? arbitrateSlot<AgentWidgetRenderHistoryConversationContext>(
176
+ "renderHistoryConversation",
177
+ Object.freeze({
178
+ ...slotBase(),
179
+ conversation: snapshotConversation(slot.conversation),
180
+ active: slot.active,
181
+ pending: slot.pending,
182
+ open: slot.open,
183
+ requestDelete: slot.requestDelete,
184
+ defaultRenderer: slot.defaultRenderer,
185
+ })
186
+ )
187
+ : null,
188
+ state: (slot) =>
189
+ slotsActive
190
+ ? arbitrateSlot<AgentWidgetRenderHistoryStateContext>(
191
+ "renderHistoryState",
192
+ Object.freeze({
193
+ ...slotBase(),
194
+ state: frozen(slot.state),
195
+ identityStatus: frozen(slot.identityStatus),
196
+ copy: frozen(slot.copy),
197
+ ...(slot.retry ? { retry: slot.retry } : {}),
198
+ ...(slot.startNewConversation
199
+ ? { startNewConversation: slot.startNewConversation }
200
+ : {}),
201
+ defaultRenderer: slot.defaultRenderer,
202
+ })
203
+ )
204
+ : null,
205
+ };
206
+
207
+ /**
208
+ * Plugin rail sections, in plugin order. Only the context is assembled here;
209
+ * a throwing `render` is the view's to warn about once and drop. `actions`
210
+ * resolves at invocation, which never happens before the view owns the DOM.
211
+ */
212
+ const railSections: HistoryRailSection[] = plugins.flatMap((plugin) =>
213
+ (plugin.railSections ?? []).map((section) => ({
214
+ ...section,
215
+ placement: section.placement ?? "above-conversations",
216
+ items: [],
217
+ render: (collapsed: boolean) =>
218
+ section.render(
219
+ Object.freeze({
220
+ collapsed,
221
+ presentation: options.getPresentation(),
222
+ actions,
223
+ })
224
+ ),
225
+ }))
226
+ );
227
+
228
+ const view = options.createView({
229
+ slots,
230
+ renderDom: false,
231
+ onModelChange: () => requestRender(),
232
+ railSections,
233
+ });
234
+
235
+ /** Every action IS the operation the default DOM invokes; close is the shell's. */
236
+ const actions: AgentWidgetHistoryRenderActions = Object.freeze({
237
+ ...view.operations,
238
+ close: options.close,
239
+ });
240
+
241
+ /** Builds (and renders) the default view; lower slots arbitrate from here on. */
242
+ const buildDefault = (): HTMLElement => {
243
+ slotsActive = true;
244
+ view.setDomRenderEnabled(true);
245
+ return view.element;
246
+ };
247
+
248
+ const buildViewContext = (
249
+ model: HistoryViewModel
250
+ ): AgentWidgetRenderHistoryViewContext =>
251
+ Object.freeze({
252
+ conversations: Object.freeze(
253
+ model.conversations.map(snapshotConversation)
254
+ ),
255
+ activeConversationId: model.activeConversationId,
256
+ state: frozen(model.state),
257
+ pendingAction: snapshotPending(model.pendingAction),
258
+ identityStatus: frozen(model.identityStatus),
259
+ nextCursor: model.nextCursor,
260
+ presentation: options.getPresentation(),
261
+ returnSurface: options.getReturnSurface(),
262
+ config,
263
+ copy: frozen(view.copy),
264
+ actions,
265
+ defaultRenderer: buildDefault,
266
+ requestRender: () => requestRender(),
267
+ onCleanup: (callback: () => void) => {
268
+ cleanups.push(callback);
269
+ },
270
+ });
271
+
272
+ const arbitrate = (): HTMLElement => {
273
+ slotsActive = false;
274
+ let custom: HTMLElement | null = null;
275
+ for (const plugin of plugins) {
276
+ if (!plugin.renderHistoryView) continue;
277
+ try {
278
+ custom = plugin.renderHistoryView(buildViewContext(view.getModel()));
279
+ } catch (error) {
280
+ warn("renderHistoryView", error);
281
+ custom = null;
282
+ }
283
+ if (custom) break;
284
+ }
285
+ // Returning the default view (or a wrapper around it) is composition, not
286
+ // a takeover: it keeps rendering and its slots keep arbitrating.
287
+ // `contains` is true for the node itself, so this covers a plain return.
288
+ const usesDefault = !custom || custom.contains(view.element);
289
+ slotsActive = usesDefault;
290
+ view.setDomRenderEnabled(usesDefault);
291
+ return custom ?? view.element;
292
+ };
293
+
294
+ function requestRender(): void {
295
+ // A hook calling requestRender() from inside its own render would recurse.
296
+ if (!ready || disposed || arbitrating) return;
297
+ arbitrating = true;
298
+ try {
299
+ // Cleanups belong to the render being replaced, so they run first.
300
+ runCleanups();
301
+ const next = arbitrate();
302
+ if (next === element) return;
303
+ const previous = element;
304
+ element = next;
305
+ options.onElementChanged(next, previous);
306
+ } finally {
307
+ arbitrating = false;
308
+ }
309
+ }
310
+
311
+ ready = true;
312
+ requestRender();
313
+
314
+ return {
315
+ view,
316
+ get element() {
317
+ return element ?? view.element;
318
+ },
319
+ requestRender,
320
+ dispose: () => {
321
+ disposed = true;
322
+ runCleanups();
323
+ },
324
+ };
325
+ }
@@ -0,0 +1,78 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { existsSync, readFileSync } from "node:fs";
3
+ import { resolve } from "node:path";
4
+
5
+ /**
6
+ * Bundle guard for the lazy history-view chunk (see
7
+ * `docs/visitor-history-implementation-plan.md` D7, Bundle size).
8
+ *
9
+ * The Messages view must NOT land in ANY core bundle shipped to consumers —
10
+ * neither the CDN IIFE (`dist/index.global.js`) nor the ESM/CJS bundles
11
+ * (`dist/index.js` / `dist/index.cjs`). It ships as the sibling chunk
12
+ * `dist/history-view.{js,cjs}`, loaded on demand: the IIFE via a sibling URL,
13
+ * ESM/CJS via the external `@runtypelabs/persona/history-view` subpath. A stray
14
+ * static import would pull the view back in; this fails then.
15
+ *
16
+ * Rejected-load retry (the other half of the transport contract) is covered by
17
+ * `history-view-loader.test.ts`.
18
+ *
19
+ * Skips when `dist/` hasn't been built (e.g. a test-only CI step).
20
+ */
21
+ const dist = (f: string) => resolve(__dirname, "..", "dist", f);
22
+
23
+ // Chunk-only literals. `persona-history-view` is the view's root class and is
24
+ // never emitted by core code; the sibling-URL stub in the core bundles is the
25
+ // bare filename `history-view.js`, which does not contain these.
26
+ const RUNTIME_MARKERS = ["persona-history-view", "persona-history-view-loading"];
27
+
28
+ const SUBPATH = "@runtypelabs/persona/history-view";
29
+
30
+ describe("history-view bundle split", () => {
31
+ const iifeBuilt =
32
+ existsSync(dist("index.global.js")) && existsSync(dist("history-view.js"));
33
+ const esmBuilt = existsSync(dist("index.js")) && existsSync(dist("index.cjs"));
34
+
35
+ it.runIf(iifeBuilt)("keeps the history view OUT of the core IIFE bundle", () => {
36
+ const core = readFileSync(dist("index.global.js"), "utf8");
37
+ for (const marker of RUNTIME_MARKERS) {
38
+ expect(core.includes(marker), `IIFE bundle unexpectedly contains "${marker}"`).toBe(
39
+ false
40
+ );
41
+ }
42
+ // The loader stub (sibling-URL reference) must remain so the chunk can load.
43
+ expect(core).toContain("history-view.js");
44
+ });
45
+
46
+ it.runIf(esmBuilt)("keeps the history view OUT of the ESM/CJS bundles", () => {
47
+ for (const file of ["index.js", "index.cjs"]) {
48
+ const core = readFileSync(dist(file), "utf8");
49
+ for (const marker of RUNTIME_MARKERS) {
50
+ expect(core.includes(marker), `${file} unexpectedly contains "${marker}"`).toBe(
51
+ false
52
+ );
53
+ }
54
+ // TODO(history-ui): the ESM/CJS core only reaches the loader once `ui.ts`
55
+ // mounts the view, so today these bundles carry no history reference at
56
+ // all. The invariant asserted meanwhile — and automatically once the UI
57
+ // lands — is that any reference goes through the external subpath rather
58
+ // than an inlined relative import.
59
+ if (core.includes("history-view")) {
60
+ expect(
61
+ core.includes(SUBPATH),
62
+ `${file} must reach the view via the external subpath`
63
+ ).toBe(true);
64
+ }
65
+ }
66
+ });
67
+
68
+ it.runIf(iifeBuilt)("ships the history view in the sibling chunk", () => {
69
+ const chunk = readFileSync(dist("history-view.js"), "utf8");
70
+ for (const marker of RUNTIME_MARKERS) {
71
+ expect(chunk.includes(marker), `chunk is missing "${marker}"`).toBe(true);
72
+ }
73
+ // A CJS twin exists for require()-based consumers.
74
+ expect(existsSync(dist("history-view.cjs"))).toBe(true);
75
+ // Declarations ship with the chunk so the subpath is typed.
76
+ expect(existsSync(dist("history-view.d.ts"))).toBe(true);
77
+ });
78
+ });
@@ -0,0 +1,41 @@
1
+ /**
2
+ * History-view runtime entry — the module that assembles the lazily loaded
3
+ * Messages surface. It is NEVER statically imported on the core path: the
4
+ * IIFE/CDN build marks `@runtypelabs/persona/history-view` external
5
+ * (`tsup.global.config.ts`) and loads the standalone `history-view.js` chunk
6
+ * from a sibling URL on first open of the history UI. The core reaches it
7
+ * through `history-view-loader.ts`.
8
+ *
9
+ * Mirrors `context-mentions-entry.ts`; see
10
+ * `docs/visitor-history-implementation-plan.md` D7 (Bundle size).
11
+ */
12
+
13
+ export { createHistoryView } from "./components/history-view";
14
+ export type {
15
+ HistoryViewOptions,
16
+ HistoryViewHandle,
17
+ HistoryViewPresentation,
18
+ HistoryHeaderPlacement,
19
+ HistoryViewPendingAction,
20
+ HistoryViewModel,
21
+ HistoryViewOperations,
22
+ HistoryRailSection,
23
+ HistoryRailSectionItem,
24
+ HistoryRailSectionPlacement,
25
+ HistoryViewSlotRenderers,
26
+ HistoryHeaderSlotContext,
27
+ HistoryConversationSlotContext,
28
+ HistoryStateSlotContext,
29
+ } from "./components/history-view";
30
+ export {
31
+ HISTORY_VIEW_COPY_DEFAULTS,
32
+ resolveHistoryViewCopy,
33
+ } from "./components/history-view/copy";
34
+ export type {
35
+ HistoryViewCopyInput,
36
+ ResolvedHistoryViewCopy,
37
+ } from "./components/history-view/copy";
38
+ export type {
39
+ HistoryListState,
40
+ HistoryListErrorReason,
41
+ } from "./components/history-view/state";
@@ -0,0 +1,30 @@
1
+ import { describe, it, expect, vi } from "vitest";
2
+ import {
3
+ loadHistoryView,
4
+ setHistoryViewLoader,
5
+ type HistoryViewModule,
6
+ } from "./history-view-loader";
7
+
8
+ const fakeModule = {
9
+ createHistoryView: vi.fn(),
10
+ } as unknown as HistoryViewModule;
11
+
12
+ describe("loadHistoryView", () => {
13
+ it("retries after a rejected load instead of caching the failure forever", async () => {
14
+ const loader = vi
15
+ .fn<() => Promise<HistoryViewModule>>()
16
+ .mockRejectedValueOnce(new Error("network"))
17
+ .mockResolvedValueOnce(fakeModule);
18
+ setHistoryViewLoader(loader);
19
+
20
+ // First call surfaces the rejection to the caller.
21
+ await expect(loadHistoryView()).rejects.toThrow("network");
22
+ // A later call retries (the failed promise was not cached) and resolves.
23
+ await expect(loadHistoryView()).resolves.toBe(fakeModule);
24
+ expect(loader).toHaveBeenCalledTimes(2);
25
+
26
+ // Once resolved, the module is cached (no third loader call).
27
+ await expect(loadHistoryView()).resolves.toBe(fakeModule);
28
+ expect(loader).toHaveBeenCalledTimes(2);
29
+ });
30
+ });
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Loader indirection for the lazy history-view chunk (core bundle, tiny).
3
+ *
4
+ * Mirrors `context-mentions-loader.ts`: the IIFE/CDN entry (`index-global.ts`)
5
+ * registers a loader that imports the standalone `history-view.js` chunk from a
6
+ * sibling URL. ESM/CJS consumers fall back to importing the package's
7
+ * `./history-view` subpath — which `build:client` marks external, so the view is
8
+ * NOT inlined into `dist/index.{js,cjs}` and stays out of the core bundle until
9
+ * the history UI is first opened. (A relative `./history-view-entry` import
10
+ * would be inlined by the `--splitting false` build, defeating the split.)
11
+ */
12
+
13
+ import { createChunkLoader } from "./utils/chunk-loader";
14
+ import type {
15
+ HistoryViewOptions,
16
+ HistoryViewHandle,
17
+ } from "./history-view-entry";
18
+
19
+ export type HistoryViewModule = {
20
+ createHistoryView: (options: HistoryViewOptions) => HistoryViewHandle;
21
+ };
22
+
23
+ // IIFE/CDN: sibling-URL chunk via the registered loader.
24
+ // ESM/CJS fallback: the package's own `./history-view` subpath (external, so the
25
+ // runtime chunk is code-split out of dist/index.{js,cjs} rather than inlined).
26
+ // Memoization + rejection-retry semantics live in `createChunkLoader`.
27
+ const { setLoader, load } = createChunkLoader<HistoryViewModule>({
28
+ fallbackImport: () => import("@runtypelabs/persona/history-view"),
29
+ });
30
+
31
+ export const setHistoryViewLoader = setLoader;
32
+ export const loadHistoryView = load;
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Standalone history-view chunk (`dist/history-view.js`).
3
+ *
4
+ * The IIFE/CDN widget bundle marks `@runtypelabs/persona/history-view` external
5
+ * so the Messages surface is excluded from the core payload; this file is built
6
+ * as a self-contained sibling chunk (`tsup.history-view.config.ts`) and loaded on
7
+ * demand by the loader registered in `index-global.ts`. ESM/CJS consumers reach
8
+ * it via this same (external) subpath.
9
+ *
10
+ * Lazy-runtime transport entry only: exports are limited to the mount contract
11
+ * the loader needs, NOT the public history provider/controller seam.
12
+ */
13
+
14
+ export { createHistoryView } from "./history-view-entry";
15
+ export {
16
+ HISTORY_VIEW_COPY_DEFAULTS,
17
+ resolveHistoryViewCopy,
18
+ } from "./history-view-entry";
19
+ export type {
20
+ HistoryViewOptions,
21
+ HistoryViewHandle,
22
+ HistoryViewPresentation,
23
+ HistoryViewPendingAction,
24
+ HistoryViewCopyInput,
25
+ ResolvedHistoryViewCopy,
26
+ HistoryListState,
27
+ HistoryListErrorReason,
28
+ } from "./history-view-entry";
package/src/index-core.ts CHANGED
@@ -32,6 +32,7 @@ export type {
32
32
  AgentWidgetMessage,
33
33
  AgentWidgetLauncherConfig,
34
34
  AgentWidgetLauncherTeaserConfig,
35
+ AgentWidgetTooltipConfig,
35
36
  AgentWidgetDockConfig,
36
37
  AgentWidgetEvent,
37
38
  AgentWidgetStreamParser,
@@ -93,6 +94,7 @@ export type {
93
94
  // Client token types
94
95
  ClientSession,
95
96
  ClientInitResponse,
97
+ PreparedClientSession,
96
98
  ClientChatRequest,
97
99
  ClientFeedbackRequest,
98
100
  ClientFeedbackType,
@@ -142,6 +144,30 @@ export type {
142
144
  AskUserQuestionOption,
143
145
  AgentWidgetAskUserQuestionFeature,
144
146
  AgentWidgetAskUserQuestionStyles,
147
+ // Visitor conversation history (client-token mode) types
148
+ AgentWidgetHistoryFeature,
149
+ AgentWidgetHistoryListAction,
150
+ AgentWidgetHistoryCopy,
151
+ HistoryScope,
152
+ HistoryIdentityStatus,
153
+ ResolvedHistoryPresentation,
154
+ HistoryReturnSurface,
155
+ HistoryConversationSummary,
156
+ HistoryConversationPatch,
157
+ HistoryConversationPage,
158
+ HistoryConversationDetail,
159
+ HistoryDisplayProjection,
160
+ // History rendering customization (plugin render hooks; types only)
161
+ ResolvedHistoryCopy,
162
+ HistoryViewState,
163
+ HistoryPendingAction,
164
+ AgentWidgetHistoryRenderActions,
165
+ AgentWidgetRenderHistoryViewContext,
166
+ AgentWidgetRenderHistoryHeaderContext,
167
+ AgentWidgetRenderHistoryConversationContext,
168
+ AgentWidgetRenderHistoryStateContext,
169
+ AgentWidgetRenderHistoryOpenErrorContext,
170
+ ClientVisitorGrant,
145
171
  AgentWidgetSuggestion,
146
172
  AgentWidgetSuggestionChipsConfig,
147
173
  AgentWidgetResolvedSuggestion,
@@ -159,6 +185,26 @@ export type {
159
185
  AgentWidgetWelcomeDismiss
160
186
  } from "./types";
161
187
 
188
+ // Custom conversation-history backends (`features.history.provider`). The
189
+ // provider registry beside this module stays private.
190
+ export type {
191
+ HistoryProvider,
192
+ HistoryOperationContext,
193
+ PreparedHistoryActivation,
194
+ HistoryListOptions,
195
+ HistoryListResult,
196
+ HistoryPageOptions,
197
+ HistoryPageResult,
198
+ HistoryDeleteAllOptions,
199
+ HistoryProviderErrorCode,
200
+ } from "./internal/history-provider";
201
+
202
+ // Value export: custom providers construct and `instanceof` this.
203
+ export {
204
+ HistoryProviderError,
205
+ isHistoryProviderError,
206
+ } from "./internal/history-provider";
207
+
162
208
  export {
163
209
  applyFeaturePreferences,
164
210
  parseWidgetPreferenceSlice,
@@ -347,7 +393,9 @@ export type {
347
393
  AgentWidgetTransformSuggestionsContext,
348
394
  AgentWidgetRenderSuggestionContext,
349
395
  AgentWidgetRenderWelcomeContext,
350
- AgentWidgetSuggestionSelectContext
396
+ AgentWidgetSuggestionSelectContext,
397
+ AgentWidgetRailSection,
398
+ AgentWidgetRailSectionContext
351
399
  } from "./plugins/types";
352
400
  // Welcome resolver: `renderWelcome` receives the resolved shape.
353
401
  export { resolveWelcomeConfig, isWelcomeVisible } from "./welcome";
@@ -398,7 +446,7 @@ export { renderLucideIcon } from "./utils/icons";
398
446
  export type { IconName } from "./utils/icons";
399
447
 
400
448
  // Button utility exports
401
- export { createIconButton, createLabelButton, createToggleGroup, createComboButton } from "./utils/buttons";
449
+ export { createIconButton, createLabelButton, createToggleGroup, createComboButton, createSplitButton } from "./utils/buttons";
402
450
  export type {
403
451
  CreateIconButtonOptions,
404
452
  CreateLabelButtonOptions,
@@ -188,3 +188,58 @@ setContextMentionsInlineLoader(() => {
188
188
  }
189
189
  return import(/* @vite-ignore */ chunkUrl);
190
190
  });
191
+
192
+ // ---------------------------------------------------------------------------
193
+ // Deferred history-view loading.
194
+ //
195
+ // This bundle is built with `@runtypelabs/persona/history-view` external (see
196
+ // `tsup.global.config.ts`): the Messages navigation surface is kept out of the
197
+ // CDN payload and fetched only when the visitor first opens history. Same
198
+ // sibling-URL scheme as the chunks above.
199
+ // ---------------------------------------------------------------------------
200
+
201
+ import { setHistoryViewLoader } from "./history-view-loader";
202
+
203
+ setHistoryViewLoader(() => {
204
+ const chunkUrl = widgetScriptSrc?.replace(
205
+ /index\.global\.js($|\?)/,
206
+ "history-view.js$1",
207
+ );
208
+ if (!chunkUrl || chunkUrl === widgetScriptSrc) {
209
+ return Promise.reject(
210
+ new Error(
211
+ "Could not derive the history-view.js URL from the widget script URL " +
212
+ `(${widgetScriptSrc ?? "unavailable"}). Self-hosted deployments that ` +
213
+ "rename index.global.js should host history-view.js alongside it.",
214
+ ),
215
+ );
216
+ }
217
+ return import(/* @vite-ignore */ chunkUrl);
218
+ });
219
+
220
+ // ---------------------------------------------------------------------------
221
+ // Deferred event-stream-view loading.
222
+ //
223
+ // Same scheme as history-view: `@runtypelabs/persona/event-stream-view` is
224
+ // external here (see `tsup.global.config.ts`), and the observability panel is
225
+ // fetched from a sibling URL only when the visitor first opens it.
226
+ // ---------------------------------------------------------------------------
227
+
228
+ import { setEventStreamViewLoader } from "./event-stream-view-loader";
229
+
230
+ setEventStreamViewLoader(() => {
231
+ const chunkUrl = widgetScriptSrc?.replace(
232
+ /index\.global\.js($|\?)/,
233
+ "event-stream-view.js$1",
234
+ );
235
+ if (!chunkUrl || chunkUrl === widgetScriptSrc) {
236
+ return Promise.reject(
237
+ new Error(
238
+ "Could not derive the event-stream-view.js URL from the widget script URL " +
239
+ `(${widgetScriptSrc ?? "unavailable"}). Self-hosted deployments that ` +
240
+ "rename index.global.js should host event-stream-view.js alongside it.",
241
+ ),
242
+ );
243
+ }
244
+ return import(/* @vite-ignore */ chunkUrl);
245
+ });