@runtypelabs/persona 4.17.0 → 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.
- package/dist/animations/glyph-cycle.d.cts +1 -1
- package/dist/animations/glyph-cycle.d.ts +1 -1
- package/dist/animations/{types-BpWGm7_6.d.cts → types-DsmTD8TO.d.cts} +12 -0
- package/dist/animations/{types-BpWGm7_6.d.ts → types-DsmTD8TO.d.ts} +12 -0
- package/dist/animations/wipe.d.cts +1 -1
- package/dist/animations/wipe.d.ts +1 -1
- package/dist/chunk-3I2S3I5P.js +1 -0
- package/dist/chunk-76IELU7F.js +1 -0
- package/dist/chunk-EFJBUH5F.js +1 -0
- package/dist/chunk-PDPVORUF.js +1 -0
- package/dist/chunk-Q735PSZL.js +1 -0
- package/dist/chunk-VK6JOIYJ.js +1 -0
- package/dist/codegen.cjs +4 -4
- package/dist/codegen.js +4 -4
- package/dist/{context-mentions-BTSTIBFC.js → context-mentions-MJL6S6P2.js} +1 -1
- package/dist/context-mentions.cjs +9 -8
- package/dist/context-mentions.d.cts +1224 -14
- package/dist/context-mentions.d.ts +1224 -14
- package/dist/context-mentions.js +9 -8
- package/dist/event-stream-view-4EZ7BGFK.js +1 -0
- package/dist/event-stream-view.cjs +129 -0
- package/dist/event-stream-view.d.cts +8384 -0
- package/dist/event-stream-view.d.ts +8384 -0
- package/dist/event-stream-view.js +129 -0
- package/dist/history-view-BD6OXLNA.js +898 -0
- package/dist/history-view.cjs +898 -0
- package/dist/history-view.d.cts +1461 -0
- package/dist/history-view.d.ts +1461 -0
- package/dist/history-view.js +898 -0
- package/dist/index.cjs +77 -77
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +2040 -21
- package/dist/index.d.ts +2040 -21
- package/dist/index.global.js +67 -66
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +77 -77
- package/dist/index.js.map +1 -1
- package/dist/launcher.global.js +5 -4
- package/dist/launcher.global.js.map +1 -1
- package/dist/plugin-kit.cjs +1 -1
- package/dist/plugin-kit.d.cts +0 -8
- package/dist/plugin-kit.d.ts +0 -8
- package/dist/plugin-kit.js +1 -1
- package/dist/smart-dom-reader.d.cts +1224 -14
- package/dist/smart-dom-reader.d.ts +1224 -14
- package/dist/theme-editor-preview.cjs +970 -73
- package/dist/theme-editor-preview.d.cts +1343 -15
- package/dist/theme-editor-preview.d.ts +1343 -15
- package/dist/theme-editor-preview.js +81 -81
- package/dist/theme-editor.cjs +7 -7
- package/dist/theme-editor.d.cts +1227 -15
- package/dist/theme-editor.d.ts +1227 -15
- package/dist/theme-editor.js +9 -9
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.d.cts +226 -7
- package/dist/theme-reference.d.ts +226 -7
- package/dist/theme-reference.js +1 -1
- package/dist/webmcp-polyfill.js +2 -2
- package/dist/widget.css +1 -1
- package/package.json +18 -6
- package/src/client-history-rest.test.ts +1034 -0
- package/src/client-visitor-history.test.ts +596 -0
- package/src/client.ts +1040 -24
- package/src/components/artifact-inline.ts +8 -1
- package/src/components/artifact-pane.test.ts +278 -1
- package/src/components/artifact-pane.ts +135 -67
- package/src/components/artifact-preview.test.ts +14 -1
- package/src/components/artifact-preview.ts +9 -0
- package/src/components/header-builder.ts +3 -0
- package/src/components/header-layouts.test.ts +171 -7
- package/src/components/header-layouts.ts +83 -53
- package/src/components/header-parts.test.ts +154 -1
- package/src/components/header-parts.ts +266 -138
- package/src/components/history-confirm-dialog.ts +177 -0
- package/src/components/history-shell-copy.ts +99 -0
- package/src/components/history-view/announcer.ts +40 -0
- package/src/components/history-view/copy.ts +105 -0
- package/src/components/history-view/css.ts +1078 -0
- package/src/components/history-view/grouping.test.ts +142 -0
- package/src/components/history-view/grouping.ts +142 -0
- package/src/components/history-view/icons.ts +73 -0
- package/src/components/history-view/rows.ts +341 -0
- package/src/components/history-view/state.ts +309 -0
- package/src/components/history-view.test.ts +2660 -0
- package/src/components/history-view.ts +1891 -0
- package/src/components/launcher-surface.test.ts +133 -0
- package/src/components/launcher.test.ts +18 -0
- package/src/components/launcher.ts +7 -7
- package/src/components/panel.ts +17 -7
- package/src/components/welcome.ts +72 -2
- package/src/defaults.ts +36 -3
- package/src/event-stream-view-bundle.test.ts +69 -0
- package/src/event-stream-view-entry.ts +19 -0
- package/src/event-stream-view-loader.ts +31 -0
- package/src/event-stream-view.ts +19 -0
- package/src/generated/runtype-openapi-contract.ts +54 -4
- package/src/history-render.ts +325 -0
- package/src/history-view-bundle.test.ts +78 -0
- package/src/history-view-entry.ts +41 -0
- package/src/history-view-loader.test.ts +30 -0
- package/src/history-view-loader.ts +32 -0
- package/src/history-view.ts +28 -0
- package/src/index-core.ts +50 -2
- package/src/index-global.ts +55 -0
- package/src/internal/demo-history-provider.test.ts +402 -0
- package/src/internal/demo-history-provider.ts +689 -0
- package/src/internal/history-provider-registry.ts +25 -0
- package/src/internal/history-provider.ts +150 -0
- package/src/internal/runtype-history-provider.test.ts +313 -0
- package/src/internal/runtype-history-provider.ts +240 -0
- package/src/plugin-kit.test.ts +16 -0
- package/src/plugin-kit.ts +13 -5
- package/src/plugins/types.ts +177 -1
- package/src/session-history.test.ts +1397 -0
- package/src/session.ts +1200 -26
- package/src/styles/widget-styles.test.ts +133 -0
- package/src/styles/widget.css +227 -8
- package/src/theme-editor/preview-utils.test.ts +19 -0
- package/src/theme-editor/preview-utils.ts +15 -1
- package/src/theme-editor/preview.ts +7 -1
- package/src/theme-editor/role-mappings.ts +4 -1
- package/src/theme-editor/sections.test.ts +168 -1
- package/src/theme-editor/sections.ts +136 -7
- package/src/theme-editor/webmcp/summary.ts +16 -2
- package/src/theme-reference.ts +20 -6
- package/src/types/theme.ts +213 -7
- package/src/types.ts +927 -7
- package/src/ui.artifact-rail-resize.test.ts +235 -0
- package/src/ui.attachments-drop.test.ts +1 -0
- package/src/ui.header-update-stability.test.ts +2 -2
- package/src/ui.history-chunk.test.ts +72 -0
- package/src/ui.history-config-provider.test.ts +318 -0
- package/src/ui.history-controller.test.ts +711 -0
- package/src/ui.history-lifecycle.test.ts +456 -0
- package/src/ui.history-render-hooks.test.ts +904 -0
- package/src/ui.history-shell.test.ts +1887 -0
- package/src/ui.rail-overlay.test.ts +573 -0
- package/src/ui.rail-resize.test.ts +284 -0
- package/src/ui.rebuild-composer.test.ts +21 -0
- package/src/ui.render-welcome.test.ts +42 -0
- package/src/ui.shortcuts.test.ts +166 -0
- package/src/ui.ts +3071 -240
- package/src/utils/__fixtures__/client-conversation-history-wire.json +108 -0
- package/src/utils/artifact-file.ts +15 -0
- package/src/utils/artifact-gate.test.ts +4 -0
- package/src/utils/artifact-gate.ts +13 -17
- package/src/utils/artifact-resize.test.ts +28 -0
- package/src/utils/artifact-resize.ts +9 -2
- package/src/utils/buttons.ts +188 -1
- package/src/utils/config-merge.test.ts +91 -0
- package/src/utils/dropdown.ts +10 -1
- package/src/utils/history-messages.test.ts +284 -0
- package/src/utils/history-messages.ts +284 -0
- package/src/utils/icons.ts +3 -0
- package/src/utils/shortcuts.test.ts +288 -0
- package/src/utils/shortcuts.ts +221 -0
- package/src/utils/theme.test.ts +501 -4
- package/src/utils/theme.ts +42 -3
- package/src/utils/tokens.ts +153 -15
- package/src/utils/tooltip-timing.ts +4 -0
- package/src/utils/tooltip.test.ts +249 -7
- package/src/utils/tooltip.ts +146 -4
- package/src/utils/visitor-store.test.ts +425 -0
- package/src/utils/visitor-store.ts +337 -0
- package/dist/chunk-QJ4LV2KW.js +0 -1
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
// @vitest-environment jsdom
|
|
2
2
|
|
|
3
|
+
import { readFileSync } from "node:fs";
|
|
4
|
+
import { resolve } from "node:path";
|
|
5
|
+
|
|
3
6
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
4
7
|
import { createLauncherSurface } from "./launcher";
|
|
8
|
+
import { mount as mountCriticalLauncher } from "../launcher-global";
|
|
9
|
+
import { createAgentExperience } from "../ui";
|
|
5
10
|
import type {
|
|
6
11
|
AgentWidgetConfig,
|
|
7
12
|
AgentWidgetLauncherTeaserConfig,
|
|
@@ -401,3 +406,131 @@ describe("createLauncherSurface", () => {
|
|
|
401
406
|
expect(document.body.children.length).toBe(0);
|
|
402
407
|
});
|
|
403
408
|
});
|
|
409
|
+
|
|
410
|
+
/**
|
|
411
|
+
* The deferred install paints the launcher from the critical bundle and swaps
|
|
412
|
+
* to the full widget on first click, so any glyph styling must reach both
|
|
413
|
+
* surfaces from the one stylesheet or the swap flickers.
|
|
414
|
+
*/
|
|
415
|
+
describe("launcher icon stroke parity", () => {
|
|
416
|
+
// jsdom leaves `import.meta.url` an http URL, so resolve from the package root.
|
|
417
|
+
const widgetCss = readFileSync(
|
|
418
|
+
resolve(process.cwd(), "src/styles/widget.css"),
|
|
419
|
+
"utf8"
|
|
420
|
+
);
|
|
421
|
+
// The SHIPPED selector, read back out of widget.css by its declaration, so
|
|
422
|
+
// a rescope of the real rule fails here instead of slipping past a
|
|
423
|
+
// hardcoded copy of the selector.
|
|
424
|
+
const strokeDeclarationAt = widgetCss.indexOf(
|
|
425
|
+
"stroke-width: var(--persona-components-launcher-iconStrokeWidth, 1.5)"
|
|
426
|
+
);
|
|
427
|
+
const strokeSelectorStart = widgetCss.lastIndexOf("}", strokeDeclarationAt) + 1;
|
|
428
|
+
const STROKE_RULE = widgetCss
|
|
429
|
+
.slice(strokeSelectorStart, widgetCss.indexOf("{", strokeSelectorStart))
|
|
430
|
+
.replace(/\/\*[\s\S]*?\*\//g, "")
|
|
431
|
+
.trim();
|
|
432
|
+
|
|
433
|
+
beforeEach(() => {
|
|
434
|
+
window.scrollTo = vi.fn();
|
|
435
|
+
window.localStorage.clear();
|
|
436
|
+
});
|
|
437
|
+
|
|
438
|
+
afterEach(() => {
|
|
439
|
+
document.body.innerHTML = "";
|
|
440
|
+
vi.restoreAllMocks();
|
|
441
|
+
});
|
|
442
|
+
|
|
443
|
+
it("renders one glyph the shared stroke rule reaches from both surfaces", () => {
|
|
444
|
+
const config = {
|
|
445
|
+
apiUrl: "https://api.example.com/chat",
|
|
446
|
+
persistState: { keyPrefix: nextPrefix() },
|
|
447
|
+
launcher: {
|
|
448
|
+
agentIconName: "message-circle",
|
|
449
|
+
callToActionIconName: "arrow-up-right",
|
|
450
|
+
},
|
|
451
|
+
};
|
|
452
|
+
|
|
453
|
+
const host = document.createElement("div");
|
|
454
|
+
document.body.appendChild(host);
|
|
455
|
+
const controller = createAgentExperience(host, config);
|
|
456
|
+
|
|
457
|
+
const criticalTarget = document.createElement("div");
|
|
458
|
+
document.body.appendChild(criticalTarget);
|
|
459
|
+
const critical = mountCriticalLauncher({
|
|
460
|
+
target: criticalTarget,
|
|
461
|
+
config,
|
|
462
|
+
onOpen: () => {},
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
const iconSvg = "[data-role='launcher-icon'] > svg";
|
|
466
|
+
const eager = host.querySelector<SVGElement>(iconSvg);
|
|
467
|
+
const deferred = criticalTarget.querySelector<SVGElement>(iconSvg);
|
|
468
|
+
|
|
469
|
+
expect(eager).not.toBeNull();
|
|
470
|
+
expect(deferred).not.toBeNull();
|
|
471
|
+
expect(deferred!.outerHTML).toBe(eager!.outerHTML);
|
|
472
|
+
// matches() resolves the descendant combinator against each surface's own
|
|
473
|
+
// ancestors, and STROKE_RULE is the selector parsed from widget.css, so
|
|
474
|
+
// this proves the shipped rule selects the glyph in both surfaces.
|
|
475
|
+
expect(eager!.matches(STROKE_RULE)).toBe(true);
|
|
476
|
+
expect(deferred!.matches(STROKE_RULE)).toBe(true);
|
|
477
|
+
|
|
478
|
+
// One pill, one line weight: the call-to-action arrow follows the same
|
|
479
|
+
// rule as the agent glyph on both surfaces.
|
|
480
|
+
const ctaSvg = "[data-role='launcher-call-to-action-icon'] > svg";
|
|
481
|
+
const eagerCta = host.querySelector<SVGElement>(ctaSvg);
|
|
482
|
+
const deferredCta = criticalTarget.querySelector<SVGElement>(ctaSvg);
|
|
483
|
+
expect(eagerCta).not.toBeNull();
|
|
484
|
+
expect(deferredCta).not.toBeNull();
|
|
485
|
+
expect(eagerCta!.matches(STROKE_RULE)).toBe(true);
|
|
486
|
+
expect(deferredCta!.matches(STROKE_RULE)).toBe(true);
|
|
487
|
+
|
|
488
|
+
critical.destroy();
|
|
489
|
+
controller.destroy();
|
|
490
|
+
});
|
|
491
|
+
|
|
492
|
+
it("emits the stroke token onto both surfaces' roots", () => {
|
|
493
|
+
const config = {
|
|
494
|
+
apiUrl: "https://api.example.com/chat",
|
|
495
|
+
persistState: { keyPrefix: nextPrefix() },
|
|
496
|
+
launcher: { agentIconName: "message-circle" },
|
|
497
|
+
theme: { components: { launcher: { iconStrokeWidth: "1.25" } } },
|
|
498
|
+
};
|
|
499
|
+
|
|
500
|
+
const host = document.createElement("div");
|
|
501
|
+
document.body.appendChild(host);
|
|
502
|
+
const controller = createAgentExperience(host, config);
|
|
503
|
+
|
|
504
|
+
const criticalTarget = document.createElement("div");
|
|
505
|
+
document.body.appendChild(criticalTarget);
|
|
506
|
+
const critical = mountCriticalLauncher({
|
|
507
|
+
target: criticalTarget,
|
|
508
|
+
config,
|
|
509
|
+
onOpen: () => {},
|
|
510
|
+
});
|
|
511
|
+
|
|
512
|
+
// The theme plumbing is duplicated across the two bundles; this is the
|
|
513
|
+
// claim that breaks if they diverge.
|
|
514
|
+
const fullRoot = host.hasAttribute("data-persona-root")
|
|
515
|
+
? host
|
|
516
|
+
: host.querySelector<HTMLElement>("[data-persona-root]");
|
|
517
|
+
const criticalRoot = criticalTarget.querySelector<HTMLElement>(
|
|
518
|
+
"[data-persona-root]"
|
|
519
|
+
);
|
|
520
|
+
expect(fullRoot).not.toBeNull();
|
|
521
|
+
expect(criticalRoot).not.toBeNull();
|
|
522
|
+
expect(
|
|
523
|
+
fullRoot!.style.getPropertyValue(
|
|
524
|
+
"--persona-components-launcher-iconStrokeWidth"
|
|
525
|
+
)
|
|
526
|
+
).toBe("1.25");
|
|
527
|
+
expect(
|
|
528
|
+
criticalRoot!.style.getPropertyValue(
|
|
529
|
+
"--persona-components-launcher-iconStrokeWidth"
|
|
530
|
+
)
|
|
531
|
+
).toBe("1.25");
|
|
532
|
+
|
|
533
|
+
critical.destroy();
|
|
534
|
+
controller.destroy();
|
|
535
|
+
});
|
|
536
|
+
});
|
|
@@ -16,6 +16,24 @@ describe("createLauncherButton", () => {
|
|
|
16
16
|
element.remove();
|
|
17
17
|
});
|
|
18
18
|
|
|
19
|
+
it("renders the pill from launcher-specific theme vars", () => {
|
|
20
|
+
const { element, update } = createLauncherButton(undefined, () => {});
|
|
21
|
+
update({});
|
|
22
|
+
expect(element.classList.contains("persona-bg-persona-launcher")).toBe(true);
|
|
23
|
+
expect(
|
|
24
|
+
element
|
|
25
|
+
.querySelector("[data-role='launcher-title']")
|
|
26
|
+
?.classList.contains("persona-text-persona-launcher")
|
|
27
|
+
).toBe(true);
|
|
28
|
+
expect(
|
|
29
|
+
element
|
|
30
|
+
.querySelector("[data-role='launcher-subtitle']")
|
|
31
|
+
?.classList.contains("persona-text-persona-launcher-muted")
|
|
32
|
+
).toBe(true);
|
|
33
|
+
expect(element.style.border).toContain("--persona-launcher-border");
|
|
34
|
+
element.remove();
|
|
35
|
+
});
|
|
36
|
+
|
|
19
37
|
it("applies collapsedMaxWidth when set", () => {
|
|
20
38
|
const { element, update } = createLauncherButton(undefined, () => {});
|
|
21
39
|
update({
|
|
@@ -21,8 +21,8 @@ export const createLauncherButton = (
|
|
|
21
21
|
<span class="persona-inline-flex persona-items-center persona-justify-center persona-rounded-full persona-bg-persona-primary persona-text-white" data-role="launcher-icon">💬</span>
|
|
22
22
|
<img data-role="launcher-image" class="persona-rounded-full persona-object-cover" alt="" style="display:none" />
|
|
23
23
|
<span class="persona-flex persona-min-w-0 persona-flex-1 persona-flex-col persona-items-start persona-text-left">
|
|
24
|
-
<span class="persona-block persona-w-full persona-truncate persona-text-sm persona-font-semibold persona-text-persona-
|
|
25
|
-
<span class="persona-block persona-w-full persona-truncate persona-text-xs persona-text-persona-muted" data-role="launcher-subtitle"></span>
|
|
24
|
+
<span class="persona-block persona-w-full persona-truncate persona-text-sm persona-font-semibold persona-text-persona-launcher" data-role="launcher-title"></span>
|
|
25
|
+
<span class="persona-block persona-w-full persona-truncate persona-text-xs persona-text-persona-launcher-muted" data-role="launcher-subtitle"></span>
|
|
26
26
|
</span>
|
|
27
27
|
<span class="persona-ml-2 persona-grid persona-place-items-center persona-rounded-full persona-bg-persona-primary persona-text-persona-call-to-action" data-role="launcher-call-to-action-icon">↗</span>
|
|
28
28
|
`;
|
|
@@ -82,7 +82,7 @@ export const createLauncherButton = (
|
|
|
82
82
|
if (launcher.agentIconName) {
|
|
83
83
|
// Use Lucide icon
|
|
84
84
|
const iconSizeNum = parseFloat(iconSize) || 24;
|
|
85
|
-
const iconSvg = renderLucideIcon(launcher.agentIconName, iconSizeNum * 0.6, "var(--persona-text-inverse, #ffffff)",
|
|
85
|
+
const iconSvg = renderLucideIcon(launcher.agentIconName, iconSizeNum * 0.6, "var(--persona-text-inverse, #ffffff)", 1.5);
|
|
86
86
|
if (iconSvg) {
|
|
87
87
|
icon.appendChild(iconSvg);
|
|
88
88
|
icon.style.display = "";
|
|
@@ -166,7 +166,7 @@ export const createLauncherButton = (
|
|
|
166
166
|
// Calculate actual icon size by subtracting padding
|
|
167
167
|
const containerSize = parseFloat(callToActionIconSize) || 24;
|
|
168
168
|
const iconSize = Math.max(containerSize - paddingTotal, 8); // Ensure minimum size of 8px
|
|
169
|
-
const iconSvg = renderLucideIcon(launcher.callToActionIconName, iconSize, "currentColor",
|
|
169
|
+
const iconSvg = renderLucideIcon(launcher.callToActionIconName, iconSize, "currentColor", 1.5);
|
|
170
170
|
if (iconSvg) {
|
|
171
171
|
callToActionIconEl.appendChild(iconSvg);
|
|
172
172
|
} else {
|
|
@@ -185,9 +185,9 @@ export const createLauncherButton = (
|
|
|
185
185
|
: positionMap["bottom-right"];
|
|
186
186
|
|
|
187
187
|
const floatingBase =
|
|
188
|
-
"persona-fixed persona-flex persona-items-center persona-gap-3 persona-rounded-launcher persona-bg-persona-
|
|
188
|
+
"persona-fixed persona-flex persona-items-center persona-gap-3 persona-rounded-launcher persona-bg-persona-launcher persona-py-2.5 persona-pl-3 persona-pr-3 persona-transition hover:persona-translate-y-[-2px] persona-cursor-pointer";
|
|
189
189
|
const dockedBase =
|
|
190
|
-
"persona-relative persona-mt-4 persona-mb-4 persona-mx-auto persona-flex persona-items-center persona-justify-center persona-rounded-launcher persona-bg-persona-
|
|
190
|
+
"persona-relative persona-mt-4 persona-mb-4 persona-mx-auto persona-flex persona-items-center persona-justify-center persona-rounded-launcher persona-bg-persona-launcher persona-transition hover:persona-translate-y-[-2px] persona-cursor-pointer";
|
|
191
191
|
|
|
192
192
|
button.className = dockedMode ? dockedBase : `${floatingBase} ${positionClass}`;
|
|
193
193
|
|
|
@@ -196,7 +196,7 @@ export const createLauncherButton = (
|
|
|
196
196
|
}
|
|
197
197
|
|
|
198
198
|
// Apply launcher border and shadow from config (with defaults matching previous Tailwind classes)
|
|
199
|
-
const defaultBorder = "1px solid var(--persona-border, #e5e7eb)";
|
|
199
|
+
const defaultBorder = "1px solid var(--persona-launcher-border, var(--persona-border, #e5e7eb))";
|
|
200
200
|
const defaultShadow = "var(--persona-launcher-shadow, 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1))";
|
|
201
201
|
|
|
202
202
|
button.style.border = launcher.border ?? defaultBorder;
|
package/src/components/panel.ts
CHANGED
|
@@ -21,6 +21,16 @@ import {
|
|
|
21
21
|
import { isWelcomeVisible, resolveWelcomeConfig } from "../welcome";
|
|
22
22
|
import { resolveContentMaxWidth } from "../utils/content-width";
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Composer-bar chrome sizes. Not header controls: these float over the
|
|
26
|
+
* composer, so they opt out of the header control-size token with explicit
|
|
27
|
+
* per-control overrides. `CLOSE_ICON_SIZE` is the X's nominal size before the
|
|
28
|
+
* factory's sparse-viewBox compensation (10 x 1.4 = the rendered 14px).
|
|
29
|
+
*/
|
|
30
|
+
export const COMPOSER_BAR_CONTROL_SIZE = "16px";
|
|
31
|
+
export const COMPOSER_BAR_CLOSE_ICON_SIZE = "10px";
|
|
32
|
+
export const COMPOSER_BAR_CLEAR_CHAT_ICON_SIZE = "14px";
|
|
33
|
+
|
|
24
34
|
export interface PanelWrapper {
|
|
25
35
|
wrapper: HTMLElement;
|
|
26
36
|
panel: HTMLElement;
|
|
@@ -257,16 +267,16 @@ const buildComposerBarPanel = (
|
|
|
257
267
|
// Minimal header: just an absolutely-positioned close button.
|
|
258
268
|
// The wrapper uses inline styles (top/right/z-index values) because the
|
|
259
269
|
// widget's hand-authored CSS doesn't ship every Tailwind utility.
|
|
260
|
-
// Composer-bar's
|
|
261
|
-
//
|
|
262
|
-
//
|
|
270
|
+
// Composer-bar's close is a 16px box, far below the header control token,
|
|
271
|
+
// to match the minimal aesthetic; the user can still override via
|
|
272
|
+
// `launcher.closeButtonSize`.
|
|
263
273
|
const { button: closeButton, wrapper: closeButtonWrapper } = createCloseButton(
|
|
264
274
|
config,
|
|
265
275
|
{
|
|
266
276
|
showClose,
|
|
267
277
|
wrapperClassName: "persona-composer-bar-close",
|
|
268
|
-
buttonSize:
|
|
269
|
-
iconSize:
|
|
278
|
+
buttonSize: COMPOSER_BAR_CONTROL_SIZE,
|
|
279
|
+
iconSize: COMPOSER_BAR_CLOSE_ICON_SIZE,
|
|
270
280
|
}
|
|
271
281
|
);
|
|
272
282
|
closeButtonWrapper.style.position = "absolute";
|
|
@@ -288,8 +298,8 @@ const buildComposerBarPanel = (
|
|
|
288
298
|
if (clearChatEnabled) {
|
|
289
299
|
const parts = createClearChatButton(config, {
|
|
290
300
|
wrapperClassName: "persona-composer-bar-clear-chat",
|
|
291
|
-
buttonSize:
|
|
292
|
-
iconSize:
|
|
301
|
+
buttonSize: COMPOSER_BAR_CONTROL_SIZE,
|
|
302
|
+
iconSize: COMPOSER_BAR_CLEAR_CHAT_ICON_SIZE,
|
|
293
303
|
});
|
|
294
304
|
clearChatButton = parts.button;
|
|
295
305
|
clearChatButtonWrapper = parts.wrapper;
|
|
@@ -285,12 +285,79 @@ export const animateWelcomeOut = (
|
|
|
285
285
|
onFinished(null);
|
|
286
286
|
return null;
|
|
287
287
|
}
|
|
288
|
+
// Collapse layout in the SAME motion as the fade. The default welcome is
|
|
289
|
+
// in-flow above the transcript (unlike plugin welcomes, which overlay it),
|
|
290
|
+
// so an opacity-only exit would hold the hero's height for the whole fade
|
|
291
|
+
// and then release it in one frame at the hidden flip — the first message
|
|
292
|
+
// renders below the hero, then teleports to the top when the repin lands.
|
|
293
|
+
//
|
|
294
|
+
// Height alone is not enough: the hero centers itself with auto margins,
|
|
295
|
+
// and auto margins absorb whatever the height collapse frees, so the
|
|
296
|
+
// transcript below would still not move until the flip. The margins'
|
|
297
|
+
// USED values are measured from geometry (computed style reports the
|
|
298
|
+
// keyword, not pixels, for flex auto margins) and animated to zero in the
|
|
299
|
+
// same keyframes, with a negative end margin canceling the host's flex gap
|
|
300
|
+
// slot — the transcript slides up continuously and the hidden flip at the
|
|
301
|
+
// end changes nothing the eye can see.
|
|
302
|
+
const rect = host.getBoundingClientRect();
|
|
303
|
+
const parent = host.parentElement;
|
|
304
|
+
let gap = 0;
|
|
305
|
+
let usedMarginTop = 0;
|
|
306
|
+
let usedMarginBottom = 0;
|
|
307
|
+
if (parent) {
|
|
308
|
+
const parentStyle = getComputedStyle(parent);
|
|
309
|
+
gap = parseFloat(parentStyle.rowGap) || 0;
|
|
310
|
+
const prev = host.previousElementSibling;
|
|
311
|
+
const next = host.nextElementSibling;
|
|
312
|
+
const parentRect = parent.getBoundingClientRect();
|
|
313
|
+
const contentTop =
|
|
314
|
+
parentRect.top +
|
|
315
|
+
(parseFloat(parentStyle.borderTopWidth) || 0) +
|
|
316
|
+
(parseFloat(parentStyle.paddingTop) || 0) -
|
|
317
|
+
parent.scrollTop;
|
|
318
|
+
usedMarginTop = Math.max(
|
|
319
|
+
0,
|
|
320
|
+
prev
|
|
321
|
+
? rect.top - prev.getBoundingClientRect().bottom - gap
|
|
322
|
+
: rect.top - contentTop
|
|
323
|
+
);
|
|
324
|
+
usedMarginBottom = Math.max(
|
|
325
|
+
0,
|
|
326
|
+
next ? next.getBoundingClientRect().top - rect.bottom - gap : 0
|
|
327
|
+
);
|
|
328
|
+
}
|
|
329
|
+
const previousOverflow = host.style.overflow;
|
|
330
|
+
host.style.overflow = "hidden";
|
|
331
|
+
// Padding collapses with the height: under border-box sizing a height of
|
|
332
|
+
// zero clamps at the vertical padding, which would leave a padding-tall
|
|
333
|
+
// remnant releasing in one frame at the flip.
|
|
334
|
+
const hostStyle = getComputedStyle(host);
|
|
335
|
+
const paddingTop = parseFloat(hostStyle.paddingTop) || 0;
|
|
336
|
+
const paddingBottom = parseFloat(hostStyle.paddingBottom) || 0;
|
|
288
337
|
// `forwards`, not `backwards`: backwards only fills the delay phase, so the
|
|
289
338
|
// element snaps back to opacity 1 for a frame after the keyframes end.
|
|
290
339
|
const animation = host.animate(
|
|
291
340
|
[
|
|
292
|
-
{
|
|
293
|
-
|
|
341
|
+
{
|
|
342
|
+
opacity: "1",
|
|
343
|
+
transform: "none",
|
|
344
|
+
height: `${rect.height}px`,
|
|
345
|
+
minHeight: "0px",
|
|
346
|
+
paddingTop: `${paddingTop}px`,
|
|
347
|
+
paddingBottom: `${paddingBottom}px`,
|
|
348
|
+
marginTop: `${usedMarginTop}px`,
|
|
349
|
+
marginBottom: `${usedMarginBottom}px`,
|
|
350
|
+
},
|
|
351
|
+
{
|
|
352
|
+
opacity: "0",
|
|
353
|
+
transform: "translateY(-4px)",
|
|
354
|
+
height: "0px",
|
|
355
|
+
minHeight: "0px",
|
|
356
|
+
paddingTop: "0px",
|
|
357
|
+
paddingBottom: "0px",
|
|
358
|
+
marginTop: "0px",
|
|
359
|
+
marginBottom: `${-gap}px`,
|
|
360
|
+
},
|
|
294
361
|
],
|
|
295
362
|
{ duration: 180, easing: "ease-out", fill: "forwards" }
|
|
296
363
|
);
|
|
@@ -298,6 +365,9 @@ export const animateWelcomeOut = (
|
|
|
298
365
|
const settle = () => {
|
|
299
366
|
if (settled) return;
|
|
300
367
|
settled = true;
|
|
368
|
+
// Restored on BOTH settle paths (finish and a re-show's cancel), so a
|
|
369
|
+
// welcome brought back by clearChat keeps its natural overflow.
|
|
370
|
+
host.style.overflow = previousOverflow;
|
|
301
371
|
onFinished(animation);
|
|
302
372
|
};
|
|
303
373
|
animation.finished.then(settle).catch(settle);
|
package/src/defaults.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import type { AgentWidgetConfig, AgentWidgetLauncherConfig } from "./types";
|
|
2
2
|
import type { DeepPartial, PersonaTheme } from "./types/theme";
|
|
3
3
|
import { deepMerge } from "./utils/deep-merge";
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_TOOLTIP_DELAY_MS,
|
|
6
|
+
DEFAULT_TOOLTIP_SKIP_DELAY_MS,
|
|
7
|
+
} from "./utils/tooltip-timing";
|
|
4
8
|
|
|
5
9
|
/**
|
|
6
10
|
* Default width for the floating launcher panel (when not overridden).
|
|
@@ -31,9 +35,10 @@ export const DEFAULT_LAUNCHER_CONFIG: AgentWidgetLauncherConfig = {
|
|
|
31
35
|
callToActionIconHidden: false,
|
|
32
36
|
agentIconSize: "40px",
|
|
33
37
|
headerIconSize: "40px",
|
|
34
|
-
closeButtonSize
|
|
38
|
+
// closeButtonSize / clearChat.size omitted so theme.components.header.controlSize
|
|
39
|
+
// sizes the header controls; setting either here would pin them past the token.
|
|
35
40
|
// Zero out browser-default <button> padding so the icon gets the full
|
|
36
|
-
//
|
|
41
|
+
// content box, matching clearChat.paddingX/Y below. Without this,
|
|
37
42
|
// UA stylesheets add ~1-2px vertical and ~6px horizontal padding that
|
|
38
43
|
// eats into the border-box width and shrinks the rendered icon.
|
|
39
44
|
closeButtonPaddingX: "0px",
|
|
@@ -52,7 +57,6 @@ export const DEFAULT_LAUNCHER_CONFIG: AgentWidgetLauncherConfig = {
|
|
|
52
57
|
enabled: true,
|
|
53
58
|
placement: "inline",
|
|
54
59
|
iconName: "refresh-cw",
|
|
55
|
-
size: "32px",
|
|
56
60
|
showTooltip: true,
|
|
57
61
|
tooltipText: "Clear chat",
|
|
58
62
|
paddingX: "0px",
|
|
@@ -77,6 +81,10 @@ export const DEFAULT_WIDGET_CONFIG: Partial<AgentWidgetConfig> = {
|
|
|
77
81
|
theme: undefined,
|
|
78
82
|
darkTheme: undefined,
|
|
79
83
|
colorScheme: "light",
|
|
84
|
+
tooltip: {
|
|
85
|
+
delayMs: DEFAULT_TOOLTIP_DELAY_MS,
|
|
86
|
+
skipDelayMs: DEFAULT_TOOLTIP_SKIP_DELAY_MS,
|
|
87
|
+
},
|
|
80
88
|
launcher: DEFAULT_LAUNCHER_CONFIG,
|
|
81
89
|
copy: {
|
|
82
90
|
// No welcomeTitle / welcomeSubtitle here on purpose: `resolveWelcomeConfig`
|
|
@@ -168,6 +176,13 @@ export const DEFAULT_WIDGET_CONFIG: Partial<AgentWidgetConfig> = {
|
|
|
168
176
|
freeTextPlaceholder: "Type your answer…",
|
|
169
177
|
submitLabel: "Send",
|
|
170
178
|
},
|
|
179
|
+
history: {
|
|
180
|
+
enabled: false,
|
|
181
|
+
presentation: "panel",
|
|
182
|
+
showScopeStatus: true,
|
|
183
|
+
showDelete: true,
|
|
184
|
+
showDeleteAll: true,
|
|
185
|
+
},
|
|
171
186
|
},
|
|
172
187
|
// Placeholder copy that models the right shape: verb-first, user-voice,
|
|
173
188
|
// one line. Integrators should swap in domain prompts.
|
|
@@ -263,6 +278,10 @@ export function mergeWithDefaults(
|
|
|
263
278
|
...config.launcher?.clearChat,
|
|
264
279
|
},
|
|
265
280
|
},
|
|
281
|
+
tooltip: {
|
|
282
|
+
...DEFAULT_WIDGET_CONFIG.tooltip,
|
|
283
|
+
...config.tooltip,
|
|
284
|
+
},
|
|
266
285
|
copy: {
|
|
267
286
|
...DEFAULT_WIDGET_CONFIG.copy,
|
|
268
287
|
...config.copy,
|
|
@@ -290,6 +309,8 @@ export function mergeWithDefaults(
|
|
|
290
309
|
const csa = config.features?.streamAnimation;
|
|
291
310
|
const dau = DEFAULT_WIDGET_CONFIG.features?.askUserQuestion;
|
|
292
311
|
const cau = config.features?.askUserQuestion;
|
|
312
|
+
const dh = DEFAULT_WIDGET_CONFIG.features?.history;
|
|
313
|
+
const ch = config.features?.history;
|
|
293
314
|
const mergedArtifacts =
|
|
294
315
|
da === undefined && ca === undefined
|
|
295
316
|
? undefined
|
|
@@ -333,6 +354,17 @@ export function mergeWithDefaults(
|
|
|
333
354
|
...cau?.styles,
|
|
334
355
|
},
|
|
335
356
|
};
|
|
357
|
+
const mergedHistory =
|
|
358
|
+
dh === undefined && ch === undefined
|
|
359
|
+
? undefined
|
|
360
|
+
: {
|
|
361
|
+
...dh,
|
|
362
|
+
...ch,
|
|
363
|
+
copy: {
|
|
364
|
+
...dh?.copy,
|
|
365
|
+
...ch?.copy,
|
|
366
|
+
},
|
|
367
|
+
};
|
|
336
368
|
return {
|
|
337
369
|
...DEFAULT_WIDGET_CONFIG.features,
|
|
338
370
|
...config.features,
|
|
@@ -341,6 +373,7 @@ export function mergeWithDefaults(
|
|
|
341
373
|
...(mergedArtifacts !== undefined ? { artifacts: mergedArtifacts } : {}),
|
|
342
374
|
...(mergedStreamAnimation !== undefined ? { streamAnimation: mergedStreamAnimation } : {}),
|
|
343
375
|
...(mergedAskUserQuestion !== undefined ? { askUserQuestion: mergedAskUserQuestion } : {}),
|
|
376
|
+
...(mergedHistory !== undefined ? { history: mergedHistory } : {}),
|
|
344
377
|
};
|
|
345
378
|
})(),
|
|
346
379
|
suggestionChips: config.suggestionChips ?? DEFAULT_WIDGET_CONFIG.suggestionChips,
|
|
@@ -0,0 +1,69 @@
|
|
|
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 event-stream-view chunk.
|
|
7
|
+
*
|
|
8
|
+
* The observability panel must NOT land in ANY core bundle shipped to
|
|
9
|
+
* consumers — neither the CDN IIFE (`dist/index.global.js`) nor the ESM/CJS
|
|
10
|
+
* bundles (`dist/index.js` / `dist/index.cjs`). It ships as the sibling chunk
|
|
11
|
+
* `dist/event-stream-view.{js,cjs}`, loaded on demand: the IIFE via a sibling
|
|
12
|
+
* URL, ESM/CJS via the external `@runtypelabs/persona/event-stream-view`
|
|
13
|
+
* subpath. A stray static import would pull the view back in; this fails then.
|
|
14
|
+
*
|
|
15
|
+
* Skips when `dist/` hasn't been built (e.g. a test-only CI step).
|
|
16
|
+
*/
|
|
17
|
+
const dist = (f: string) => resolve(__dirname, "..", "dist", f);
|
|
18
|
+
|
|
19
|
+
// Chunk-only literals: the view's root and list class names are never emitted
|
|
20
|
+
// by core code; the sibling-URL stub in the core bundles is the bare filename
|
|
21
|
+
// `event-stream-view.js`, which does not contain these.
|
|
22
|
+
const RUNTIME_MARKERS = ["persona-event-stream-view", "persona-event-stream-list"];
|
|
23
|
+
|
|
24
|
+
const SUBPATH = "@runtypelabs/persona/event-stream-view";
|
|
25
|
+
|
|
26
|
+
describe("event-stream-view bundle split", () => {
|
|
27
|
+
const iifeBuilt =
|
|
28
|
+
existsSync(dist("index.global.js")) && existsSync(dist("event-stream-view.js"));
|
|
29
|
+
const esmBuilt = existsSync(dist("index.js")) && existsSync(dist("index.cjs"));
|
|
30
|
+
|
|
31
|
+
it.runIf(iifeBuilt)("keeps the event-stream view OUT of the core IIFE bundle", () => {
|
|
32
|
+
const core = readFileSync(dist("index.global.js"), "utf8");
|
|
33
|
+
for (const marker of RUNTIME_MARKERS) {
|
|
34
|
+
expect(core.includes(marker), `IIFE bundle unexpectedly contains "${marker}"`).toBe(
|
|
35
|
+
false
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
// The loader stub (sibling-URL reference) must remain so the chunk can load.
|
|
39
|
+
expect(core).toContain("event-stream-view.js");
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it.runIf(esmBuilt)("keeps the event-stream view OUT of the ESM/CJS bundles", () => {
|
|
43
|
+
for (const file of ["index.js", "index.cjs"]) {
|
|
44
|
+
const core = readFileSync(dist(file), "utf8");
|
|
45
|
+
for (const marker of RUNTIME_MARKERS) {
|
|
46
|
+
expect(core.includes(marker), `${file} unexpectedly contains "${marker}"`).toBe(
|
|
47
|
+
false
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
// Any reference must go through the external subpath, not an inlined
|
|
51
|
+
// relative import.
|
|
52
|
+
expect(
|
|
53
|
+
core.includes(SUBPATH),
|
|
54
|
+
`${file} must reach the view via the external subpath`
|
|
55
|
+
).toBe(true);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it.runIf(iifeBuilt)("ships the event-stream view in the sibling chunk", () => {
|
|
60
|
+
const chunk = readFileSync(dist("event-stream-view.js"), "utf8");
|
|
61
|
+
for (const marker of RUNTIME_MARKERS) {
|
|
62
|
+
expect(chunk.includes(marker), `chunk is missing "${marker}"`).toBe(true);
|
|
63
|
+
}
|
|
64
|
+
// A CJS twin exists for require()-based consumers.
|
|
65
|
+
expect(existsSync(dist("event-stream-view.cjs"))).toBe(true);
|
|
66
|
+
// Declarations ship with the chunk so the subpath is typed.
|
|
67
|
+
expect(existsSync(dist("event-stream-view.d.ts"))).toBe(true);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event-stream-view runtime entry — the module that assembles the lazily
|
|
3
|
+
* loaded event-stream observability panel. It is NEVER statically imported on
|
|
4
|
+
* the core path: the IIFE/CDN build marks
|
|
5
|
+
* `@runtypelabs/persona/event-stream-view` external (`tsup.global.config.ts`)
|
|
6
|
+
* and loads the standalone `event-stream-view.js` chunk from a sibling URL on
|
|
7
|
+
* first open of the panel. The core reaches it through
|
|
8
|
+
* `event-stream-view-loader.ts`.
|
|
9
|
+
*
|
|
10
|
+
* Mirrors `history-view-entry.ts`.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
export { createEventStreamView } from "./components/event-stream-view";
|
|
14
|
+
export type { EventStreamViewOptions } from "./components/event-stream-view";
|
|
15
|
+
|
|
16
|
+
import type { createEventStreamView as CreateEventStreamView } from "./components/event-stream-view";
|
|
17
|
+
|
|
18
|
+
/** Mounted-view handle: what `createEventStreamView` returns. */
|
|
19
|
+
export type EventStreamViewHandle = ReturnType<typeof CreateEventStreamView>;
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Loader indirection for the lazy event-stream-view chunk (core bundle, tiny).
|
|
3
|
+
*
|
|
4
|
+
* Mirrors `history-view-loader.ts`: the IIFE/CDN entry (`index-global.ts`)
|
|
5
|
+
* registers a loader that imports the standalone `event-stream-view.js` chunk
|
|
6
|
+
* from a sibling URL. ESM/CJS consumers fall back to importing the package's
|
|
7
|
+
* `./event-stream-view` subpath — which `build:client` marks external, so the
|
|
8
|
+
* view is NOT inlined into `dist/index.{js,cjs}` and stays out of the core
|
|
9
|
+
* bundle until the event-stream panel is first opened.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import { createChunkLoader } from "./utils/chunk-loader";
|
|
13
|
+
import type {
|
|
14
|
+
EventStreamViewOptions,
|
|
15
|
+
EventStreamViewHandle,
|
|
16
|
+
} from "./event-stream-view-entry";
|
|
17
|
+
|
|
18
|
+
export type EventStreamViewModule = {
|
|
19
|
+
createEventStreamView: (options: EventStreamViewOptions) => EventStreamViewHandle;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// IIFE/CDN: sibling-URL chunk via the registered loader.
|
|
23
|
+
// ESM/CJS fallback: the package's own `./event-stream-view` subpath (external,
|
|
24
|
+
// so the runtime chunk is code-split out of dist/index.{js,cjs} rather than
|
|
25
|
+
// inlined). Memoization + rejection-retry semantics live in `createChunkLoader`.
|
|
26
|
+
const { setLoader, load } = createChunkLoader<EventStreamViewModule>({
|
|
27
|
+
fallbackImport: () => import("@runtypelabs/persona/event-stream-view"),
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
export const setEventStreamViewLoader = setLoader;
|
|
31
|
+
export const loadEventStreamView = load;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Standalone event-stream-view chunk (`dist/event-stream-view.js`).
|
|
3
|
+
*
|
|
4
|
+
* The IIFE/CDN widget bundle marks `@runtypelabs/persona/event-stream-view`
|
|
5
|
+
* external so the observability panel is excluded from the core payload; this
|
|
6
|
+
* file is built as a self-contained sibling chunk
|
|
7
|
+
* (`tsup.event-stream-view.config.ts`) and loaded on demand by the loader
|
|
8
|
+
* registered in `index-global.ts`. ESM/CJS consumers reach it via this same
|
|
9
|
+
* (external) subpath.
|
|
10
|
+
*
|
|
11
|
+
* Lazy-runtime transport entry only: exports are limited to the mount contract
|
|
12
|
+
* the loader needs.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
export { createEventStreamView } from "./event-stream-view-entry";
|
|
16
|
+
export type {
|
|
17
|
+
EventStreamViewOptions,
|
|
18
|
+
EventStreamViewHandle,
|
|
19
|
+
} from "./event-stream-view-entry";
|