@runtypelabs/persona 4.23.1 → 4.24.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/animations/glyph-cycle.d.cts +1 -1
- package/dist/animations/glyph-cycle.d.ts +1 -1
- package/dist/animations/{types-9Vxm-PPz.d.cts → types-Db61aI9J.d.cts} +25 -2
- package/dist/animations/{types-9Vxm-PPz.d.ts → types-Db61aI9J.d.ts} +25 -2
- package/dist/animations/wipe.d.cts +1 -1
- package/dist/animations/wipe.d.ts +1 -1
- package/dist/animations-extra.d.cts +25 -2
- package/dist/animations-extra.d.ts +25 -2
- package/dist/approval-ui-3NMYNXBD.js +1 -0
- package/dist/approval-ui.cjs +2 -2
- package/dist/approval-ui.d.cts +261 -40
- package/dist/approval-ui.d.ts +261 -40
- package/dist/approval-ui.js +2 -2
- package/dist/{artifacts-ui-F4N6VMKF.js → artifacts-ui-IGB5YKLO.js} +4 -4
- package/dist/artifacts-ui.d.cts +256 -28
- package/dist/artifacts-ui.d.ts +256 -28
- package/dist/{chunk-NIKYKSNZ.js → chunk-7CO4T5KB.js} +1 -1
- package/dist/codegen.cjs +11 -11
- package/dist/codegen.js +13 -13
- package/dist/context-mentions.d.cts +256 -28
- package/dist/context-mentions.d.ts +256 -28
- package/dist/event-stream-view.d.cts +256 -28
- package/dist/event-stream-view.d.ts +256 -28
- package/dist/forms-ui.d.cts +291 -30
- package/dist/forms-ui.d.ts +291 -30
- package/dist/history-view.d.cts +25 -2
- package/dist/history-view.d.ts +25 -2
- package/dist/index.cjs +70 -69
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +337 -51
- package/dist/index.d.ts +337 -51
- package/dist/index.global.js +57 -54
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +70 -69
- package/dist/index.js.map +1 -1
- package/dist/install.global.js +1 -1
- package/dist/install.global.js.map +1 -1
- package/dist/launcher.global.js +2 -2
- package/dist/launcher.global.js.map +1 -1
- package/dist/session-reconnect.d.cts +256 -28
- package/dist/session-reconnect.d.ts +256 -28
- package/dist/smart-dom-reader.d.cts +256 -28
- package/dist/smart-dom-reader.d.ts +256 -28
- package/dist/theme-editor-preview.cjs +74 -73
- package/dist/theme-editor-preview.d.cts +263 -28
- package/dist/theme-editor-preview.d.ts +263 -28
- package/dist/theme-editor-preview.js +51 -50
- package/dist/theme-editor.cjs +10 -10
- package/dist/theme-editor.d.cts +276 -30
- package/dist/theme-editor.d.ts +276 -30
- package/dist/theme-editor.js +10 -10
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.d.cts +1522 -11
- package/dist/theme-reference.d.ts +1522 -11
- package/dist/theme-reference.js +1 -1
- package/dist/widget.css +1 -1
- package/package.json +3 -2
- package/src/__fixtures__/defaults-parity.v4.json +776 -0
- package/src/approval-ui-entry.ts +0 -1
- package/src/approval-ui-loader.ts +5 -4
- package/src/approval-ui.ts +0 -1
- package/src/client-identity-proof.test.ts +193 -0
- package/src/client-session-warm.test.ts +216 -0
- package/src/client-visitor-history.test.ts +6 -0
- package/src/client.tool-pair-replay.test.ts +325 -0
- package/src/client.ts +154 -61
- package/src/codegen.test.ts +24 -0
- package/src/components/activity-row.test.ts +100 -0
- package/src/components/activity-row.ts +136 -0
- package/src/components/approval-actions.test.ts +8 -5
- package/src/components/approval-actions.ts +19 -6
- package/src/components/approval-bubble.test.ts +56 -9
- package/src/components/approval-bubble.ts +36 -41
- package/src/components/composer-builder.ts +5 -2
- package/src/components/composer-parts.ts +10 -2
- package/src/components/header-builder.test.ts +32 -0
- package/src/components/header-builder.ts +7 -4
- package/src/components/header-layouts.test.ts +12 -0
- package/src/components/header-layouts.ts +3 -2
- package/src/components/launcher.ts +11 -2
- package/src/components/message-bubble.test.ts +22 -0
- package/src/components/message-bubble.ts +7 -3
- package/src/components/panel.test.ts +52 -1
- package/src/components/panel.ts +47 -8
- package/src/components/pill-composer-builder.test.ts +2 -0
- package/src/components/pill-composer-builder.ts +0 -1
- package/src/components/reasoning-bubble.ts +29 -17
- package/src/components/suggestions.test.ts +1 -0
- package/src/components/suggestions.ts +0 -16
- package/src/components/tool-bubble.ts +131 -114
- package/src/components/tool-details.test.ts +87 -0
- package/src/components/tool-details.ts +102 -0
- package/src/components/welcome.test.ts +8 -0
- package/src/components/welcome.ts +4 -2
- package/src/defaults-parity.test.ts +158 -0
- package/src/defaults.test.ts +69 -0
- package/src/defaults.ts +128 -49
- package/src/generated/runtype-openapi-contract.ts +28 -13
- package/src/index-core.ts +2 -0
- package/src/install.test.ts +24 -0
- package/src/install.ts +3 -1
- package/src/launcher-global.ts +2 -1
- package/src/launcher-variants.test.ts +52 -0
- package/src/runtime/init-update-reset.test.ts +21 -0
- package/src/session.ts +61 -9
- package/src/styles/widget-styles.test.ts +19 -0
- package/src/styles/widget.css +295 -23
- package/src/theme-editor/index.ts +2 -0
- package/src/theme-editor/preview-utils.test.ts +8 -0
- package/src/theme-editor/preview-utils.ts +7 -3
- package/src/theme-editor/sections.test.ts +31 -0
- package/src/theme-editor/sections.ts +55 -1
- package/src/theme-editor/state-preferences.test.ts +49 -0
- package/src/theme-editor/state.ts +73 -15
- package/src/theme-reference.test.ts +20 -0
- package/src/theme-reference.ts +11 -5
- package/src/tool-call-display-defaults.test.ts +33 -1
- package/src/types/theme.ts +80 -3
- package/src/types.ts +179 -23
- package/src/ui.activity-row.test.ts +91 -0
- package/src/ui.approval-plugin.test.ts +63 -0
- package/src/ui.composer-compact.test.ts +24 -2
- package/src/ui.composer-placement.test.ts +23 -0
- package/src/ui.detached-panel.test.ts +33 -6
- package/src/ui.docked.test.ts +19 -0
- package/src/ui.header-update-stability.test.ts +25 -0
- package/src/ui.identity-proof.test.ts +58 -0
- package/src/ui.launcher-teaser.test.ts +25 -0
- package/src/ui.message-width.test.ts +17 -2
- package/src/ui.reasoning-display.test.ts +92 -0
- package/src/ui.rebuild-composer.test.ts +20 -3
- package/src/ui.scroll.test.ts +32 -0
- package/src/ui.session-init.test.ts +312 -0
- package/src/ui.status-indicator.test.ts +82 -0
- package/src/ui.suggest-replies.test.ts +11 -0
- package/src/ui.ts +363 -74
- package/src/ui.v5-defaults.test.ts +144 -0
- package/src/ui.welcome.test.ts +92 -3
- package/src/utils/__snapshots__/tokens-parity.test.ts.snap +811 -140
- package/src/utils/code-generators.ts +14 -0
- package/src/utils/composer-compact.test.ts +0 -2
- package/src/utils/composer-compact.ts +2 -4
- package/src/utils/composer-placement.ts +2 -3
- package/src/utils/config-merge.test.ts +81 -5
- package/src/utils/config-merge.ts +13 -3
- package/src/utils/defaults-provenance.ts +67 -0
- package/src/utils/dock.ts +7 -1
- package/src/utils/launcher-variant.ts +16 -0
- package/src/utils/panel-config.ts +97 -0
- package/src/utils/status-indicator.ts +39 -0
- package/src/utils/theme.test.ts +182 -0
- package/src/utils/theme.ts +131 -18
- package/src/utils/tokens.test.ts +122 -1
- package/src/utils/tokens.ts +177 -17
- package/src/utils/tool-pair-replay.ts +100 -0
- package/src/welcome.test.ts +23 -0
- package/src/welcome.ts +16 -9
- package/dist/approval-ui-K7R5Q5OC.js +0 -1
|
@@ -82,10 +82,11 @@ describe("createAgentExperience detached panel", () => {
|
|
|
82
82
|
setInnerWidth(originalInnerWidth);
|
|
83
83
|
});
|
|
84
84
|
|
|
85
|
-
it("keeps flush sidebar chrome when detachedPanel is unset", () => {
|
|
85
|
+
it.each([false, true])("keeps flush sidebar chrome when detachedPanel is unset (v5=%s)", (v5Defaults) => {
|
|
86
86
|
const mount = createMount();
|
|
87
87
|
const controller = createAgentExperience(mount, {
|
|
88
88
|
apiUrl: "https://api.example.com/chat",
|
|
89
|
+
future: { v5Defaults },
|
|
89
90
|
launcher: {
|
|
90
91
|
sidebarMode: true,
|
|
91
92
|
position: "bottom-right",
|
|
@@ -100,7 +101,9 @@ describe("createAgentExperience detached panel", () => {
|
|
|
100
101
|
expect(wrapper.style.bottom).toBe("0px");
|
|
101
102
|
expect(wrapper.style.right).toBe("0px");
|
|
102
103
|
expect(wrapper.style.height).toBe("100vh");
|
|
103
|
-
expect(panel?.style.borderRadius).toBe(
|
|
104
|
+
expect(panel?.style.borderRadius).toBe(
|
|
105
|
+
"var(--persona-components-panel-modes-sidebar-borderRadius, 0)"
|
|
106
|
+
);
|
|
104
107
|
|
|
105
108
|
controller.destroy();
|
|
106
109
|
});
|
|
@@ -172,11 +175,12 @@ describe("createAgentExperience detached panel", () => {
|
|
|
172
175
|
controller.destroy();
|
|
173
176
|
});
|
|
174
177
|
|
|
175
|
-
it("drops card chrome for docked + launcher off + detached on a mobile viewport", () => {
|
|
178
|
+
it.each([false, true])("drops card chrome for docked + launcher off + detached on a mobile viewport (v5=%s)", (v5Defaults) => {
|
|
176
179
|
setInnerWidth(480);
|
|
177
180
|
const mount = createMount();
|
|
178
181
|
const controller = createAgentExperience(mount, {
|
|
179
182
|
apiUrl: "https://api.example.com/chat",
|
|
183
|
+
future: { v5Defaults },
|
|
180
184
|
launcher: {
|
|
181
185
|
mountMode: "docked",
|
|
182
186
|
enabled: false,
|
|
@@ -188,7 +192,9 @@ describe("createAgentExperience detached panel", () => {
|
|
|
188
192
|
|
|
189
193
|
// Host-layout goes flush fullscreen: no detached attribute, no card shadow or inset.
|
|
190
194
|
expect(mount.hasAttribute("data-persona-panel-detached")).toBe(false);
|
|
191
|
-
expect(panel?.style.boxShadow).toBe(
|
|
195
|
+
expect(panel?.style.boxShadow).toBe(
|
|
196
|
+
"var(--persona-components-panel-modes-mobile-shadow, none)"
|
|
197
|
+
);
|
|
192
198
|
expect(wrapper?.style.padding).toBe("");
|
|
193
199
|
|
|
194
200
|
controller.destroy();
|
|
@@ -214,20 +220,24 @@ describe("createAgentExperience detached panel", () => {
|
|
|
214
220
|
controller.destroy();
|
|
215
221
|
});
|
|
216
222
|
|
|
217
|
-
it("renders a flush inline embed with no shadow; detached opts elevation back in", () => {
|
|
223
|
+
it.each([false, true])("renders a flush inline embed with no shadow; detached opts elevation back in (v5=%s)", (v5Defaults) => {
|
|
218
224
|
const mount = createMount();
|
|
219
225
|
const controller = createAgentExperience(mount, {
|
|
220
226
|
apiUrl: "https://api.example.com/chat",
|
|
227
|
+
future: { v5Defaults },
|
|
221
228
|
launcher: { enabled: false },
|
|
222
229
|
});
|
|
223
230
|
|
|
224
231
|
const { panel } = queryEmbedRefs(mount);
|
|
225
|
-
expect(panel?.style.boxShadow).toBe(
|
|
232
|
+
expect(panel?.style.boxShadow).toBe(
|
|
233
|
+
"var(--persona-components-panel-modes-inline-shadow, none)"
|
|
234
|
+
);
|
|
226
235
|
controller.destroy();
|
|
227
236
|
|
|
228
237
|
const detachedMount = createMount();
|
|
229
238
|
const detachedController = createAgentExperience(detachedMount, {
|
|
230
239
|
apiUrl: "https://api.example.com/chat",
|
|
240
|
+
future: { v5Defaults },
|
|
231
241
|
launcher: { enabled: false, detachedPanel: true },
|
|
232
242
|
});
|
|
233
243
|
|
|
@@ -235,6 +245,23 @@ describe("createAgentExperience detached panel", () => {
|
|
|
235
245
|
expect(detachedPanel?.style.boxShadow).toContain("--persona-panel-shadow");
|
|
236
246
|
detachedController.destroy();
|
|
237
247
|
});
|
|
248
|
+
|
|
249
|
+
it.each([false, true])("emits mode chrome tokens in defaults v%s", (v5Defaults) => {
|
|
250
|
+
const mount = createMount();
|
|
251
|
+
const controller = createAgentExperience(mount, {
|
|
252
|
+
apiUrl: "https://api.example.com/chat",
|
|
253
|
+
future: { v5Defaults },
|
|
254
|
+
launcher: { enabled: false },
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
const { panel } = queryEmbedRefs(mount);
|
|
258
|
+
expect(mount.dataset.personaDefaults).toBe(v5Defaults ? "v5" : "v4");
|
|
259
|
+
expect(panel?.style.boxShadow).toBe(
|
|
260
|
+
"var(--persona-components-panel-modes-inline-shadow, none)"
|
|
261
|
+
);
|
|
262
|
+
expect(mount.style.getPropertyValue("--persona-components-panel-modes-inline-shadow")).toBe("none");
|
|
263
|
+
controller.destroy();
|
|
264
|
+
});
|
|
238
265
|
});
|
|
239
266
|
|
|
240
267
|
describe("createAgentExperience detached artifact split chrome", () => {
|
package/src/ui.docked.test.ts
CHANGED
|
@@ -10,6 +10,25 @@ describe("createAgentExperience docked mode", () => {
|
|
|
10
10
|
document.body.innerHTML = "";
|
|
11
11
|
});
|
|
12
12
|
|
|
13
|
+
it.each([false, true])("lets an active dark mode token override global panel chrome (v5=%s)", (v5Defaults) => {
|
|
14
|
+
const mount = document.createElement("div");
|
|
15
|
+
document.body.appendChild(mount);
|
|
16
|
+
const controller = createAgentExperience(mount, {
|
|
17
|
+
apiUrl: "https://api.example.com/chat",
|
|
18
|
+
colorScheme: "dark",
|
|
19
|
+
future: { v5Defaults },
|
|
20
|
+
launcher: { mountMode: "docked", autoExpand: true },
|
|
21
|
+
theme: { components: { panel: { border: "2px solid red" } } },
|
|
22
|
+
darkTheme: {
|
|
23
|
+
components: { panel: { modes: { docked: { border: "3px solid blue" } } } },
|
|
24
|
+
},
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
const container = mount.querySelector<HTMLElement>(".persona-widget-container");
|
|
28
|
+
expect(container?.style.border).toBe("3px solid blue");
|
|
29
|
+
controller.destroy();
|
|
30
|
+
});
|
|
31
|
+
|
|
13
32
|
it("toggles docked panel open/closed; built-in launcher stays hidden (open via controller.open)", () => {
|
|
14
33
|
const mount = document.createElement("div");
|
|
15
34
|
document.body.appendChild(mount);
|
|
@@ -94,6 +94,31 @@ describe("header stability across unrelated updates", () => {
|
|
|
94
94
|
|
|
95
95
|
controller.destroy();
|
|
96
96
|
});
|
|
97
|
+
|
|
98
|
+
it.each([false, true])("updates header padding through live theme variables without rebuilding the header (v5=%s)", (v5Defaults) => {
|
|
99
|
+
window.scrollTo = vi.fn();
|
|
100
|
+
const mount = createMount();
|
|
101
|
+
const controller = createAgentExperience(mount, { ...inlineConfig(), future: { v5Defaults } });
|
|
102
|
+
const header = mount.querySelector<HTMLElement>(".persona-widget-header")!;
|
|
103
|
+
|
|
104
|
+
expect(header.style.padding).toBe(
|
|
105
|
+
"var(--persona-components-header-padding, 20px 24px)"
|
|
106
|
+
);
|
|
107
|
+
expect(mount.style.getPropertyValue("--persona-components-header-padding")).toBe(
|
|
108
|
+
v5Defaults ? "8px 8px 8px 16px" : "20px 24px"
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
controller.update({ theme: { components: { header: { padding: "12px 16px" } } } });
|
|
112
|
+
expect(mount.querySelector(".persona-widget-header")).toBe(header);
|
|
113
|
+
expect(mount.style.getPropertyValue("--persona-components-header-padding")).toBe(
|
|
114
|
+
"12px 16px"
|
|
115
|
+
);
|
|
116
|
+
expect(mount.style.getPropertyValue("--persona-components-header-minimalPadding")).toBe(
|
|
117
|
+
"12px 16px"
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
controller.destroy();
|
|
121
|
+
});
|
|
97
122
|
});
|
|
98
123
|
|
|
99
124
|
describe("composer and header icon stability across unrelated updates", () => {
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
import { afterEach, expect, it, vi } from 'vitest';
|
|
4
|
+
import { createAgentExperience } from './ui';
|
|
5
|
+
|
|
6
|
+
let controller: ReturnType<typeof createAgentExperience> | undefined;
|
|
7
|
+
|
|
8
|
+
afterEach(() => {
|
|
9
|
+
controller?.destroy();
|
|
10
|
+
controller = undefined;
|
|
11
|
+
document.body.replaceChildren();
|
|
12
|
+
vi.restoreAllMocks();
|
|
13
|
+
vi.unstubAllGlobals();
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
it('forwards verified identity from the embedded composer on the first turn with history disabled', async () => {
|
|
17
|
+
const chatBodies: Array<Record<string, unknown>> = [];
|
|
18
|
+
vi.stubGlobal('fetch', vi.fn(async (url: string, init: RequestInit) => {
|
|
19
|
+
const body = JSON.parse(String(init.body));
|
|
20
|
+
if (url.endsWith('/init')) {
|
|
21
|
+
return Response.json({
|
|
22
|
+
sessionId: 'session-1',
|
|
23
|
+
expiresAt: new Date(Date.now() + 600_000).toISOString(),
|
|
24
|
+
flow: { id: 'agent-1', name: 'Agent', description: null },
|
|
25
|
+
config: { welcomeMessage: null, placeholder: 'Ask...', theme: null },
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
chatBodies.push(body);
|
|
29
|
+
if (!body.identityProof) {
|
|
30
|
+
return Response.json({ error: 'Tenant scope required' }, { status: 403 });
|
|
31
|
+
}
|
|
32
|
+
return new Response('data: {"type":"done"}\n\n', {
|
|
33
|
+
headers: { 'Content-Type': 'text/event-stream' },
|
|
34
|
+
});
|
|
35
|
+
}));
|
|
36
|
+
const mount = document.createElement('div');
|
|
37
|
+
document.body.appendChild(mount);
|
|
38
|
+
const getIdentityProof = vi.fn().mockResolvedValue('signed-organization-proof');
|
|
39
|
+
controller = createAgentExperience(mount, {
|
|
40
|
+
apiUrl: 'https://example.com',
|
|
41
|
+
clientToken: 'ct_test_identity',
|
|
42
|
+
agentId: 'agent-1',
|
|
43
|
+
identityProvider: 'clerk',
|
|
44
|
+
getIdentityProof,
|
|
45
|
+
features: { history: { enabled: false } },
|
|
46
|
+
launcher: { enabled: false },
|
|
47
|
+
persistState: false,
|
|
48
|
+
});
|
|
49
|
+
const textarea = mount.querySelector<HTMLTextAreaElement>('[data-persona-composer-input]')!;
|
|
50
|
+
textarea.value = 'Show my orders';
|
|
51
|
+
textarea.dispatchEvent(new Event('input', { bubbles: true }));
|
|
52
|
+
mount.querySelector<HTMLFormElement>('[data-persona-composer-form]')!
|
|
53
|
+
.dispatchEvent(new Event('submit', { bubbles: true, cancelable: true }));
|
|
54
|
+
await vi.waitFor(() => expect(chatBodies).toHaveLength(1));
|
|
55
|
+
expect(chatBodies[0].identityProof).toEqual({ provider: 'clerk', token: 'signed-organization-proof' });
|
|
56
|
+
expect(getIdentityProof).toHaveBeenCalledTimes(1);
|
|
57
|
+
expect(mount.querySelector('[data-persona-history-toggle]')).toBeNull();
|
|
58
|
+
});
|
|
@@ -60,6 +60,31 @@ describe("launcher teaser", () => {
|
|
|
60
60
|
controller.destroy();
|
|
61
61
|
});
|
|
62
62
|
|
|
63
|
+
it("keeps the defaults-version root attribute in parity through critical-launcher handoff", () => {
|
|
64
|
+
const prefix = nextPrefix();
|
|
65
|
+
const config = {
|
|
66
|
+
apiUrl: "https://api.example.com/chat",
|
|
67
|
+
persistState: { keyPrefix: prefix },
|
|
68
|
+
future: { v5Defaults: false },
|
|
69
|
+
};
|
|
70
|
+
const host = createMount();
|
|
71
|
+
const controller = createAgentExperience(host, config);
|
|
72
|
+
const criticalTarget = createMount();
|
|
73
|
+
const critical = mountCriticalLauncher({ target: criticalTarget, config, onOpen: () => {} });
|
|
74
|
+
|
|
75
|
+
expect(host.getAttribute("data-persona-defaults")).toBe("v4");
|
|
76
|
+
expect(critical.root.getAttribute("data-persona-defaults")).toBe("v4");
|
|
77
|
+
|
|
78
|
+
controller.update({ future: { v5Defaults: true } });
|
|
79
|
+
critical.update({ ...config, future: { v5Defaults: true } });
|
|
80
|
+
|
|
81
|
+
expect(host.getAttribute("data-persona-defaults")).toBe("v5");
|
|
82
|
+
expect(critical.root.getAttribute("data-persona-defaults")).toBe("v5");
|
|
83
|
+
|
|
84
|
+
critical.destroy();
|
|
85
|
+
controller.destroy();
|
|
86
|
+
});
|
|
87
|
+
|
|
63
88
|
it("live-updates the teaser through controller.update()", () => {
|
|
64
89
|
const prefix = nextPrefix();
|
|
65
90
|
const host = createMount();
|
|
@@ -135,6 +135,21 @@ describe("createAgentExperience: role-specific message width", () => {
|
|
|
135
135
|
controller.destroy();
|
|
136
136
|
});
|
|
137
137
|
|
|
138
|
+
it.each([false, true])("allows explicit max-width tokens on full rows with v5Defaults=%s", (v5Defaults) => {
|
|
139
|
+
const mount = createMount();
|
|
140
|
+
const controller = createController(mount, {
|
|
141
|
+
future: { v5Defaults },
|
|
142
|
+
theme: { components: { message: { assistant: { maxWidth: "65ch" } } } },
|
|
143
|
+
layout: { messages: { assistant: { width: "full" } } },
|
|
144
|
+
});
|
|
145
|
+
injectMessage(controller, { id: "token-width", role: "assistant", content: "Response" });
|
|
146
|
+
expectRowLayout(getRow(mount, "token-width"), {
|
|
147
|
+
role: "assistant", width: "full", maxWidth: "var(--persona-message-assistant-max-width, 100%)",
|
|
148
|
+
});
|
|
149
|
+
expect(mount.style.getPropertyValue("--persona-message-assistant-max-width")).toBe("65ch");
|
|
150
|
+
controller.destroy();
|
|
151
|
+
});
|
|
152
|
+
|
|
138
153
|
it("configures user and assistant geometry independently", () => {
|
|
139
154
|
const mount = createMount();
|
|
140
155
|
const controller = createController(mount, {
|
|
@@ -165,7 +180,7 @@ describe("createAgentExperience: role-specific message width", () => {
|
|
|
165
180
|
expectRowLayout(getRow(mount, "assistant-full"), {
|
|
166
181
|
role: "assistant",
|
|
167
182
|
width: "full",
|
|
168
|
-
maxWidth: "100%",
|
|
183
|
+
maxWidth: "var(--persona-message-assistant-max-width, 100%)",
|
|
169
184
|
});
|
|
170
185
|
|
|
171
186
|
controller.destroy();
|
|
@@ -316,7 +331,7 @@ describe("createAgentExperience: role-specific message width", () => {
|
|
|
316
331
|
expectRowLayout(userRow, {
|
|
317
332
|
role: "user",
|
|
318
333
|
width: "full",
|
|
319
|
-
maxWidth: "100%",
|
|
334
|
+
maxWidth: "var(--persona-message-user-max-width, 100%)",
|
|
320
335
|
});
|
|
321
336
|
expect(userRow.querySelector(":scope > [data-custom-user]")).not.toBeNull();
|
|
322
337
|
|
|
@@ -81,6 +81,22 @@ const injectText = (
|
|
|
81
81
|
const rowOf = (mount: HTMLElement, id: string) =>
|
|
82
82
|
mount.querySelector<HTMLElement>(`#wrapper-${id}`);
|
|
83
83
|
|
|
84
|
+
const toggleExpansion = (mount: HTMLElement, id: string, bubbleType: "reasoning" | "tool") => {
|
|
85
|
+
const header = rowOf(mount, id)?.querySelector<HTMLElement>(
|
|
86
|
+
`button[data-bubble-type="${bubbleType}"]`
|
|
87
|
+
);
|
|
88
|
+
expect(header).not.toBeNull();
|
|
89
|
+
header?.dispatchEvent(new Event("pointerdown", { bubbles: true }));
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const expansionStateOf = (mount: HTMLElement, id: string, bubbleType: "reasoning" | "tool") => {
|
|
93
|
+
const header = rowOf(mount, id)?.querySelector<HTMLElement>(
|
|
94
|
+
`button[data-bubble-type="${bubbleType}"]`
|
|
95
|
+
);
|
|
96
|
+
expect(header).not.toBeNull();
|
|
97
|
+
return header!.getAttribute("aria-expanded");
|
|
98
|
+
};
|
|
99
|
+
|
|
84
100
|
describe("features.reasoningDisplay.iconName", () => {
|
|
85
101
|
beforeEach(() => {
|
|
86
102
|
window.localStorage.clear();
|
|
@@ -296,3 +312,79 @@ describe("features.toolCallDisplay.completedVisibility", () => {
|
|
|
296
312
|
).toEqual("complete");
|
|
297
313
|
});
|
|
298
314
|
});
|
|
315
|
+
|
|
316
|
+
describe("expandable transcript state", () => {
|
|
317
|
+
beforeEach(() => {
|
|
318
|
+
window.localStorage.clear();
|
|
319
|
+
vi.stubGlobal("requestAnimationFrame", (cb: (time: number) => void) => {
|
|
320
|
+
cb(0);
|
|
321
|
+
return 1;
|
|
322
|
+
});
|
|
323
|
+
vi.stubGlobal("cancelAnimationFrame", () => {});
|
|
324
|
+
window.scrollTo = vi.fn();
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
afterEach(() => {
|
|
328
|
+
controllers.splice(0).forEach((controller) => controller.destroy());
|
|
329
|
+
mounts.splice(0).forEach((mount) => mount.remove());
|
|
330
|
+
document.body.innerHTML = "";
|
|
331
|
+
vi.restoreAllMocks();
|
|
332
|
+
});
|
|
333
|
+
|
|
334
|
+
it("keeps tool and reasoning expansion separate for widgets with matching message ids", () => {
|
|
335
|
+
const first = makeController();
|
|
336
|
+
injectReasoning(first.controller, "shared-reasoning", "complete");
|
|
337
|
+
injectToolCall(first.controller, "shared-tool", "complete");
|
|
338
|
+
toggleExpansion(first.mount, "shared-reasoning", "reasoning");
|
|
339
|
+
toggleExpansion(first.mount, "shared-tool", "tool");
|
|
340
|
+
expect(expansionStateOf(first.mount, "shared-reasoning", "reasoning")).toBe("true");
|
|
341
|
+
expect(expansionStateOf(first.mount, "shared-tool", "tool")).toBe("true");
|
|
342
|
+
|
|
343
|
+
// The transcript derives DOM ids from the message id. Keep both widgets
|
|
344
|
+
// mounted while removing only the first rendered row ids so the fixture
|
|
345
|
+
// can model equal message ids without duplicate document ids.
|
|
346
|
+
first.mount.querySelectorAll("[id]").forEach((element) => element.removeAttribute("id"));
|
|
347
|
+
|
|
348
|
+
const second = makeController();
|
|
349
|
+
injectReasoning(second.controller, "shared-reasoning", "complete");
|
|
350
|
+
injectToolCall(second.controller, "shared-tool", "complete");
|
|
351
|
+
|
|
352
|
+
expect(expansionStateOf(second.mount, "shared-reasoning", "reasoning")).toBe("false");
|
|
353
|
+
expect(expansionStateOf(second.mount, "shared-tool", "tool")).toBe("false");
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it("resets tool and reasoning expansion on clearChat", () => {
|
|
357
|
+
const { mount, controller } = makeController();
|
|
358
|
+
injectReasoning(controller, "reasoning-after-clear", "complete");
|
|
359
|
+
injectToolCall(controller, "tool-after-clear", "complete");
|
|
360
|
+
toggleExpansion(mount, "reasoning-after-clear", "reasoning");
|
|
361
|
+
toggleExpansion(mount, "tool-after-clear", "tool");
|
|
362
|
+
expect(expansionStateOf(mount, "reasoning-after-clear", "reasoning")).toBe("true");
|
|
363
|
+
expect(expansionStateOf(mount, "tool-after-clear", "tool")).toBe("true");
|
|
364
|
+
|
|
365
|
+
controller.clearChat();
|
|
366
|
+
injectReasoning(controller, "reasoning-after-clear", "complete");
|
|
367
|
+
injectToolCall(controller, "tool-after-clear", "complete");
|
|
368
|
+
|
|
369
|
+
expect(expansionStateOf(mount, "reasoning-after-clear", "reasoning")).toBe("false");
|
|
370
|
+
expect(expansionStateOf(mount, "tool-after-clear", "tool")).toBe("false");
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
it("releases expansion state when a widget is destroyed", () => {
|
|
374
|
+
const first = makeController();
|
|
375
|
+
injectReasoning(first.controller, "reasoning-after-destroy", "complete");
|
|
376
|
+
injectToolCall(first.controller, "tool-after-destroy", "complete");
|
|
377
|
+
toggleExpansion(first.mount, "reasoning-after-destroy", "reasoning");
|
|
378
|
+
toggleExpansion(first.mount, "tool-after-destroy", "tool");
|
|
379
|
+
expect(expansionStateOf(first.mount, "reasoning-after-destroy", "reasoning")).toBe("true");
|
|
380
|
+
expect(expansionStateOf(first.mount, "tool-after-destroy", "tool")).toBe("true");
|
|
381
|
+
first.controller.destroy();
|
|
382
|
+
|
|
383
|
+
const second = makeController();
|
|
384
|
+
injectReasoning(second.controller, "reasoning-after-destroy", "complete");
|
|
385
|
+
injectToolCall(second.controller, "tool-after-destroy", "complete");
|
|
386
|
+
|
|
387
|
+
expect(expansionStateOf(second.mount, "reasoning-after-destroy", "reasoning")).toBe("false");
|
|
388
|
+
expect(expansionStateOf(second.mount, "tool-after-destroy", "tool")).toBe("false");
|
|
389
|
+
});
|
|
390
|
+
});
|
|
@@ -366,23 +366,40 @@ describe("rebuildComposer (composer ctx requestRender)", () => {
|
|
|
366
366
|
expect(footer.style.display).toBe("none");
|
|
367
367
|
});
|
|
368
368
|
|
|
369
|
-
it("keeps the floating panel's fixed height across a rebuild", () => {
|
|
369
|
+
it.each([false, true])("keeps the floating panel's fixed height across a rebuild (v5=%s)", (v5Defaults) => {
|
|
370
370
|
// applyFullHeightStyles wipes panel.style.cssText and only restores width;
|
|
371
371
|
// the fixed height belongs to recalcPanelHeight. Without a re-stamp in
|
|
372
372
|
// syncPanelChrome, a rebuild leaves the floating panel height-less and it
|
|
373
373
|
// grows to fit its content (pre-chat submit stretched to the viewport).
|
|
374
374
|
const { plugin, state } = createGatePlugin();
|
|
375
375
|
const { mount } = makeController({
|
|
376
|
+
future: { v5Defaults },
|
|
376
377
|
plugins: [plugin],
|
|
377
378
|
launcher: { enabled: true },
|
|
378
379
|
});
|
|
379
380
|
|
|
381
|
+
expect(mount.style.getPropertyValue("--persona-components-panel-height")).toBe(v5Defaults ? "min(704px, calc(100dvh - 104px))" : "min(640px, max(200px, calc(100vh - 64px)))");
|
|
380
382
|
const panel = mount.querySelector<HTMLElement>(".persona-widget-panel")!;
|
|
381
|
-
expect(panel.style.height).toBe("
|
|
383
|
+
expect(panel.style.height).toBe("max(200px, var(--persona-components-panel-height))");
|
|
382
384
|
|
|
383
385
|
state.ctx!.requestRender();
|
|
384
386
|
|
|
385
|
-
expect(panel.style.height).toBe("
|
|
387
|
+
expect(panel.style.height).toBe("max(200px, var(--persona-components-panel-height))");
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
it.each([false, true])("retains transient connection status through a composer rebuild (v5=%s)", (v5Defaults) => {
|
|
391
|
+
const { plugin, state } = createGatePlugin();
|
|
392
|
+
state.gated = false;
|
|
393
|
+
const { mount, controller } = makeController({
|
|
394
|
+
plugins: [plugin], future: { v5Defaults },
|
|
395
|
+
statusIndicator: { mode: "transient", connectingText: "Connecting now" },
|
|
396
|
+
});
|
|
397
|
+
controller.injectTestMessage({ type: "status", status: "connecting" });
|
|
398
|
+
state.ctx!.requestRender();
|
|
399
|
+
const status = mount.querySelector<HTMLElement>("[data-persona-composer-status]")!;
|
|
400
|
+
expect(status.style.display).toBe("");
|
|
401
|
+
expect(status.textContent).toBe("Connecting now");
|
|
402
|
+
expect(status.nextElementSibling?.matches("[data-persona-composer-form]")).toBe(true);
|
|
386
403
|
});
|
|
387
404
|
|
|
388
405
|
it("does not stamp copy onto a plugin-owned composer's input", () => {
|
package/src/ui.scroll.test.ts
CHANGED
|
@@ -818,6 +818,38 @@ describe("createAgentExperience streaming scroll", () => {
|
|
|
818
818
|
controller.destroy();
|
|
819
819
|
});
|
|
820
820
|
|
|
821
|
+
describe.each([false, true])('manual activity disclosure (V5 %s)', v5Defaults => {
|
|
822
|
+
it.each(['pointer', 'keyboard'])('keeps the viewport after %s expansion and later content growth', activation => {
|
|
823
|
+
const raf = installRafMock();
|
|
824
|
+
const resize = installResizeObserverMock();
|
|
825
|
+
const mount = createMount();
|
|
826
|
+
const controller = createAgentExperience(mount, {
|
|
827
|
+
apiUrl: '/test', future: { v5Defaults }, launcher: { enabled: false },
|
|
828
|
+
features: { toolCallDisplay: { variant: 'row', grouped: false, autoExpand: false } }
|
|
829
|
+
});
|
|
830
|
+
const body = mount.querySelector<HTMLElement>('#persona-scroll-container')!;
|
|
831
|
+
const metrics = installScrollMetrics(body, { scrollHeight: 1000, clientHeight: 400 });
|
|
832
|
+
controller.injectTestMessage({ type: 'message', message: {
|
|
833
|
+
id: 'tool-scroll', role: 'assistant', content: '', createdAt: STREAM_CREATED_AT,
|
|
834
|
+
variant: 'tool', toolCall: { id: 'tool-scroll', name: 'Search', status: 'complete', result: 'Long result' }
|
|
835
|
+
} });
|
|
836
|
+
raf.flush();
|
|
837
|
+
metrics.setScrollTop(600);
|
|
838
|
+
const header = mount.querySelector<HTMLButtonElement>('[data-message-id="tool-scroll"] > button')!;
|
|
839
|
+
header.dispatchEvent(activation === 'pointer'
|
|
840
|
+
? new MouseEvent('pointerdown', { bubbles: true, cancelable: true })
|
|
841
|
+
: new KeyboardEvent('keydown', { key: 'Enter', bubbles: true, cancelable: true }));
|
|
842
|
+
expect(header.getAttribute('aria-expanded')).toBe('true');
|
|
843
|
+
metrics.setScrollHeight(1400);
|
|
844
|
+
resize.trigger(); raf.flush();
|
|
845
|
+
expect(metrics.getScrollTop()).toBe(600);
|
|
846
|
+
metrics.setScrollHeight(1600);
|
|
847
|
+
resize.trigger(); raf.flush();
|
|
848
|
+
expect(metrics.getScrollTop()).toBe(600);
|
|
849
|
+
controller.destroy();
|
|
850
|
+
});
|
|
851
|
+
});
|
|
852
|
+
|
|
821
853
|
it("does not yank a paused reader when content grows without a render event", () => {
|
|
822
854
|
const raf = installRafMock();
|
|
823
855
|
const resize = installResizeObserverMock();
|