@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
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import type { AgentWidgetConfig, AgentWidgetMessage } from "../types";
|
|
2
|
+
import { createNode } from "../utils/dom";
|
|
3
|
+
import { formatUnknownValue } from "../utils/formatting";
|
|
4
|
+
import { renderLucideIcon } from "../utils/icons";
|
|
5
|
+
|
|
6
|
+
const isEmptyRequest = (value: unknown): boolean => {
|
|
7
|
+
if (value == null) return true;
|
|
8
|
+
if (typeof value === "string") {
|
|
9
|
+
if (!value.trim()) return true;
|
|
10
|
+
try { return isEmptyRequest(JSON.parse(value)); } catch { return false; }
|
|
11
|
+
}
|
|
12
|
+
return typeof value === "object" && Object.keys(value).length === 0;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const isStructured = (value: unknown): boolean => {
|
|
16
|
+
if (value !== null && typeof value === "object") return true;
|
|
17
|
+
if (typeof value !== "string") return false;
|
|
18
|
+
try {
|
|
19
|
+
const parsed: unknown = JSON.parse(value);
|
|
20
|
+
return parsed !== null && typeof parsed === "object";
|
|
21
|
+
} catch { return false; }
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
/** Plain text only: tool output is untrusted and must never become HTML. */
|
|
25
|
+
export function appendToolDetails(content: HTMLElement, message: AgentWidgetMessage, config: AgentWidgetConfig): void {
|
|
26
|
+
const tool = message.toolCall;
|
|
27
|
+
if (!tool) return;
|
|
28
|
+
const styles = config.toolCall ?? {};
|
|
29
|
+
const section = (label: "Request" | "Response", value: unknown) => {
|
|
30
|
+
const block = createNode("section", {
|
|
31
|
+
className: "persona-tool-detail",
|
|
32
|
+
attrs: { "data-persona-tool-detail": label.toLowerCase() },
|
|
33
|
+
style: {
|
|
34
|
+
backgroundColor: styles.codeBlockBackgroundColor,
|
|
35
|
+
borderColor: styles.codeBlockBorderColor,
|
|
36
|
+
borderWidth: styles.codeBlockBorderColor ? "1px" : undefined,
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
const title = createNode("div", {
|
|
40
|
+
className: "persona-tool-detail-label",
|
|
41
|
+
text: label,
|
|
42
|
+
style: { color: styles.labelTextColor },
|
|
43
|
+
});
|
|
44
|
+
const text = formatUnknownValue(value);
|
|
45
|
+
const pre = createNode("pre", {
|
|
46
|
+
className: `persona-tool-detail-value${isStructured(value) ? " persona-tool-detail-code" : ""}`,
|
|
47
|
+
text,
|
|
48
|
+
attrs: text.length > 420 || text.split("\n").length > 7
|
|
49
|
+
? { tabindex: "0", "aria-label": `${label} content` } : {},
|
|
50
|
+
style: { color: styles.codeBlockTextColor ?? styles.contentTextColor },
|
|
51
|
+
});
|
|
52
|
+
const copy = createNode("button", {
|
|
53
|
+
className: "persona-tool-detail-copy",
|
|
54
|
+
attrs: { type: "button", "data-persona-copy-tool-detail": "", "aria-label": `Copy ${label.toLowerCase()}`, title: `Copy ${label.toLowerCase()}` },
|
|
55
|
+
});
|
|
56
|
+
const icon = renderLucideIcon("copy", 14, "currentColor", 1.7);
|
|
57
|
+
if (icon) copy.appendChild(icon);
|
|
58
|
+
block.append(title, pre, copy);
|
|
59
|
+
content.appendChild(block);
|
|
60
|
+
};
|
|
61
|
+
if (!isEmptyRequest(tool.args)) section("Request", tool.args);
|
|
62
|
+
const response = tool.status === "complete" && tool.result !== undefined
|
|
63
|
+
? tool.result : tool.chunks?.join("");
|
|
64
|
+
if (response !== undefined && (typeof response !== "string" || response.trim())) section("Response", response);
|
|
65
|
+
if (tool.success === false) {
|
|
66
|
+
content.appendChild(createNode("div", {
|
|
67
|
+
className: "persona-text-sm persona-whitespace-pre-wrap",
|
|
68
|
+
text: tool.error || "Tool failed",
|
|
69
|
+
attrs: { "data-persona-tool-error": "", role: "status" },
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const copyResets = new WeakMap<HTMLButtonElement, ReturnType<typeof setTimeout>>();
|
|
75
|
+
|
|
76
|
+
/** Delegated by the transcript so copying works after streaming DOM morphs. */
|
|
77
|
+
export async function copyToolDetail(button: HTMLButtonElement): Promise<void> {
|
|
78
|
+
const block = button.closest("[data-persona-tool-detail]");
|
|
79
|
+
const value = block?.querySelector("pre");
|
|
80
|
+
if (!value) return;
|
|
81
|
+
const label = `Copy ${block?.getAttribute("data-persona-tool-detail")}`;
|
|
82
|
+
clearTimeout(copyResets.get(button));
|
|
83
|
+
try {
|
|
84
|
+
await navigator.clipboard.writeText(value.textContent ?? "");
|
|
85
|
+
button.dataset.copied = "true";
|
|
86
|
+
button.setAttribute("aria-label", "Copied");
|
|
87
|
+
button.title = "Copied";
|
|
88
|
+
const icon = renderLucideIcon("check", 14, "currentColor", 2);
|
|
89
|
+
if (icon) button.replaceChildren(icon);
|
|
90
|
+
} catch {
|
|
91
|
+
button.setAttribute("aria-label", "Copy failed — try again");
|
|
92
|
+
button.title = "Copy failed — try again";
|
|
93
|
+
}
|
|
94
|
+
copyResets.set(button, setTimeout(() => {
|
|
95
|
+
delete button.dataset.copied;
|
|
96
|
+
button.setAttribute("aria-label", label);
|
|
97
|
+
button.title = label;
|
|
98
|
+
const icon = renderLucideIcon("copy", 14, "currentColor", 1.7);
|
|
99
|
+
if (icon) button.replaceChildren(icon);
|
|
100
|
+
copyResets.delete(button);
|
|
101
|
+
}, 1800));
|
|
102
|
+
}
|
|
@@ -151,6 +151,14 @@ describe("composer lift animation", () => {
|
|
|
151
151
|
footer.remove();
|
|
152
152
|
});
|
|
153
153
|
|
|
154
|
+
it("accepts the centered layout's 320ms transition", () => {
|
|
155
|
+
const footer = makeFooter();
|
|
156
|
+
const animate = stubAnimate(footer);
|
|
157
|
+
animateComposerLiftChange(footer, 240, "drop", 320);
|
|
158
|
+
expect(animate.mock.calls[0]![1]).toMatchObject({ duration: 320 });
|
|
159
|
+
footer.remove();
|
|
160
|
+
});
|
|
161
|
+
|
|
154
162
|
it("reverses the travel for the rise", () => {
|
|
155
163
|
const footer = makeFooter();
|
|
156
164
|
const animate = stubAnimate(footer);
|
|
@@ -177,6 +177,7 @@ export const applyWelcomeConfig = (
|
|
|
177
177
|
applyWelcomeIcon(elements.iconHolder, resolved.icon);
|
|
178
178
|
elements.host.setAttribute("data-persona-welcome-variant", resolved.variant);
|
|
179
179
|
elements.host.setAttribute("data-persona-welcome-dismiss", resolved.dismiss);
|
|
180
|
+
elements.host.setAttribute("data-persona-welcome-layout", resolved.layout ?? "top");
|
|
180
181
|
elements.host.setAttribute(
|
|
181
182
|
"data-persona-welcome-anchor",
|
|
182
183
|
resolved.anchor ?? "bottom"
|
|
@@ -422,7 +423,8 @@ export const animateWelcomeOut = (
|
|
|
422
423
|
export const animateComposerLiftChange = (
|
|
423
424
|
footer: HTMLElement,
|
|
424
425
|
distance: number,
|
|
425
|
-
direction: "drop" | "rise"
|
|
426
|
+
direction: "drop" | "rise",
|
|
427
|
+
duration = 260
|
|
426
428
|
): Animation | null => {
|
|
427
429
|
if (
|
|
428
430
|
distance <= 1 ||
|
|
@@ -437,6 +439,6 @@ export const animateComposerLiftChange = (
|
|
|
437
439
|
// risk pinning a stale transform across a later re-show.
|
|
438
440
|
return footer.animate(
|
|
439
441
|
[{ transform: `translateY(${offset}px)` }, { transform: "none" }],
|
|
440
|
-
{ duration
|
|
442
|
+
{ duration, easing: "cubic-bezier(0.2, 0, 0, 1)", fill: "none" }
|
|
441
443
|
);
|
|
442
444
|
};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Locked 4.22.0 defaults contract. The fixture was captured before the v5
|
|
5
|
+
* defaults work began and must not be regenerated as part of later changes.
|
|
6
|
+
*
|
|
7
|
+
* JSDOM does not perform browser layout or resolve inherited custom
|
|
8
|
+
* properties. This test therefore records the DOM-facing computed declarations
|
|
9
|
+
* it can reliably expose, alongside the resolved config and emitted token map.
|
|
10
|
+
* The Playwright visual harness covers physical layout separately.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { readFileSync } from "node:fs";
|
|
14
|
+
import { resolve } from "node:path";
|
|
15
|
+
import { afterEach, describe, expect, it } from "vitest";
|
|
16
|
+
|
|
17
|
+
import { mergeWithDefaults } from "./defaults";
|
|
18
|
+
import { createAgentExperience } from "./ui";
|
|
19
|
+
import { createTheme, themeToCssVariables } from "./utils/tokens";
|
|
20
|
+
|
|
21
|
+
const fixturePath = resolve(process.cwd(), "src/__fixtures__/defaults-parity.v4.json");
|
|
22
|
+
const stylesheetPath = resolve(process.cwd(), "src/styles/widget.css");
|
|
23
|
+
|
|
24
|
+
const styles = (element: Element | null) => {
|
|
25
|
+
if (!element) throw new Error("Expected baseline element to be rendered");
|
|
26
|
+
const computed = getComputedStyle(element);
|
|
27
|
+
return Object.fromEntries(
|
|
28
|
+
["display", "position", "background-color", "color", "border-radius", "padding", "margin", "font-size", "line-height", "box-shadow", "width", "height"].map(
|
|
29
|
+
(property) => [property, computed.getPropertyValue(property)]
|
|
30
|
+
)
|
|
31
|
+
);
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const snapshot = (v5Defaults = false) => {
|
|
35
|
+
const stylesheet = document.createElement("style");
|
|
36
|
+
stylesheet.textContent = readFileSync(stylesheetPath, "utf8");
|
|
37
|
+
document.head.appendChild(stylesheet);
|
|
38
|
+
const mount = document.createElement("div");
|
|
39
|
+
document.body.appendChild(mount);
|
|
40
|
+
const controller = createAgentExperience(mount, {
|
|
41
|
+
apiUrl: "https://api.example.com/chat",
|
|
42
|
+
launcher: { autoExpand: true },
|
|
43
|
+
persistState: false,
|
|
44
|
+
future: { v5Defaults },
|
|
45
|
+
});
|
|
46
|
+
controller.injectUserMessage({ content: "Baseline user message" });
|
|
47
|
+
controller.injectAssistantMessage({ content: "Baseline assistant message" });
|
|
48
|
+
controller.injectTestMessage({
|
|
49
|
+
type: "message",
|
|
50
|
+
message: {
|
|
51
|
+
id: "baseline-tool",
|
|
52
|
+
role: "assistant",
|
|
53
|
+
content: "",
|
|
54
|
+
createdAt: "2026-01-01T00:00:00.000Z",
|
|
55
|
+
sequence: 3,
|
|
56
|
+
streaming: false,
|
|
57
|
+
variant: "tool",
|
|
58
|
+
toolCall: { id: "baseline-tool", name: "baseline_tool", status: "complete", chunks: ["done"] },
|
|
59
|
+
},
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
const result = {
|
|
63
|
+
themeCssVariables: themeToCssVariables(createTheme(undefined, { future: { v5Defaults } })),
|
|
64
|
+
mergedDefaultConfig: mergeWithDefaults(),
|
|
65
|
+
computedStyles: {
|
|
66
|
+
header: styles(mount.querySelector(".persona-widget-header")),
|
|
67
|
+
userBubble: styles(mount.querySelector(".persona-message-row-user .persona-message-bubble")),
|
|
68
|
+
assistantBubble: styles(mount.querySelector(".persona-message-row-assistant .persona-message-bubble")),
|
|
69
|
+
composer: styles(mount.querySelector("[data-persona-composer-form]")),
|
|
70
|
+
launcher: styles(mount.querySelector(".persona-launcher-surface button")),
|
|
71
|
+
toolBubble: styles(mount.querySelector(".persona-tool-bubble")),
|
|
72
|
+
},
|
|
73
|
+
};
|
|
74
|
+
controller.destroy();
|
|
75
|
+
mount.remove();
|
|
76
|
+
stylesheet.remove();
|
|
77
|
+
return result;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
afterEach(() => document.body.replaceChildren());
|
|
81
|
+
|
|
82
|
+
type DefaultsSnapshot = ReturnType<typeof snapshot>;
|
|
83
|
+
|
|
84
|
+
// JSDOM leaves some computed declarations as var(...) strings. Resolve a
|
|
85
|
+
// whole-value reference against that snapshot's own emitted tokens before
|
|
86
|
+
// comparing, so deleting an unreachable fallback is not a visual regression.
|
|
87
|
+
// Unknown references stay verbatim; changes to actual tokens still fail the
|
|
88
|
+
// independent, complete themeCssVariables comparison. The fixture stays intact.
|
|
89
|
+
const resolveComputedTokens = (value: DefaultsSnapshot): DefaultsSnapshot => ({
|
|
90
|
+
...value,
|
|
91
|
+
computedStyles: Object.fromEntries(
|
|
92
|
+
Object.entries(value.computedStyles).map(([name, declarations]) => [name,
|
|
93
|
+
Object.fromEntries(Object.entries(declarations).map(([property, declaration]) => {
|
|
94
|
+
const seen = new Set<string>();
|
|
95
|
+
let resolved = declaration;
|
|
96
|
+
while (!seen.has(resolved)) {
|
|
97
|
+
seen.add(resolved);
|
|
98
|
+
const reference = /^var\((--[\w-]+)(?:,[\s\S]*)?\)$/.exec(resolved);
|
|
99
|
+
const token = reference && value.themeCssVariables[reference[1]];
|
|
100
|
+
if (typeof token !== "string") break;
|
|
101
|
+
resolved = token;
|
|
102
|
+
}
|
|
103
|
+
return [property, resolved.replace(/([\d.]+)rem\b/g, (_, amount) => `${Number(amount) * 16}px`)];
|
|
104
|
+
})),
|
|
105
|
+
])
|
|
106
|
+
) as DefaultsSnapshot["computedStyles"],
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
describe("4.22.0 defaults parity (flag off)", () => {
|
|
110
|
+
it("matches the committed baseline", () => {
|
|
111
|
+
// JSON is deliberately the fixture format: it makes omitted/undefined
|
|
112
|
+
// config and token values deterministic across Node versions.
|
|
113
|
+
const actual = JSON.parse(JSON.stringify(snapshot(false)));
|
|
114
|
+
const baseline = JSON.parse(readFileSync(fixturePath, "utf8"));
|
|
115
|
+
// Phase 3 activates formerly unused tokens. Lock their old and new values
|
|
116
|
+
// explicitly instead of rewriting the historical fixture or ignoring keys.
|
|
117
|
+
const activatedTokens: Record<string, [string, string]> = {
|
|
118
|
+
"--persona-components-header-padding": ["1rem", "20px 24px"],
|
|
119
|
+
"--persona-components-panel-height": ["600px", "min(640px, max(200px, calc(100vh - 64px)))"],
|
|
120
|
+
"--persona-components-panel-maxHeight": ["calc(100vh - 80px)", "none"],
|
|
121
|
+
"--persona-components-panel-maxWidth": ["440px", "none"],
|
|
122
|
+
};
|
|
123
|
+
const addedTokens: Record<string, string> = {
|
|
124
|
+
"--persona-components-header-minimalPadding": "16px 24px",
|
|
125
|
+
"--persona-components-message-gap": "12px",
|
|
126
|
+
"--persona-cw-container": "#f9fafb",
|
|
127
|
+
"--persona-cw-surface": "#f9fafb",
|
|
128
|
+
"--persona-cw-border": "#e5e7eb",
|
|
129
|
+
};
|
|
130
|
+
for (const [mode, border, shadow, radius] of [
|
|
131
|
+
["floating", "1px solid var(--persona-border)", baseline.themeCssVariables["--persona-components-panel-shadow"], "0.75rem"],
|
|
132
|
+
["inline", "1px solid var(--persona-border)", "none", "0.75rem"],
|
|
133
|
+
["docked", "none", "none", "0.75rem"],
|
|
134
|
+
["sidebar", "none", undefined, "0"],
|
|
135
|
+
["mobile", "none", "none", "0"],
|
|
136
|
+
]) {
|
|
137
|
+
addedTokens[`--persona-components-panel-modes-${mode}-border`] = border!;
|
|
138
|
+
addedTokens[`--persona-components-panel-modes-${mode}-borderRadius`] = radius!;
|
|
139
|
+
if (shadow !== undefined) addedTokens[`--persona-components-panel-modes-${mode}-shadow`] = shadow;
|
|
140
|
+
}
|
|
141
|
+
// Resolve computed declarations before adapting the token map for comparison.
|
|
142
|
+
// The baseline stylesheet uses rem; this JSDOM harness assumes a 16px root.
|
|
143
|
+
const resolvedActual = resolveComputedTokens(actual);
|
|
144
|
+
const resolvedBaseline = resolveComputedTokens(baseline);
|
|
145
|
+
resolvedActual.themeCssVariables = { ...actual.themeCssVariables };
|
|
146
|
+
for (const [key, [oldValue, newValue]] of Object.entries(activatedTokens)) {
|
|
147
|
+
expect(baseline.themeCssVariables[key], key).toBe(oldValue);
|
|
148
|
+
expect(actual.themeCssVariables[key], key).toBe(newValue);
|
|
149
|
+
resolvedActual.themeCssVariables[key] = oldValue;
|
|
150
|
+
}
|
|
151
|
+
for (const [key, value] of Object.entries(addedTokens)) {
|
|
152
|
+
expect(baseline.themeCssVariables).not.toHaveProperty(key);
|
|
153
|
+
expect(actual.themeCssVariables[key], key).toBe(value);
|
|
154
|
+
delete resolvedActual.themeCssVariables[key];
|
|
155
|
+
}
|
|
156
|
+
expect(resolvedActual).toEqual(resolvedBaseline);
|
|
157
|
+
});
|
|
158
|
+
});
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import {
|
|
3
|
+
DEFAULT_WIDGET_CONFIG,
|
|
4
|
+
mergeWithDefaults,
|
|
5
|
+
resolveDefaults,
|
|
6
|
+
resolveDefaultsVersion,
|
|
7
|
+
} from "./defaults";
|
|
8
|
+
|
|
9
|
+
describe.each([false, true])("defaults resolver (v5Defaults: %s)", (v5Defaults) => {
|
|
10
|
+
const future = { v5Defaults };
|
|
11
|
+
|
|
12
|
+
it("selects the requested version and its documented defaults", () => {
|
|
13
|
+
expect(resolveDefaultsVersion({ future })).toBe(v5Defaults ? "v5" : "v4");
|
|
14
|
+
const defaults = resolveDefaults({ future });
|
|
15
|
+
if (!v5Defaults) expect(defaults).toEqual(DEFAULT_WIDGET_CONFIG);
|
|
16
|
+
expect(defaults.layout?.header?.showSubtitle).toBe(!v5Defaults);
|
|
17
|
+
expect(defaults.composer?.layout).toBe(v5Defaults ? "single-row" : undefined);
|
|
18
|
+
expect(defaults.launcher?.variant).toBe(v5Defaults ? "circle" : undefined);
|
|
19
|
+
expect(defaults.composer?.placement).toBe(v5Defaults ? "overlay" : undefined);
|
|
20
|
+
expect(defaults.launcher?.width).toBe(v5Defaults
|
|
21
|
+
? "min(400px, calc(100vw - 24px))"
|
|
22
|
+
: "min(440px, calc(100vw - 24px))");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("preserves explicit composer and header options above the version defaults", () => {
|
|
26
|
+
const result = mergeWithDefaults({
|
|
27
|
+
future, composer: { layout: "stacked", placement: "block" },
|
|
28
|
+
layout: { header: { showSubtitle: true } },
|
|
29
|
+
});
|
|
30
|
+
expect(result.composer?.layout).toBe("stacked");
|
|
31
|
+
expect(result.composer?.placement).toBe("block");
|
|
32
|
+
expect(result.layout?.header?.showSubtitle).toBe(true);
|
|
33
|
+
const partial = mergeWithDefaults({ future, composer: { placement: "overlay" } });
|
|
34
|
+
expect(partial.composer?.layout).toBe(v5Defaults ? "single-row" : undefined);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("merges partial role sizing above the selected default width", () => {
|
|
38
|
+
const result = mergeWithDefaults({ future, layout: { messages: { assistant: { maxWidth: "65ch" } } } });
|
|
39
|
+
expect(result.layout?.messages?.assistant?.maxWidth).toBe("65ch");
|
|
40
|
+
expect(result.layout?.messages?.assistant?.width).toBe(v5Defaults ? "full" : undefined);
|
|
41
|
+
expect(result.layout?.messages?.user).toBeUndefined();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("merges explicit options and nested theme overrides above the selected defaults", () => {
|
|
45
|
+
const config = {
|
|
46
|
+
future,
|
|
47
|
+
launcher: { headerIconSize: "31px", title: "Custom title" },
|
|
48
|
+
features: { reasoningDisplay: { expandable: false } },
|
|
49
|
+
theme: { components: { header: { padding: "7px", title: { fontSize: "19px" } } } },
|
|
50
|
+
};
|
|
51
|
+
const result = mergeWithDefaults(config);
|
|
52
|
+
expect(result.future).toEqual(future);
|
|
53
|
+
expect(result.launcher?.headerIconSize).toBe("31px");
|
|
54
|
+
expect(result.launcher?.title).toBe("Custom title");
|
|
55
|
+
expect(result.features?.reasoningDisplay).toEqual({
|
|
56
|
+
...DEFAULT_WIDGET_CONFIG.features?.reasoningDisplay,
|
|
57
|
+
...(v5Defaults ? { variant: "row", iconVisibility: "active", autoExpand: false, loadingAnimation: "shimmer" } : {}),
|
|
58
|
+
expandable: false,
|
|
59
|
+
});
|
|
60
|
+
expect(result.theme?.components?.header).toEqual(config.theme.components.header);
|
|
61
|
+
expect(resolveDefaults({ future }).launcher?.headerIconSize).toBe(v5Defaults ? "20px" : "40px");
|
|
62
|
+
});
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it("keeps the legacy defaults when the opt-in is missing", () => {
|
|
66
|
+
expect(resolveDefaultsVersion()).toBe("v4");
|
|
67
|
+
expect(resolveDefaultsVersion({ future: {} })).toBe("v4");
|
|
68
|
+
expect(JSON.stringify(mergeWithDefaults())).toBe(JSON.stringify(DEFAULT_WIDGET_CONFIG));
|
|
69
|
+
});
|
package/src/defaults.ts
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
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 { inheritDefaultProvenance } from "./utils/defaults-provenance";
|
|
5
|
+
import { inheritPanelAliasProvenance } from "./utils/panel-config";
|
|
4
6
|
import {
|
|
5
7
|
DEFAULT_TOOLTIP_DELAY_MS,
|
|
6
8
|
DEFAULT_TOOLTIP_SKIP_DELAY_MS,
|
|
@@ -16,12 +18,33 @@ export const DEFAULT_FLOATING_LAUNCHER_WIDTH = "min(440px, calc(100vw - 24px))";
|
|
|
16
18
|
/** Max width cap paired with {@link DEFAULT_FLOATING_LAUNCHER_WIDTH} for theme defaults. */
|
|
17
19
|
export const DEFAULT_FLOATING_LAUNCHER_MAX_WIDTH = "440px";
|
|
18
20
|
|
|
21
|
+
/** Canonical header avatar box size, shared by the initial render and updates. */
|
|
22
|
+
export const DEFAULT_HEADER_ICON_SIZE = "40px";
|
|
23
|
+
|
|
19
24
|
/**
|
|
20
25
|
* Canonical composer placeholder. The low-level builders fall back to this so
|
|
21
26
|
* they cannot drift from `DEFAULT_WIDGET_CONFIG.copy.inputPlaceholder`.
|
|
22
27
|
*/
|
|
23
28
|
export const DEFAULT_INPUT_PLACEHOLDER = "How can I help...";
|
|
24
29
|
|
|
30
|
+
/** Shared by config resolution and standalone transcript component builders. */
|
|
31
|
+
export const DEFAULT_TOOL_CALL_DISPLAY = {
|
|
32
|
+
collapsedMode: "tool-call",
|
|
33
|
+
activePreview: false,
|
|
34
|
+
grouped: false,
|
|
35
|
+
groupedMode: "stack",
|
|
36
|
+
previewMaxLines: 3,
|
|
37
|
+
expandable: true,
|
|
38
|
+
loadingAnimation: "none",
|
|
39
|
+
} satisfies NonNullable<NonNullable<AgentWidgetConfig["features"]>["toolCallDisplay"]>;
|
|
40
|
+
|
|
41
|
+
export const DEFAULT_REASONING_DISPLAY = {
|
|
42
|
+
activePreview: false,
|
|
43
|
+
previewMaxLines: 3,
|
|
44
|
+
expandable: true,
|
|
45
|
+
loadingAnimation: "none",
|
|
46
|
+
} satisfies NonNullable<NonNullable<AgentWidgetConfig["features"]>["reasoningDisplay"]>;
|
|
47
|
+
|
|
25
48
|
export const DEFAULT_LAUNCHER_CONFIG: AgentWidgetLauncherConfig = {
|
|
26
49
|
enabled: true,
|
|
27
50
|
mountMode: "floating",
|
|
@@ -40,7 +63,7 @@ export const DEFAULT_LAUNCHER_CONFIG: AgentWidgetLauncherConfig = {
|
|
|
40
63
|
autoExpand: false,
|
|
41
64
|
callToActionIconHidden: false,
|
|
42
65
|
agentIconSize: "40px",
|
|
43
|
-
headerIconSize:
|
|
66
|
+
headerIconSize: DEFAULT_HEADER_ICON_SIZE,
|
|
44
67
|
// closeButtonSize / clearChat.size omitted so theme.components.header.controlSize
|
|
45
68
|
// sizes the header controls; setting either here would pin them past the token.
|
|
46
69
|
// Zero out browser-default <button> padding so the icon gets the full
|
|
@@ -78,7 +101,7 @@ export const DEFAULT_LAUNCHER_CONFIG: AgentWidgetLauncherConfig = {
|
|
|
78
101
|
* Default widget configuration
|
|
79
102
|
* Single source of truth for all default values
|
|
80
103
|
*/
|
|
81
|
-
export const
|
|
104
|
+
export const DEFAULTS_BASE: Partial<AgentWidgetConfig> = {
|
|
82
105
|
apiUrl: "https://api.runtype.com/api/chat/dispatch",
|
|
83
106
|
// Client token mode defaults (optional, only used when clientToken is set)
|
|
84
107
|
clientToken: undefined,
|
|
@@ -155,21 +178,8 @@ export const DEFAULT_WIDGET_CONFIG: Partial<AgentWidgetConfig> = {
|
|
|
155
178
|
// default so the default UX keeps the affordance.)
|
|
156
179
|
showActivityWhilePinned: true,
|
|
157
180
|
},
|
|
158
|
-
toolCallDisplay:
|
|
159
|
-
|
|
160
|
-
activePreview: false,
|
|
161
|
-
grouped: false,
|
|
162
|
-
groupedMode: "stack",
|
|
163
|
-
previewMaxLines: 3,
|
|
164
|
-
expandable: true,
|
|
165
|
-
loadingAnimation: "none",
|
|
166
|
-
},
|
|
167
|
-
reasoningDisplay: {
|
|
168
|
-
activePreview: false,
|
|
169
|
-
previewMaxLines: 3,
|
|
170
|
-
expandable: true,
|
|
171
|
-
loadingAnimation: "none",
|
|
172
|
-
},
|
|
181
|
+
toolCallDisplay: DEFAULT_TOOL_CALL_DISPLAY,
|
|
182
|
+
reasoningDisplay: DEFAULT_REASONING_DISPLAY,
|
|
173
183
|
streamAnimation: {
|
|
174
184
|
type: "none",
|
|
175
185
|
placeholder: "none",
|
|
@@ -246,6 +256,49 @@ export const DEFAULT_WIDGET_CONFIG: Partial<AgentWidgetConfig> = {
|
|
|
246
256
|
debug: false,
|
|
247
257
|
};
|
|
248
258
|
|
|
259
|
+
/** Version-specific defaults, selected by the staged V5 opt-in. */
|
|
260
|
+
export const DEFAULTS_V4: Partial<AgentWidgetConfig> = {};
|
|
261
|
+
export const DEFAULT_LAUNCHER_V5: Partial<AgentWidgetLauncherConfig> = {
|
|
262
|
+
variant: "circle",
|
|
263
|
+
width: "min(400px, calc(100vw - 24px))",
|
|
264
|
+
headerIconSize: "20px",
|
|
265
|
+
};
|
|
266
|
+
export const DEFAULTS_V5: Partial<AgentWidgetConfig> = {
|
|
267
|
+
launcher: DEFAULT_LAUNCHER_V5,
|
|
268
|
+
features: {
|
|
269
|
+
toolCallDisplay: { variant: "row", iconVisibility: "active", autoExpand: false, grouped: true, groupedMode: "collapsible", loadingAnimation: "shimmer" },
|
|
270
|
+
reasoningDisplay: { variant: "row", iconVisibility: "active", autoExpand: false, loadingAnimation: "shimmer" },
|
|
271
|
+
},
|
|
272
|
+
composer: { layout: "single-row", placement: "overlay" },
|
|
273
|
+
sendButton: { iconName: "arrow-up" },
|
|
274
|
+
statusIndicator: { mode: "transient" },
|
|
275
|
+
layout: {
|
|
276
|
+
contentMaxWidth: "768px",
|
|
277
|
+
topFade: true,
|
|
278
|
+
header: { showSubtitle: false },
|
|
279
|
+
messages: { assistant: { width: "full" } },
|
|
280
|
+
},
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
export type DefaultsVersion = "v4" | "v5";
|
|
284
|
+
|
|
285
|
+
/** Shared by config, token, theme, and editor resolution. Only true opts in. */
|
|
286
|
+
export function resolveDefaultsVersion(
|
|
287
|
+
config?: Pick<AgentWidgetConfig, "future">
|
|
288
|
+
): DefaultsVersion {
|
|
289
|
+
return config?.future?.v5Defaults === true ? "v5" : "v4";
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
export function resolveDefaults(
|
|
293
|
+
config?: Pick<AgentWidgetConfig, "future">
|
|
294
|
+
): Partial<AgentWidgetConfig> {
|
|
295
|
+
const overlay = resolveDefaultsVersion(config) === "v5" ? DEFAULTS_V5 : DEFAULTS_V4;
|
|
296
|
+
return deepMerge(DEFAULTS_BASE, overlay) as Partial<AgentWidgetConfig>;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** Backward-compatible export of the legacy widget defaults. */
|
|
300
|
+
export const DEFAULT_WIDGET_CONFIG = resolveDefaults();
|
|
301
|
+
|
|
249
302
|
function mergeThemePartials(
|
|
250
303
|
base: DeepPartial<PersonaTheme> | undefined,
|
|
251
304
|
override: DeepPartial<PersonaTheme> | undefined
|
|
@@ -266,57 +319,62 @@ function mergeThemePartials(
|
|
|
266
319
|
export function mergeWithDefaults(
|
|
267
320
|
config?: Partial<AgentWidgetConfig>
|
|
268
321
|
): Partial<AgentWidgetConfig> {
|
|
269
|
-
|
|
322
|
+
const defaults = resolveDefaults(config);
|
|
323
|
+
if (!config) return inheritDefaultProvenance(inheritPanelAliasProvenance(defaults), undefined, DEFAULTS_V5);
|
|
270
324
|
|
|
271
|
-
return {
|
|
272
|
-
...
|
|
325
|
+
return inheritDefaultProvenance(inheritPanelAliasProvenance({
|
|
326
|
+
...defaults,
|
|
273
327
|
...config,
|
|
274
|
-
theme: mergeThemePartials(
|
|
275
|
-
darkTheme: mergeThemePartials(
|
|
328
|
+
theme: mergeThemePartials(defaults.theme, config.theme),
|
|
329
|
+
darkTheme: mergeThemePartials(defaults.darkTheme, config.darkTheme),
|
|
276
330
|
launcher: {
|
|
277
|
-
...
|
|
331
|
+
...defaults.launcher,
|
|
278
332
|
...config.launcher,
|
|
279
333
|
dock: {
|
|
280
|
-
...
|
|
334
|
+
...defaults.launcher?.dock,
|
|
281
335
|
...config.launcher?.dock,
|
|
282
336
|
},
|
|
283
337
|
clearChat: {
|
|
284
|
-
...
|
|
338
|
+
...defaults.launcher?.clearChat,
|
|
285
339
|
...config.launcher?.clearChat,
|
|
286
340
|
},
|
|
287
341
|
},
|
|
342
|
+
...((defaults.composer || config.composer) ? { composer: {
|
|
343
|
+
...defaults.composer,
|
|
344
|
+
...config.composer,
|
|
345
|
+
} } : {}),
|
|
288
346
|
tooltip: {
|
|
289
|
-
...
|
|
347
|
+
...defaults.tooltip,
|
|
290
348
|
...config.tooltip,
|
|
291
349
|
},
|
|
292
350
|
copy: {
|
|
293
|
-
...
|
|
351
|
+
...defaults.copy,
|
|
294
352
|
...config.copy,
|
|
295
353
|
},
|
|
296
354
|
sendButton: {
|
|
297
|
-
...
|
|
355
|
+
...defaults.sendButton,
|
|
298
356
|
...config.sendButton,
|
|
299
357
|
},
|
|
300
358
|
statusIndicator: {
|
|
301
|
-
...
|
|
359
|
+
...defaults.statusIndicator,
|
|
302
360
|
...config.statusIndicator,
|
|
303
361
|
},
|
|
304
362
|
voiceRecognition: {
|
|
305
|
-
...
|
|
363
|
+
...defaults.voiceRecognition,
|
|
306
364
|
...config.voiceRecognition,
|
|
307
365
|
},
|
|
308
366
|
features: (() => {
|
|
309
|
-
const da =
|
|
367
|
+
const da = defaults.features?.artifacts;
|
|
310
368
|
const ca = config.features?.artifacts;
|
|
311
|
-
const dsb =
|
|
369
|
+
const dsb = defaults.features?.scrollToBottom;
|
|
312
370
|
const csb = config.features?.scrollToBottom;
|
|
313
|
-
const dsc =
|
|
371
|
+
const dsc = defaults.features?.scrollBehavior;
|
|
314
372
|
const csc = config.features?.scrollBehavior;
|
|
315
|
-
const dsa =
|
|
373
|
+
const dsa = defaults.features?.streamAnimation;
|
|
316
374
|
const csa = config.features?.streamAnimation;
|
|
317
|
-
const dau =
|
|
375
|
+
const dau = defaults.features?.askUserQuestion;
|
|
318
376
|
const cau = config.features?.askUserQuestion;
|
|
319
|
-
const dh =
|
|
377
|
+
const dh = defaults.features?.history;
|
|
320
378
|
const ch = config.features?.history;
|
|
321
379
|
const mergedArtifacts =
|
|
322
380
|
da === undefined && ca === undefined
|
|
@@ -373,8 +431,16 @@ export function mergeWithDefaults(
|
|
|
373
431
|
},
|
|
374
432
|
};
|
|
375
433
|
return {
|
|
376
|
-
...
|
|
434
|
+
...defaults.features,
|
|
377
435
|
...config.features,
|
|
436
|
+
toolCallDisplay: {
|
|
437
|
+
...defaults.features?.toolCallDisplay,
|
|
438
|
+
...config.features?.toolCallDisplay,
|
|
439
|
+
},
|
|
440
|
+
reasoningDisplay: {
|
|
441
|
+
...defaults.features?.reasoningDisplay,
|
|
442
|
+
...config.features?.reasoningDisplay,
|
|
443
|
+
},
|
|
378
444
|
...(mergedScrollToBottom !== undefined ? { scrollToBottom: mergedScrollToBottom } : {}),
|
|
379
445
|
...(mergedScrollBehavior !== undefined ? { scrollBehavior: mergedScrollBehavior } : {}),
|
|
380
446
|
...(mergedArtifacts !== undefined ? { artifacts: mergedArtifacts } : {}),
|
|
@@ -383,46 +449,59 @@ export function mergeWithDefaults(
|
|
|
383
449
|
...(mergedHistory !== undefined ? { history: mergedHistory } : {}),
|
|
384
450
|
};
|
|
385
451
|
})(),
|
|
386
|
-
suggestionChips: config.suggestionChips ??
|
|
452
|
+
suggestionChips: config.suggestionChips ?? defaults.suggestionChips,
|
|
387
453
|
suggestionChipsConfig: {
|
|
388
|
-
...
|
|
454
|
+
...defaults.suggestionChipsConfig,
|
|
389
455
|
...config.suggestionChipsConfig,
|
|
390
456
|
},
|
|
391
457
|
layout: {
|
|
392
|
-
...
|
|
458
|
+
...defaults.layout,
|
|
393
459
|
...config.layout,
|
|
394
460
|
header: {
|
|
395
|
-
...
|
|
461
|
+
...defaults.layout?.header,
|
|
396
462
|
...config.layout?.header,
|
|
397
463
|
},
|
|
398
464
|
messages: {
|
|
399
|
-
...
|
|
465
|
+
...defaults.layout?.messages,
|
|
400
466
|
...config.layout?.messages,
|
|
467
|
+
...((defaults.layout?.messages?.user || config.layout?.messages?.user) ? {
|
|
468
|
+
user: {
|
|
469
|
+
...defaults.layout?.messages?.user,
|
|
470
|
+
...config.layout?.messages?.user,
|
|
471
|
+
},
|
|
472
|
+
} : {}),
|
|
473
|
+
...((defaults.layout?.messages?.assistant || config.layout?.messages?.assistant) ? {
|
|
474
|
+
assistant: {
|
|
475
|
+
...defaults.layout?.messages?.assistant,
|
|
476
|
+
...config.layout?.messages?.assistant,
|
|
477
|
+
},
|
|
478
|
+
} : {}),
|
|
401
479
|
avatar: {
|
|
402
|
-
...
|
|
480
|
+
...defaults.layout?.messages?.avatar,
|
|
403
481
|
...config.layout?.messages?.avatar,
|
|
404
482
|
},
|
|
405
483
|
timestamp: {
|
|
406
|
-
...
|
|
484
|
+
...defaults.layout?.messages?.timestamp,
|
|
407
485
|
...config.layout?.messages?.timestamp,
|
|
408
486
|
},
|
|
409
487
|
},
|
|
410
488
|
slots: {
|
|
411
|
-
...
|
|
489
|
+
...defaults.layout?.slots,
|
|
412
490
|
...config.layout?.slots,
|
|
413
491
|
},
|
|
414
492
|
},
|
|
415
493
|
markdown: {
|
|
416
|
-
...
|
|
494
|
+
...defaults.markdown,
|
|
417
495
|
...config.markdown,
|
|
418
496
|
options: {
|
|
419
|
-
...
|
|
497
|
+
...defaults.markdown?.options,
|
|
420
498
|
...config.markdown?.options,
|
|
421
499
|
},
|
|
422
500
|
},
|
|
423
501
|
messageActions: {
|
|
424
|
-
...
|
|
502
|
+
...defaults.messageActions,
|
|
425
503
|
...config.messageActions,
|
|
426
504
|
},
|
|
427
|
-
};
|
|
505
|
+
}, config), config, DEFAULTS_V5);
|
|
506
|
+
|
|
428
507
|
}
|