@runtypelabs/persona 4.23.2 → 4.25.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-CI7OckWI.d.cts → types-Db61aI9J.d.cts} +2 -0
- package/dist/animations/{types-CI7OckWI.d.ts → types-Db61aI9J.d.ts} +2 -0
- package/dist/animations/wipe.d.cts +1 -1
- package/dist/animations/wipe.d.ts +1 -1
- package/dist/animations-extra.d.cts +2 -0
- package/dist/animations-extra.d.ts +2 -0
- package/dist/approval-ui-3NMYNXBD.js +1 -0
- package/dist/approval-ui.cjs +2 -2
- package/dist/approval-ui.d.cts +278 -37
- package/dist/approval-ui.d.ts +278 -37
- 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 +273 -25
- package/dist/artifacts-ui.d.ts +273 -25
- 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 +273 -25
- package/dist/context-mentions.d.ts +273 -25
- package/dist/event-stream-view.d.cts +273 -25
- package/dist/event-stream-view.d.ts +273 -25
- package/dist/forms-ui.d.cts +319 -27
- package/dist/forms-ui.d.ts +319 -27
- package/dist/history-view.d.cts +2 -0
- package/dist/history-view.d.ts +2 -0
- package/dist/index.cjs +70 -69
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +344 -39
- package/dist/index.d.ts +344 -39
- 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 +273 -25
- package/dist/session-reconnect.d.ts +273 -25
- package/dist/smart-dom-reader.d.cts +273 -25
- package/dist/smart-dom-reader.d.ts +273 -25
- package/dist/theme-editor-preview.cjs +74 -73
- package/dist/theme-editor-preview.d.cts +280 -25
- package/dist/theme-editor-preview.d.ts +280 -25
- package/dist/theme-editor-preview.js +51 -50
- package/dist/theme-editor.cjs +10 -10
- package/dist/theme-editor.d.cts +293 -27
- package/dist/theme-editor.d.ts +293 -27
- 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/voice-runtime-7DA7MJH4.js +1 -0
- package/dist/voice-runtime.cjs +1 -1
- package/dist/voice-runtime.d.cts +33 -0
- package/dist/voice-runtime.d.ts +33 -0
- package/dist/voice-runtime.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.ts +122 -30
- 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 +2 -2
- package/src/index-core.ts +4 -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 +59 -7
- package/src/session.voice.test.ts +72 -1
- 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 +207 -22
- 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 +422 -74
- package/src/ui.v5-defaults.test.ts +144 -0
- package/src/ui.voice-prewarm.test.ts +222 -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/voice/runtype-voice-provider.ts +238 -27
- package/src/voice/voice.test.ts +272 -2
- package/src/welcome.test.ts +23 -0
- package/src/welcome.ts +16 -9
- package/dist/approval-ui-K7R5Q5OC.js +0 -1
- package/dist/voice-runtime-EFR3R5ES.js +0 -1
|
@@ -2,7 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
import { beforeEach, describe, expect, it } from "vitest";
|
|
4
4
|
import {
|
|
5
|
-
approvalDetailsExpansionState,
|
|
6
5
|
createApprovalBubble,
|
|
7
6
|
humanizeToolName,
|
|
8
7
|
updateApprovalDetailsUI,
|
|
@@ -11,6 +10,7 @@ import {
|
|
|
11
10
|
getWebMcpToolDisplayTitle,
|
|
12
11
|
recordWebMcpToolDisplayTitles,
|
|
13
12
|
} from "../webmcp-bridge";
|
|
13
|
+
import { createTheme, themeToCssVariables } from "../utils/tokens";
|
|
14
14
|
import type {
|
|
15
15
|
AgentWidgetApproval,
|
|
16
16
|
AgentWidgetConfig,
|
|
@@ -74,6 +74,51 @@ describe("createApprovalBubble shadow", () => {
|
|
|
74
74
|
});
|
|
75
75
|
});
|
|
76
76
|
|
|
77
|
+
describe("createApprovalBubble resolved-status colors", () => {
|
|
78
|
+
const colorFor = (status: "approved" | "denied" | "timeout") =>
|
|
79
|
+
createApprovalBubble(makeMessage({ status }))
|
|
80
|
+
.querySelector("[data-approval-icon] svg")
|
|
81
|
+
?.getAttribute("stroke") ?? "";
|
|
82
|
+
|
|
83
|
+
const resolveCssVariable = (
|
|
84
|
+
value: string,
|
|
85
|
+
variables: Record<string, string>
|
|
86
|
+
): string | undefined => {
|
|
87
|
+
const match = /^var\((--[\w-]+)(?:,\s*(.+))?\)$/.exec(value.trim());
|
|
88
|
+
if (!match) return value;
|
|
89
|
+
return variables[match[1]] ??
|
|
90
|
+
(match[2] ? resolveCssVariable(match[2], variables) : undefined);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
it("resolves each legacy feedback color through emitted palette tokens", () => {
|
|
94
|
+
const variables = themeToCssVariables(createTheme());
|
|
95
|
+
|
|
96
|
+
expect(resolveCssVariable(colorFor("approved"), variables)).toBe("#16a34a");
|
|
97
|
+
expect(resolveCssVariable(colorFor("denied"), variables)).toBe("#dc2626");
|
|
98
|
+
expect(resolveCssVariable(colorFor("timeout"), variables)).toBe("#ca8a04");
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("keeps host feedback token overrides ahead of palette fallbacks", () => {
|
|
102
|
+
const variables = {
|
|
103
|
+
...themeToCssVariables(createTheme()),
|
|
104
|
+
"--persona-feedback-error": "rebeccapurple",
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
expect(resolveCssVariable(colorFor("denied"), variables)).toBe(
|
|
108
|
+
"rebeccapurple"
|
|
109
|
+
);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
it("gives the pending Deny label and icon a color emitted by the default theme", () => {
|
|
113
|
+
const bubble = createApprovalBubble(makeMessage());
|
|
114
|
+
const deny = bubble.querySelector<HTMLButtonElement>('[data-approval-action="deny"]')!;
|
|
115
|
+
const variables = themeToCssVariables(createTheme());
|
|
116
|
+
expect(resolveCssVariable(deny.style.color, variables)).toBe("#dc2626");
|
|
117
|
+
expect(resolveCssVariable(deny.querySelector("svg")!.getAttribute("stroke")!, variables))
|
|
118
|
+
.toBe("#dc2626");
|
|
119
|
+
});
|
|
120
|
+
});
|
|
121
|
+
|
|
77
122
|
const TOOL_DESCRIPTION =
|
|
78
123
|
"Add products to the shopping cart. IMPORTANT: If a product has required options you MUST include the variantId.";
|
|
79
124
|
|
|
@@ -95,12 +140,14 @@ const getDetails = (bubble: HTMLElement) =>
|
|
|
95
140
|
bubble.querySelector("[data-approval-details]") as HTMLElement | null;
|
|
96
141
|
|
|
97
142
|
beforeEach(() => {
|
|
98
|
-
|
|
143
|
+
expansionState = new Map<string, boolean>();
|
|
99
144
|
// Reset display titles recorded by a prior test (the map is rebuilt from
|
|
100
145
|
// each full snapshot, so recording an empty snapshot clears it).
|
|
101
146
|
recordWebMcpToolDisplayTitles([]);
|
|
102
147
|
});
|
|
103
148
|
|
|
149
|
+
let expansionState: Map<string, boolean>;
|
|
150
|
+
|
|
104
151
|
describe("humanizeToolName", () => {
|
|
105
152
|
it("converts snake_case to a sentence", () => {
|
|
106
153
|
expect(humanizeToolName("add_to_cart")).toBe("Add to cart");
|
|
@@ -157,8 +204,8 @@ describe("createApprovalBubble summary and details", () => {
|
|
|
157
204
|
});
|
|
158
205
|
|
|
159
206
|
it("respects a per-message expansion override from prior toggling", () => {
|
|
160
|
-
|
|
161
|
-
const bubble = createApprovalBubble(makeDetailedMessage());
|
|
207
|
+
expansionState.set("msg-1", true);
|
|
208
|
+
const bubble = createApprovalBubble(makeDetailedMessage(), undefined, expansionState);
|
|
162
209
|
expect(getToggle(bubble)?.getAttribute("aria-expanded")).toBe("true");
|
|
163
210
|
expect(getDetails(bubble)?.style.display).toBe("");
|
|
164
211
|
});
|
|
@@ -204,7 +251,7 @@ describe("createApprovalBubble summary and details", () => {
|
|
|
204
251
|
recordWebMcpToolDisplayTitles([
|
|
205
252
|
{ name: "add_to_cart", description: "", title: "Add to Cart" },
|
|
206
253
|
]);
|
|
207
|
-
const bubble = createApprovalBubble(makeDetailedMessage());
|
|
254
|
+
const bubble = createApprovalBubble(makeDetailedMessage(), undefined, expansionState);
|
|
208
255
|
expect(getSummary(bubble)?.textContent).toBe(
|
|
209
256
|
"The assistant wants to use “Add to Cart”."
|
|
210
257
|
);
|
|
@@ -265,14 +312,14 @@ describe("updateApprovalDetailsUI", () => {
|
|
|
265
312
|
const bubble = createApprovalBubble(makeDetailedMessage());
|
|
266
313
|
expect(getDetails(bubble)?.style.display).toBe("none");
|
|
267
314
|
|
|
268
|
-
|
|
269
|
-
updateApprovalDetailsUI("msg-1", bubble);
|
|
315
|
+
expansionState.set("msg-1", true);
|
|
316
|
+
updateApprovalDetailsUI("msg-1", bubble, undefined, expansionState);
|
|
270
317
|
expect(getDetails(bubble)?.style.display).toBe("");
|
|
271
318
|
expect(getToggle(bubble)?.getAttribute("aria-expanded")).toBe("true");
|
|
272
319
|
expect(getToggle(bubble)?.textContent).toContain("Hide details");
|
|
273
320
|
|
|
274
|
-
|
|
275
|
-
updateApprovalDetailsUI("msg-1", bubble);
|
|
321
|
+
expansionState.set("msg-1", false);
|
|
322
|
+
updateApprovalDetailsUI("msg-1", bubble, undefined, expansionState);
|
|
276
323
|
expect(getDetails(bubble)?.style.display).toBe("none");
|
|
277
324
|
expect(getToggle(bubble)?.textContent).toContain("Show details");
|
|
278
325
|
});
|
|
@@ -9,14 +9,6 @@ import { ChevronDown, ChevronUp, ShieldAlert, ShieldCheck, ShieldX } from "lucid
|
|
|
9
9
|
import { renderIconNode } from "../utils/icon-node";
|
|
10
10
|
import { WEBMCP_TOOL_PREFIX } from "../webmcp-bridge";
|
|
11
11
|
|
|
12
|
-
/**
|
|
13
|
-
* Per-message expanded/collapsed state for the technical-details section.
|
|
14
|
-
* Absent means "use the config default" (`approval.detailsDisplay`). Lives at
|
|
15
|
-
* module scope so the choice survives idiomorph re-renders, mirroring
|
|
16
|
-
* `toolExpansionState` in tool-bubble.ts.
|
|
17
|
-
*/
|
|
18
|
-
export const approvalDetailsExpansionState = new Map<string, boolean>();
|
|
19
|
-
|
|
20
12
|
/**
|
|
21
13
|
* Turn a wire tool name into a user-facing label: strips the `webmcp:`
|
|
22
14
|
* prefix and splits snake_case / kebab-case / camelCase into a sentence
|
|
@@ -41,10 +33,11 @@ const resolveApprovalConfig = (config?: AgentWidgetConfig) =>
|
|
|
41
33
|
|
|
42
34
|
const isDetailsExpanded = (
|
|
43
35
|
messageId: string,
|
|
36
|
+
expansionState: Map<string, boolean>,
|
|
44
37
|
config?: AgentWidgetConfig
|
|
45
38
|
): boolean => {
|
|
46
39
|
const detailsMode = resolveApprovalConfig(config)?.detailsDisplay ?? "collapsed";
|
|
47
|
-
return
|
|
40
|
+
return expansionState.get(messageId) ?? detailsMode === "expanded";
|
|
48
41
|
};
|
|
49
42
|
|
|
50
43
|
const applyDetailsToggleState = (
|
|
@@ -72,18 +65,19 @@ const applyDetailsToggleState = (
|
|
|
72
65
|
|
|
73
66
|
/**
|
|
74
67
|
* Sync the technical-details section (toggle label/chevron + visibility) with
|
|
75
|
-
*
|
|
68
|
+
* the widget instance's expansion state. Called from the ui.ts expansion event
|
|
76
69
|
* delegation after a toggle click.
|
|
77
70
|
*/
|
|
78
71
|
export const updateApprovalDetailsUI = (
|
|
79
72
|
messageId: string,
|
|
80
73
|
bubble: HTMLElement,
|
|
81
|
-
config?: AgentWidgetConfig
|
|
74
|
+
config?: AgentWidgetConfig,
|
|
75
|
+
expansionState = new Map<string, boolean>()
|
|
82
76
|
): void => {
|
|
83
77
|
const toggle = bubble.querySelector('button[data-bubble-type="approval"]') as HTMLElement | null;
|
|
84
78
|
const details = bubble.querySelector("[data-approval-details]") as HTMLElement | null;
|
|
85
79
|
if (!toggle || !details) return;
|
|
86
|
-
const expanded = isDetailsExpanded(messageId, config);
|
|
80
|
+
const expanded = isDetailsExpanded(messageId, expansionState, config);
|
|
87
81
|
applyDetailsToggleState(toggle, expanded, config);
|
|
88
82
|
details.style.display = expanded ? "" : "none";
|
|
89
83
|
};
|
|
@@ -112,16 +106,16 @@ export const updateApprovalBubbleUI = (
|
|
|
112
106
|
// Update badge color
|
|
113
107
|
if (approval.status === "approved") {
|
|
114
108
|
statusBadge.className = "persona-inline-flex persona-items-center persona-px-2 persona-py-0.5 persona-rounded-full persona-text-xs persona-font-medium";
|
|
115
|
-
statusBadge.style.backgroundColor = "var(--persona-palette-colors-success-100
|
|
116
|
-
statusBadge.style.color = "var(--persona-palette-colors-success-700
|
|
109
|
+
statusBadge.style.backgroundColor = "var(--persona-palette-colors-success-100)";
|
|
110
|
+
statusBadge.style.color = "var(--persona-palette-colors-success-700)";
|
|
117
111
|
} else if (approval.status === "denied") {
|
|
118
112
|
statusBadge.className = "persona-inline-flex persona-items-center persona-px-2 persona-py-0.5 persona-rounded-full persona-text-xs persona-font-medium";
|
|
119
|
-
statusBadge.style.backgroundColor = "var(--persona-palette-colors-error-100
|
|
120
|
-
statusBadge.style.color = "var(--persona-palette-colors-error-700
|
|
113
|
+
statusBadge.style.backgroundColor = "var(--persona-palette-colors-error-100)";
|
|
114
|
+
statusBadge.style.color = "var(--persona-palette-colors-error-700)";
|
|
121
115
|
} else if (approval.status === "timeout") {
|
|
122
116
|
statusBadge.className = "persona-inline-flex persona-items-center persona-px-2 persona-py-0.5 persona-rounded-full persona-text-xs persona-font-medium";
|
|
123
|
-
statusBadge.style.backgroundColor = "var(--persona-palette-colors-warning-100
|
|
124
|
-
statusBadge.style.color = "var(--persona-palette-colors-warning-700
|
|
117
|
+
statusBadge.style.backgroundColor = "var(--persona-palette-colors-warning-100)";
|
|
118
|
+
statusBadge.style.color = "var(--persona-palette-colors-warning-700)";
|
|
125
119
|
}
|
|
126
120
|
statusBadge.setAttribute("data-approval-status", approval.status);
|
|
127
121
|
}
|
|
@@ -133,9 +127,9 @@ export const updateApprovalBubbleUI = (
|
|
|
133
127
|
const iconData = approval.status === "denied" ? ShieldX
|
|
134
128
|
: approval.status === "timeout" ? ShieldAlert
|
|
135
129
|
: ShieldCheck;
|
|
136
|
-
const iconColor = approval.status === "approved" ? "var(--persona-feedback-success,
|
|
137
|
-
: approval.status === "denied" ? "var(--persona-feedback-error,
|
|
138
|
-
: approval.status === "timeout" ? "var(--persona-feedback-warning,
|
|
130
|
+
const iconColor = approval.status === "approved" ? "var(--persona-feedback-success, var(--persona-palette-colors-success-600))"
|
|
131
|
+
: approval.status === "denied" ? "var(--persona-feedback-error, var(--persona-palette-colors-error-600))"
|
|
132
|
+
: approval.status === "timeout" ? "var(--persona-feedback-warning, var(--persona-palette-colors-warning-600))"
|
|
139
133
|
: (approvalConfig?.titleColor ?? "currentColor");
|
|
140
134
|
const icon = renderIconNode(iconData, 20, iconColor, 2);
|
|
141
135
|
if (icon) {
|
|
@@ -155,7 +149,8 @@ export const updateApprovalBubbleUI = (
|
|
|
155
149
|
*/
|
|
156
150
|
export const createApprovalBubble = (
|
|
157
151
|
message: AgentWidgetMessage,
|
|
158
|
-
config?: AgentWidgetConfig
|
|
152
|
+
config?: AgentWidgetConfig,
|
|
153
|
+
expansionState = new Map<string, boolean>()
|
|
159
154
|
): HTMLElement => {
|
|
160
155
|
const approval = message.approval;
|
|
161
156
|
const approvalConfig = config?.approval !== false ? config?.approval : undefined;
|
|
@@ -178,12 +173,12 @@ export const createApprovalBubble = (
|
|
|
178
173
|
bubble.setAttribute("data-message-id", message.id);
|
|
179
174
|
|
|
180
175
|
// Apply styling: use semantic tokens with config overrides
|
|
181
|
-
bubble.style.backgroundColor = approvalConfig?.backgroundColor ?? "var(--persona-approval-bg
|
|
182
|
-
bubble.style.borderColor = approvalConfig?.borderColor ?? "var(--persona-approval-border
|
|
176
|
+
bubble.style.backgroundColor = approvalConfig?.backgroundColor ?? "var(--persona-approval-bg)";
|
|
177
|
+
bubble.style.borderColor = approvalConfig?.borderColor ?? "var(--persona-approval-border)";
|
|
183
178
|
bubble.style.boxShadow =
|
|
184
179
|
approvalConfig?.shadow !== undefined
|
|
185
180
|
? (approvalConfig.shadow.trim() === "" ? "none" : approvalConfig.shadow)
|
|
186
|
-
: "var(--persona-approval-shadow
|
|
181
|
+
: "var(--persona-approval-shadow)";
|
|
187
182
|
|
|
188
183
|
if (!approval) {
|
|
189
184
|
return bubble;
|
|
@@ -201,9 +196,9 @@ export const createApprovalBubble = (
|
|
|
201
196
|
const iconData = approval.status === "denied" ? ShieldX
|
|
202
197
|
: approval.status === "timeout" ? ShieldAlert
|
|
203
198
|
: ShieldCheck;
|
|
204
|
-
const iconColor = approval.status === "approved" ? "var(--persona-feedback-success,
|
|
205
|
-
: approval.status === "denied" ? "var(--persona-feedback-error,
|
|
206
|
-
: approval.status === "timeout" ? "var(--persona-feedback-warning,
|
|
199
|
+
const iconColor = approval.status === "approved" ? "var(--persona-feedback-success, var(--persona-palette-colors-success-600))"
|
|
200
|
+
: approval.status === "denied" ? "var(--persona-feedback-error, var(--persona-palette-colors-error-600))"
|
|
201
|
+
: approval.status === "timeout" ? "var(--persona-feedback-warning, var(--persona-palette-colors-warning-600))"
|
|
207
202
|
: (approvalConfig?.titleColor ?? "currentColor");
|
|
208
203
|
const icon = renderIconNode(iconData, 20, iconColor, 2);
|
|
209
204
|
if (icon) {
|
|
@@ -227,16 +222,16 @@ export const createApprovalBubble = (
|
|
|
227
222
|
const badge = createElement("span", "persona-inline-flex persona-items-center persona-px-2 persona-py-0.5 persona-rounded-full persona-text-xs persona-font-medium");
|
|
228
223
|
badge.setAttribute("data-approval-status", approval.status);
|
|
229
224
|
if (approval.status === "approved") {
|
|
230
|
-
badge.style.backgroundColor = "var(--persona-palette-colors-success-100
|
|
231
|
-
badge.style.color = "var(--persona-palette-colors-success-700
|
|
225
|
+
badge.style.backgroundColor = "var(--persona-palette-colors-success-100)";
|
|
226
|
+
badge.style.color = "var(--persona-palette-colors-success-700)";
|
|
232
227
|
badge.textContent = "Approved";
|
|
233
228
|
} else if (approval.status === "denied") {
|
|
234
|
-
badge.style.backgroundColor = "var(--persona-palette-colors-error-100
|
|
235
|
-
badge.style.color = "var(--persona-palette-colors-error-700
|
|
229
|
+
badge.style.backgroundColor = "var(--persona-palette-colors-error-100)";
|
|
230
|
+
badge.style.color = "var(--persona-palette-colors-error-700)";
|
|
236
231
|
badge.textContent = "Denied";
|
|
237
232
|
} else if (approval.status === "timeout") {
|
|
238
|
-
badge.style.backgroundColor = "var(--persona-palette-colors-warning-100
|
|
239
|
-
badge.style.color = "var(--persona-palette-colors-warning-700
|
|
233
|
+
badge.style.backgroundColor = "var(--persona-palette-colors-warning-100)";
|
|
234
|
+
badge.style.color = "var(--persona-palette-colors-warning-700)";
|
|
240
235
|
badge.textContent = "Timeout";
|
|
241
236
|
}
|
|
242
237
|
titleRow.appendChild(badge);
|
|
@@ -303,7 +298,7 @@ export const createApprovalBubble = (
|
|
|
303
298
|
const showDescriptionInDetails = Boolean(approval.description) && !summaryFallsBackToDescription;
|
|
304
299
|
const hasDetails = showDescriptionInDetails || Boolean(approval.parameters);
|
|
305
300
|
if (detailsMode !== "hidden" && hasDetails) {
|
|
306
|
-
const expanded = isDetailsExpanded(message.id, config);
|
|
301
|
+
const expanded = isDetailsExpanded(message.id, expansionState, config);
|
|
307
302
|
|
|
308
303
|
const toggle = createElement(
|
|
309
304
|
"button",
|
|
@@ -364,10 +359,10 @@ export const createApprovalBubble = (
|
|
|
364
359
|
// Approve button
|
|
365
360
|
const approveBtn = createElement("button", "persona-inline-flex persona-items-center persona-px-3 persona-py-1.5 persona-rounded-md persona-text-xs persona-font-medium persona-border-none persona-cursor-pointer") as HTMLButtonElement;
|
|
366
361
|
approveBtn.type = "button";
|
|
367
|
-
approveBtn.style.backgroundColor = approvalConfig?.approveButtonColor ?? "var(--persona-approval-approve-bg
|
|
368
|
-
approveBtn.style.color = approvalConfig?.approveButtonTextColor ?? "
|
|
362
|
+
approveBtn.style.backgroundColor = approvalConfig?.approveButtonColor ?? "var(--persona-approval-approve-bg)";
|
|
363
|
+
approveBtn.style.color = approvalConfig?.approveButtonTextColor ?? "var(--persona-button-primary-fg)";
|
|
369
364
|
approveBtn.setAttribute("data-approval-action", "approve");
|
|
370
|
-
const approveIcon = renderIconNode(ShieldCheck, 14, approvalConfig?.approveButtonTextColor ?? "
|
|
365
|
+
const approveIcon = renderIconNode(ShieldCheck, 14, approvalConfig?.approveButtonTextColor ?? "var(--persona-button-primary-fg)", 2);
|
|
371
366
|
if (approveIcon) {
|
|
372
367
|
approveIcon.style.marginRight = "4px";
|
|
373
368
|
approveBtn.appendChild(approveIcon);
|
|
@@ -379,10 +374,10 @@ export const createApprovalBubble = (
|
|
|
379
374
|
const denyBtn = createElement("button", "persona-inline-flex persona-items-center persona-px-3 persona-py-1.5 persona-rounded-md persona-text-xs persona-font-medium persona-cursor-pointer") as HTMLButtonElement;
|
|
380
375
|
denyBtn.type = "button";
|
|
381
376
|
denyBtn.style.backgroundColor = approvalConfig?.denyButtonColor ?? "transparent";
|
|
382
|
-
denyBtn.style.color = approvalConfig?.denyButtonTextColor ?? "var(--persona-feedback-error,
|
|
383
|
-
denyBtn.style.border = `1px solid ${approvalConfig?.denyButtonTextColor ? approvalConfig.denyButtonTextColor : "var(--persona-palette-colors-error-200
|
|
377
|
+
denyBtn.style.color = approvalConfig?.denyButtonTextColor ?? "var(--persona-feedback-error, var(--persona-palette-colors-error-600))";
|
|
378
|
+
denyBtn.style.border = `1px solid ${approvalConfig?.denyButtonTextColor ? approvalConfig.denyButtonTextColor : "var(--persona-palette-colors-error-200)"}`;
|
|
384
379
|
denyBtn.setAttribute("data-approval-action", "deny");
|
|
385
|
-
const denyIcon = renderIconNode(ShieldX, 14, approvalConfig?.denyButtonTextColor ?? "var(--persona-feedback-error,
|
|
380
|
+
const denyIcon = renderIconNode(ShieldX, 14, approvalConfig?.denyButtonTextColor ?? "var(--persona-feedback-error, var(--persona-palette-colors-error-600))", 2);
|
|
386
381
|
if (denyIcon) {
|
|
387
382
|
denyIcon.style.marginRight = "4px";
|
|
388
383
|
denyBtn.appendChild(denyIcon);
|
|
@@ -75,7 +75,6 @@ export const buildComposer = (context: ComposerBuildContext): ComposerElements =
|
|
|
75
75
|
className:
|
|
76
76
|
"persona-widget-composer persona-flex persona-flex-col persona-gap-2 persona-rounded-2xl persona-border persona-border-persona-border persona-bg-persona-input-background persona-px-4 persona-py-3",
|
|
77
77
|
attrs: { "data-persona-composer-form": "" },
|
|
78
|
-
style: { outline: "none" },
|
|
79
78
|
});
|
|
80
79
|
|
|
81
80
|
const { textarea, attachAutoResize } = createComposerTextarea(config);
|
|
@@ -144,7 +143,11 @@ export const buildComposer = (context: ComposerBuildContext): ComposerElements =
|
|
|
144
143
|
}
|
|
145
144
|
});
|
|
146
145
|
|
|
147
|
-
|
|
146
|
+
if (config?.statusIndicator?.mode === "transient") {
|
|
147
|
+
footer.append(suggestions, statusText, composerForm);
|
|
148
|
+
} else {
|
|
149
|
+
footer.append(suggestions, composerForm, statusText);
|
|
150
|
+
}
|
|
148
151
|
|
|
149
152
|
return {
|
|
150
153
|
footer,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { applyStatusIndicatorState } from "../utils/status-indicator";
|
|
1
2
|
import { createElement, createNode, cx } from "../utils/dom";
|
|
2
3
|
import { renderLucideIcon } from "../utils/icons";
|
|
3
4
|
import { AgentWidgetConfig } from "../types";
|
|
@@ -293,6 +294,14 @@ export const createSendButton = (config?: AgentWidgetConfig): SendButtonParts =>
|
|
|
293
294
|
sendIcon = renderLucideIcon(iconName, iconSize, iconColor, iconStroke);
|
|
294
295
|
}
|
|
295
296
|
stopIcon = renderLucideIcon(stopIconName, iconSize, iconColor, iconStroke);
|
|
297
|
+
// Keep explicit per-control sizing authoritative; otherwise use the theme.
|
|
298
|
+
if (!sendButtonConfig.iconSize) {
|
|
299
|
+
for (const icon of [sendIcon, stopIcon]) {
|
|
300
|
+
if (!icon) continue;
|
|
301
|
+
icon.style.width = `var(--persona-components-composer-sendIconSize, ${iconSize}px)`;
|
|
302
|
+
icon.style.height = `var(--persona-components-composer-sendIconSize, ${iconSize}px)`;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
296
305
|
|
|
297
306
|
if (sendIcon && stopIcon) {
|
|
298
307
|
// One grid cell, both glyphs in it. `data-mode` on the stack decides
|
|
@@ -538,8 +547,7 @@ export const createStatusText = (config?: AgentWidgetConfig): HTMLElement => {
|
|
|
538
547
|
);
|
|
539
548
|
statusText.setAttribute("data-persona-composer-status", "");
|
|
540
549
|
|
|
541
|
-
|
|
542
|
-
statusText.style.display = isVisible ? "" : "none";
|
|
550
|
+
applyStatusIndicatorState(statusText, statusConfig, "idle");
|
|
543
551
|
const idleLabel = statusConfig.idleText ?? "Online";
|
|
544
552
|
if (statusConfig.idleLink) {
|
|
545
553
|
const link = createElement("a") as HTMLAnchorElement;
|
|
@@ -1,9 +1,41 @@
|
|
|
1
1
|
// @vitest-environment jsdom
|
|
2
2
|
|
|
3
3
|
import { describe, expect, it } from "vitest";
|
|
4
|
+
import {
|
|
5
|
+
DEFAULT_HEADER_ICON_SIZE,
|
|
6
|
+
DEFAULT_LAUNCHER_CONFIG,
|
|
7
|
+
} from "../defaults";
|
|
4
8
|
import { buildHeader } from "./header-builder";
|
|
5
9
|
|
|
6
10
|
describe("buildHeader tooltips", () => {
|
|
11
|
+
it.each([{ v5Defaults: false }, { v5Defaults: true }])(
|
|
12
|
+
"uses the standard padding token under v5Defaults=%s",
|
|
13
|
+
(future) => {
|
|
14
|
+
const { header } = buildHeader({ config: { future } as any });
|
|
15
|
+
expect(header.style.padding).toBe(
|
|
16
|
+
"var(--persona-components-header-padding, 20px 24px)"
|
|
17
|
+
);
|
|
18
|
+
}
|
|
19
|
+
);
|
|
20
|
+
it.each([false, true])("preserves legacy numeric glyph sizing for non-pixel icon boxes (v5=%s)", (v5Defaults) => {
|
|
21
|
+
const { iconHolder } = buildHeader({ config: {
|
|
22
|
+
apiUrl: "/dispatch", future: { v5Defaults },
|
|
23
|
+
launcher: { headerIconSize: "2rem", headerIconName: "bot" },
|
|
24
|
+
} });
|
|
25
|
+
const glyph = iconHolder.querySelector("svg")!;
|
|
26
|
+
expect(iconHolder.style.width).toBe("2rem");
|
|
27
|
+
expect(glyph.getAttribute("width")).toBe("1.2");
|
|
28
|
+
expect(glyph.style.width).toBe("calc(2px * var(--persona-components-header-iconScale, 0.6))");
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it("uses the shared launcher icon default", () => {
|
|
32
|
+
const { iconHolder } = buildHeader({});
|
|
33
|
+
|
|
34
|
+
expect(DEFAULT_LAUNCHER_CONFIG.headerIconSize).toBe(DEFAULT_HEADER_ICON_SIZE);
|
|
35
|
+
expect(iconHolder.style.height).toBe(DEFAULT_HEADER_ICON_SIZE);
|
|
36
|
+
expect(iconHolder.style.width).toBe(DEFAULT_HEADER_ICON_SIZE);
|
|
37
|
+
});
|
|
38
|
+
|
|
7
39
|
it("portals the close-button tooltip into the mounted document", () => {
|
|
8
40
|
const iframeDocument = document.implementation.createHTMLDocument("preview");
|
|
9
41
|
const { header, closeButton, closeButtonWrapper } = buildHeader({
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createElement, createNode } from "../utils/dom";
|
|
2
2
|
import { renderLucideIcon } from "../utils/icons";
|
|
3
3
|
import { AgentWidgetConfig } from "../types";
|
|
4
|
+
import { DEFAULT_HEADER_ICON_SIZE } from "../defaults";
|
|
4
5
|
import { createCloseButton, createClearChatButton } from "./header-parts";
|
|
5
6
|
|
|
6
7
|
/** CSS `color` values; variables are set on `[data-persona-root]` from `theme.components.header`. */
|
|
@@ -10,7 +11,7 @@ export const HEADER_THEME_CSS = {
|
|
|
10
11
|
subtitleColor:
|
|
11
12
|
"var(--persona-header-subtitle-fg, var(--persona-text-muted, var(--persona-muted, #9ca3af)))",
|
|
12
13
|
actionIconColor:
|
|
13
|
-
"var(--persona-header-action-icon-fg, var(--persona-muted, #9ca3af))",
|
|
14
|
+
"var(--persona-header-control-state-fg, var(--persona-header-action-icon-fg, var(--persona-muted, #9ca3af)))",
|
|
14
15
|
/** One knob for both header builders; the Messages rail header shares it. */
|
|
15
16
|
minHeight: "var(--persona-header-min-height, auto)",
|
|
16
17
|
} as const;
|
|
@@ -64,9 +65,10 @@ export const buildHeader = (context: HeaderBuildContext): HeaderElements => {
|
|
|
64
65
|
|
|
65
66
|
const header = createNode("div", {
|
|
66
67
|
className:
|
|
67
|
-
"persona-widget-header persona-flex persona-items-center persona-gap-3
|
|
68
|
+
"persona-widget-header persona-flex persona-items-center persona-gap-3",
|
|
68
69
|
attrs: { "data-persona-theme-zone": "header" },
|
|
69
70
|
style: {
|
|
71
|
+
padding: "var(--persona-components-header-padding, 20px 24px)",
|
|
70
72
|
minHeight: HEADER_THEME_CSS.minHeight,
|
|
71
73
|
backgroundColor: "var(--persona-header-bg, var(--persona-surface, #ffffff))",
|
|
72
74
|
borderBottomColor: "var(--persona-header-border, var(--persona-divider, #f1f5f9))",
|
|
@@ -77,7 +79,7 @@ export const buildHeader = (context: HeaderBuildContext): HeaderElements => {
|
|
|
77
79
|
});
|
|
78
80
|
|
|
79
81
|
const launcher = config?.launcher ?? {};
|
|
80
|
-
const headerIconSize = launcher.headerIconSize ??
|
|
82
|
+
const headerIconSize = launcher.headerIconSize ?? DEFAULT_HEADER_ICON_SIZE;
|
|
81
83
|
const closeButtonPlacement = launcher.closeButtonPlacement ?? "inline";
|
|
82
84
|
const headerIconHidden = launcher.headerIconHidden ?? false;
|
|
83
85
|
const headerIconName = launcher.headerIconName;
|
|
@@ -100,6 +102,8 @@ export const buildHeader = (context: HeaderBuildContext): HeaderElements => {
|
|
|
100
102
|
const iconSize = parseFloat(headerIconSize) || 24;
|
|
101
103
|
const iconSvg = renderLucideIcon(headerIconName, iconSize * 0.6, "currentColor", 1);
|
|
102
104
|
if (iconSvg) {
|
|
105
|
+
iconSvg.style.width = `calc(${iconSize}px * var(--persona-components-header-iconScale, 0.6))`;
|
|
106
|
+
iconSvg.style.height = `calc(${iconSize}px * var(--persona-components-header-iconScale, 0.6))`;
|
|
103
107
|
iconHolder.replaceChildren(iconSvg);
|
|
104
108
|
} else {
|
|
105
109
|
// Fallback to agentIconText if Lucide icon fails
|
|
@@ -238,4 +242,3 @@ export const attachHeaderToContainer = (
|
|
|
238
242
|
container.appendChild(headerElements.clearChatButtonWrapper);
|
|
239
243
|
}
|
|
240
244
|
};
|
|
241
|
-
|
|
@@ -16,6 +16,18 @@ const build = (layoutHeaderConfig: Parameters<typeof buildMinimalHeader>[0]["lay
|
|
|
16
16
|
});
|
|
17
17
|
|
|
18
18
|
describe("minimal header trailing actions", () => {
|
|
19
|
+
it.each([{ v5Defaults: false }, { v5Defaults: true }])(
|
|
20
|
+
"uses the minimal padding token under v5Defaults=%s",
|
|
21
|
+
(future) => {
|
|
22
|
+
const elements = buildMinimalHeader({
|
|
23
|
+
config: { apiUrl: "https://example.com/api", future } as AgentWidgetConfig,
|
|
24
|
+
});
|
|
25
|
+
expect(elements.header.style.padding).toBe(
|
|
26
|
+
"var(--persona-components-header-minimalPadding, var(--persona-components-header-padding, 16px 24px))"
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
|
|
19
31
|
it("clusters actions at the trailing edge with the close button's chrome", () => {
|
|
20
32
|
const onAction = vi.fn();
|
|
21
33
|
const elements = build({
|
|
@@ -122,10 +122,12 @@ export const buildMinimalHeader: HeaderLayoutRenderer = (context) => {
|
|
|
122
122
|
// max(title 24px, cluster 32px) + py-4 + border = the strip's 65px.
|
|
123
123
|
const header = createElement(
|
|
124
124
|
"div",
|
|
125
|
-
"persona-flex persona-items-center persona-justify-between
|
|
125
|
+
"persona-flex persona-items-center persona-justify-between"
|
|
126
126
|
);
|
|
127
127
|
header.setAttribute("data-persona-theme-zone", "header");
|
|
128
128
|
header.style.minHeight = HEADER_THEME_CSS.minHeight;
|
|
129
|
+
header.style.padding =
|
|
130
|
+
"var(--persona-components-header-minimalPadding, var(--persona-components-header-padding, 16px 24px))";
|
|
129
131
|
header.style.backgroundColor = 'var(--persona-header-bg, var(--persona-surface, #ffffff))';
|
|
130
132
|
header.style.borderBottomColor = 'var(--persona-header-border, var(--persona-divider, #f1f5f9))';
|
|
131
133
|
header.style.boxShadow = 'var(--persona-header-shadow, none)';
|
|
@@ -393,4 +395,3 @@ export const buildHeaderWithLayout = (
|
|
|
393
395
|
|
|
394
396
|
return headerElements;
|
|
395
397
|
};
|
|
396
|
-
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolveLauncherVariant } from "../utils/launcher-variant";
|
|
1
2
|
import { createElement, createNode, cx } from "../utils/dom";
|
|
2
3
|
import { AgentWidgetConfig, AgentWidgetLauncherTeaserConfig } from "../types";
|
|
3
4
|
import { positionMap } from "../utils/positioning";
|
|
@@ -31,6 +32,14 @@ export const createLauncherButton = (
|
|
|
31
32
|
const update = (newConfig: AgentWidgetConfig) => {
|
|
32
33
|
const launcher = newConfig.launcher ?? {};
|
|
33
34
|
const dockedMode = isDockedMountMode(newConfig);
|
|
35
|
+
const circle = resolveLauncherVariant(newConfig) === "circle";
|
|
36
|
+
if (circle) {
|
|
37
|
+
button.dataset.personaLauncherVariant = "circle";
|
|
38
|
+
button.setAttribute("aria-label", launcher.title ?? "Open chat");
|
|
39
|
+
} else {
|
|
40
|
+
button.removeAttribute("data-persona-launcher-variant");
|
|
41
|
+
button.removeAttribute("aria-label");
|
|
42
|
+
}
|
|
34
43
|
|
|
35
44
|
const titleEl = button.querySelector("[data-role='launcher-title']");
|
|
36
45
|
if (titleEl) {
|
|
@@ -49,7 +58,7 @@ export const createLauncherButton = (
|
|
|
49
58
|
// Hide/show text container
|
|
50
59
|
const textContainer = button.querySelector(".persona-flex-col");
|
|
51
60
|
if (textContainer) {
|
|
52
|
-
if (launcher.textHidden || dockedMode) {
|
|
61
|
+
if (launcher.textHidden || dockedMode || circle) {
|
|
53
62
|
(textContainer as HTMLElement).style.display = "none";
|
|
54
63
|
} else {
|
|
55
64
|
(textContainer as HTMLElement).style.display = "";
|
|
@@ -153,7 +162,7 @@ export const createLauncherButton = (
|
|
|
153
162
|
callToActionIconEl.style.padding = "";
|
|
154
163
|
}
|
|
155
164
|
|
|
156
|
-
if (launcher.callToActionIconHidden) {
|
|
165
|
+
if (launcher.callToActionIconHidden || circle) {
|
|
157
166
|
callToActionIconEl.style.display = "none";
|
|
158
167
|
} else {
|
|
159
168
|
callToActionIconEl.style.display = dockedMode ? "none" : "";
|
|
@@ -359,6 +359,20 @@ describe("createStandardBubble: components.message geometry tokens", () => {
|
|
|
359
359
|
});
|
|
360
360
|
});
|
|
361
361
|
|
|
362
|
+
it.each([false, true])("consumes selected default geometry with v5Defaults=%s", (v5Defaults) => {
|
|
363
|
+
for (const role of ["user", "assistant"] as const) {
|
|
364
|
+
const bubble = createStandardBubble(
|
|
365
|
+
makeMessage({ id: `default-${role}`, role, content: "Hi" }),
|
|
366
|
+
({ text }) => text, undefined, undefined, undefined,
|
|
367
|
+
{ widgetConfig: { future: { v5Defaults } } as AgentWidgetConfig }
|
|
368
|
+
);
|
|
369
|
+
expect(bubble.style.padding).toBe(v5Defaults
|
|
370
|
+
? `var(--persona-message-${role}-padding, 0.75rem 1.25rem)` : "");
|
|
371
|
+
expect(bubble.style.lineHeight).toBe(v5Defaults
|
|
372
|
+
? `var(--persona-message-${role}-line-height, 1.75)` : "");
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
|
|
362
376
|
it("routes each configured property through its role token with the bubble preset as fallback", () => {
|
|
363
377
|
const bubble = createStandardBubble(
|
|
364
378
|
makeMessage({ id: "geo-user", role: "user", content: "Hi" }),
|
|
@@ -896,6 +910,14 @@ describe("createMessageActions: chromeless action buttons", () => {
|
|
|
896
910
|
const actionButtons = (row: HTMLElement) =>
|
|
897
911
|
Array.from(row.querySelectorAll<HTMLButtonElement>("button[data-action]"));
|
|
898
912
|
|
|
913
|
+
it("keeps voting controls hidden unless explicitly enabled", () => {
|
|
914
|
+
const row = createMessageActions(makeMessage({ id: "actions-default-votes" }), {});
|
|
915
|
+
|
|
916
|
+
expect(actionButtons(row).map((btn) => btn.getAttribute("data-action"))).toEqual([
|
|
917
|
+
"copy",
|
|
918
|
+
]);
|
|
919
|
+
});
|
|
920
|
+
|
|
899
921
|
it("styles every built-in action through the message-action class alone", () => {
|
|
900
922
|
const row = createMessageActions(makeMessage({ id: "actions-builtin" }), {
|
|
901
923
|
showCopy: true,
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
StopReasonKind
|
|
19
19
|
} from "../types";
|
|
20
20
|
import type { MessageGeometryTokens } from "../types/theme";
|
|
21
|
+
import { resolveThemeDefaults } from "../utils/tokens";
|
|
21
22
|
import { createMentionTokenElement } from "../utils/mention-token";
|
|
22
23
|
import { createIconButton } from "../utils/buttons";
|
|
23
24
|
import { renderLucideIcon } from "../utils/icons";
|
|
@@ -820,8 +821,8 @@ export const createMessageActions = (
|
|
|
820
821
|
eligibility?: MessageActionEligibility
|
|
821
822
|
): HTMLElement => {
|
|
822
823
|
const showCopy = actionsConfig.showCopy ?? true;
|
|
823
|
-
const showUpvote = actionsConfig.showUpvote ??
|
|
824
|
-
const showDownvote = actionsConfig.showDownvote ??
|
|
824
|
+
const showUpvote = actionsConfig.showUpvote ?? false;
|
|
825
|
+
const showDownvote = actionsConfig.showDownvote ?? false;
|
|
825
826
|
const showReadAloud = actionsConfig.showReadAloud ?? false;
|
|
826
827
|
// Retry and edit are per-message: the config flag only opts in, the caller's
|
|
827
828
|
// eligibility decides which single message actually shows the control.
|
|
@@ -1002,12 +1003,15 @@ export const createStandardBubble = (
|
|
|
1002
1003
|
|
|
1003
1004
|
bubble.setAttribute("data-persona-theme-zone", message.role === "user" ? "user-message" : "assistant-message");
|
|
1004
1005
|
|
|
1006
|
+
const defaultMessageTokens = resolveThemeDefaults(options?.widgetConfig?.future).components.message;
|
|
1005
1007
|
const messageTokens = options?.widgetConfig?.theme?.components?.message;
|
|
1006
1008
|
applyBubbleGeometry(
|
|
1007
1009
|
bubble,
|
|
1008
1010
|
message.role,
|
|
1009
1011
|
layout,
|
|
1010
|
-
message.role === "user"
|
|
1012
|
+
message.role === "user"
|
|
1013
|
+
? { ...defaultMessageTokens.user, ...messageTokens?.user, ...options?.widgetConfig?.darkTheme?.components?.message?.user }
|
|
1014
|
+
: { ...defaultMessageTokens.assistant, ...messageTokens?.assistant, ...options?.widgetConfig?.darkTheme?.components?.message?.assistant }
|
|
1011
1015
|
);
|
|
1012
1016
|
|
|
1013
1017
|
// Apply component-level color overrides via CSS variables
|