@runtypelabs/persona 4.23.2 → 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-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 +215 -37
- package/dist/approval-ui.d.ts +215 -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 +210 -25
- package/dist/artifacts-ui.d.ts +210 -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 +210 -25
- package/dist/context-mentions.d.ts +210 -25
- package/dist/event-stream-view.d.cts +210 -25
- package/dist/event-stream-view.d.ts +210 -25
- package/dist/forms-ui.d.cts +245 -27
- package/dist/forms-ui.d.ts +245 -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 +270 -39
- package/dist/index.d.ts +270 -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 +210 -25
- package/dist/session-reconnect.d.ts +210 -25
- package/dist/smart-dom-reader.d.cts +210 -25
- package/dist/smart-dom-reader.d.ts +210 -25
- package/dist/theme-editor-preview.cjs +74 -73
- package/dist/theme-editor-preview.d.cts +217 -25
- package/dist/theme-editor-preview.d.ts +217 -25
- package/dist/theme-editor-preview.js +51 -50
- package/dist/theme-editor.cjs +10 -10
- package/dist/theme-editor.d.cts +230 -27
- package/dist/theme-editor.d.ts +230 -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/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 +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 +29 -6
- 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 +136 -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 +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/welcome.test.ts +23 -0
- package/src/welcome.ts +16 -9
- package/dist/approval-ui-K7R5Q5OC.js +0 -1
|
@@ -588,6 +588,13 @@ function appendSerializableObjectBlock(
|
|
|
588
588
|
lines.push(`${indent}},`);
|
|
589
589
|
}
|
|
590
590
|
|
|
591
|
+
/** Preserve an explicitly authored defaults-version opt-in in every snippet. */
|
|
592
|
+
function appendFutureDefaultsConfig(lines: string[], config: any, indent: string): void {
|
|
593
|
+
if (config.future?.v5Defaults !== undefined) {
|
|
594
|
+
lines.push(`${indent}future: { v5Defaults: ${config.future.v5Defaults === true} },`);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
591
598
|
/**
|
|
592
599
|
* Resolve the mount-target selector for a single-quoted JS string context.
|
|
593
600
|
* Defaults to `body` and escapes backslashes / single quotes so an arbitrary
|
|
@@ -647,6 +654,7 @@ function generateESMCode(config: any, options?: CodeGeneratorOptions): string {
|
|
|
647
654
|
if (config.target) lines.push(` target: "${config.target}",`);
|
|
648
655
|
if (config.flowId) lines.push(` flowId: "${config.flowId}",`);
|
|
649
656
|
if (shouldEmitParserType) lines.push(` parserType: "${parserType}",`);
|
|
657
|
+
appendFutureDefaultsConfig(lines, config, " ");
|
|
650
658
|
|
|
651
659
|
if (config.theme && typeof config.theme === "object" && Object.keys(config.theme).length > 0) {
|
|
652
660
|
appendSerializableObjectBlock(lines, "theme", config.theme as Record<string, unknown>, " ");
|
|
@@ -797,6 +805,7 @@ function generateReactComponentCode(config: any, options?: CodeGeneratorOptions)
|
|
|
797
805
|
if (config.target) lines.push(` target: "${config.target}",`);
|
|
798
806
|
if (config.flowId) lines.push(` flowId: "${config.flowId}",`);
|
|
799
807
|
if (shouldEmitParserType) lines.push(` parserType: "${parserType}",`);
|
|
808
|
+
appendFutureDefaultsConfig(lines, config, " ");
|
|
800
809
|
|
|
801
810
|
if (config.theme && typeof config.theme === "object" && Object.keys(config.theme).length > 0) {
|
|
802
811
|
appendSerializableObjectBlock(lines, "theme", config.theme as Record<string, unknown>, " ");
|
|
@@ -1064,6 +1073,7 @@ function generateReactAdvancedCode(config: any, options?: CodeGeneratorOptions):
|
|
|
1064
1073
|
if (config.agentId) lines.push(` agentId: "${config.agentId}",`);
|
|
1065
1074
|
if (config.target) lines.push(` target: "${config.target}",`);
|
|
1066
1075
|
if (config.flowId) lines.push(` flowId: "${config.flowId}",`);
|
|
1076
|
+
appendFutureDefaultsConfig(lines, config, " ");
|
|
1067
1077
|
|
|
1068
1078
|
if (config.theme && typeof config.theme === "object" && Object.keys(config.theme).length > 0) {
|
|
1069
1079
|
appendSerializableObjectBlock(lines, "theme", config.theme as Record<string, unknown>, " ");
|
|
@@ -1324,6 +1334,9 @@ function buildSerializableConfig(config: any): Record<string, any> {
|
|
|
1324
1334
|
if (config.target) serializableConfig.target = config.target;
|
|
1325
1335
|
if (config.flowId) serializableConfig.flowId = config.flowId;
|
|
1326
1336
|
if (shouldEmitParserType) serializableConfig.parserType = parserType;
|
|
1337
|
+
if (config.future?.v5Defaults !== undefined) {
|
|
1338
|
+
serializableConfig.future = { v5Defaults: config.future.v5Defaults === true };
|
|
1339
|
+
}
|
|
1327
1340
|
if (config.theme) serializableConfig.theme = config.theme;
|
|
1328
1341
|
if (config.launcher) serializableConfig.launcher = config.launcher;
|
|
1329
1342
|
if (config.copy) serializableConfig.copy = config.copy;
|
|
@@ -1477,6 +1490,7 @@ function generateScriptManualCode(config: any, options?: CodeGeneratorOptions):
|
|
|
1477
1490
|
if (config.target) lines.push(` target: "${config.target}",`);
|
|
1478
1491
|
if (config.flowId) lines.push(` flowId: "${config.flowId}",`);
|
|
1479
1492
|
if (shouldEmitParserType) lines.push(` parserType: "${parserType}",`);
|
|
1493
|
+
appendFutureDefaultsConfig(lines, config, " ");
|
|
1480
1494
|
|
|
1481
1495
|
if (config.theme && typeof config.theme === "object" && Object.keys(config.theme).length > 0) {
|
|
1482
1496
|
appendSerializableObjectBlock(lines, "theme", config.theme as Record<string, unknown>, " ");
|
|
@@ -13,7 +13,6 @@ const base: ComposerCompactInput = {
|
|
|
13
13
|
hasChips: false,
|
|
14
14
|
hasQuote: false,
|
|
15
15
|
hasPendingSubmission: false,
|
|
16
|
-
dictationActive: false,
|
|
17
16
|
};
|
|
18
17
|
|
|
19
18
|
describe("isComposerCompact", () => {
|
|
@@ -38,7 +37,6 @@ describe("isComposerCompact", () => {
|
|
|
38
37
|
["hasChips"],
|
|
39
38
|
["hasQuote"],
|
|
40
39
|
["hasPendingSubmission"],
|
|
41
|
-
["dictationActive"],
|
|
42
40
|
] as const)("%s expands an otherwise empty composer", (key) => {
|
|
43
41
|
expect(isComposerCompact({ ...base, [key]: true })).toBe(false);
|
|
44
42
|
});
|
|
@@ -18,13 +18,12 @@ export type ComposerCompactInput = {
|
|
|
18
18
|
hasChips: boolean;
|
|
19
19
|
hasQuote: boolean;
|
|
20
20
|
hasPendingSubmission: boolean;
|
|
21
|
-
dictationActive: boolean;
|
|
22
21
|
};
|
|
23
22
|
|
|
24
23
|
/**
|
|
25
24
|
* Compact when the draft is empty or a single line and nothing else occupies
|
|
26
25
|
* the composer. Text alone is not enough: any chip, attachment, quote, pending
|
|
27
|
-
* submission
|
|
26
|
+
* submission expands it. Recording state alone does not affect layout.
|
|
28
27
|
*/
|
|
29
28
|
export function isComposerCompact(input: ComposerCompactInput): boolean {
|
|
30
29
|
if (input.wrapped) return false;
|
|
@@ -33,8 +32,7 @@ export function isComposerCompact(input: ComposerCompactInput): boolean {
|
|
|
33
32
|
input.hasAttachments ||
|
|
34
33
|
input.hasChips ||
|
|
35
34
|
input.hasQuote ||
|
|
36
|
-
input.hasPendingSubmission
|
|
37
|
-
input.dictationActive
|
|
35
|
+
input.hasPendingSubmission
|
|
38
36
|
);
|
|
39
37
|
}
|
|
40
38
|
|
|
@@ -4,9 +4,8 @@ export const DEFAULT_ANCHOR_COMPOSER_TOP = "44%";
|
|
|
4
4
|
export const DEFAULT_COMPOSER_GAP = "24px";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
* its own geometry and always resolves `"block"`.
|
|
7
|
+
* V4 falls back to block; mergeWithDefaults supplies V5 overlay placement.
|
|
8
|
+
* Composer-bar mode owns its own geometry and always resolves `"block"`.
|
|
10
9
|
*/
|
|
11
10
|
export const resolveComposerPlacement = (
|
|
12
11
|
config: AgentWidgetConfig | undefined,
|
|
@@ -1,14 +1,49 @@
|
|
|
1
1
|
import { describe, expect, it, vi } from "vitest";
|
|
2
2
|
|
|
3
3
|
import { mergeConfigUpdate } from "./config-merge";
|
|
4
|
-
import { mergeWithDefaults } from "../defaults";
|
|
4
|
+
import { DEFAULT_FLOATING_LAUNCHER_WIDTH, mergeWithDefaults } from "../defaults";
|
|
5
|
+
import { getPanelAliasProvenance } from "./panel-config";
|
|
5
6
|
import type { AgentWidgetConfig, StreamAnimationPlugin } from "../types";
|
|
6
7
|
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
8
|
+
describe.each([false, true])("mergeConfigUpdate (v5=%s)", (v5Defaults) => {
|
|
9
|
+
// Stored controller config is post-mergeWithDefaults.
|
|
10
|
+
const base = (overrides: Partial<AgentWidgetConfig> = {}): AgentWidgetConfig =>
|
|
11
|
+
mergeWithDefaults({ apiUrl: "https://api.example.com/chat", future: { v5Defaults }, ...overrides }) as AgentWidgetConfig;
|
|
12
|
+
|
|
13
|
+
it("retains and clears legacy panel-alias provenance across live patches", () => {
|
|
14
|
+
const initial = base({ launcher: { width: "600px", dock: { width: "520px" } } });
|
|
15
|
+
expect(getPanelAliasProvenance(initial)).toMatchObject({ launcherWidth: true, dockWidth: true });
|
|
16
|
+
|
|
17
|
+
const retained = mergeConfigUpdate(initial, { launcher: { title: "Updated" } });
|
|
18
|
+
expect(getPanelAliasProvenance(retained)).toMatchObject({ launcherWidth: true, dockWidth: true });
|
|
19
|
+
|
|
20
|
+
const cleared = mergeConfigUpdate(retained, { launcher: { width: undefined, dock: { width: undefined } } });
|
|
21
|
+
expect(getPanelAliasProvenance(cleared)).toMatchObject({ launcherWidth: false, dockWidth: false });
|
|
22
|
+
|
|
23
|
+
const parentCleared = mergeConfigUpdate(initial, { launcher: undefined });
|
|
24
|
+
expect(getPanelAliasProvenance(parentCleared)).toMatchObject({ launcherWidth: false, dockWidth: false });
|
|
25
|
+
|
|
26
|
+
const dockParentCleared = mergeConfigUpdate(initial, { launcher: { dock: undefined } });
|
|
27
|
+
expect(getPanelAliasProvenance(dockParentCleared)).toMatchObject({ launcherWidth: true, dockWidth: false });
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it("does not treat an explicit undefined width as a legacy alias", () => {
|
|
31
|
+
const config = mergeWithDefaults({
|
|
32
|
+
future: { v5Defaults },
|
|
33
|
+
launcherWidth: "600px",
|
|
34
|
+
launcher: { width: undefined },
|
|
35
|
+
}) as AgentWidgetConfig;
|
|
36
|
+
expect(getPanelAliasProvenance(config)).toMatchObject({
|
|
37
|
+
launcherWidth: false,
|
|
38
|
+
legacyLauncherWidth: true,
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("keeps an explicit legacy width even when it equals the materialized default", () => {
|
|
43
|
+
const config = base({ launcher: { width: DEFAULT_FLOATING_LAUNCHER_WIDTH } });
|
|
44
|
+
expect(getPanelAliasProvenance(config).launcherWidth).toBe(true);
|
|
45
|
+
});
|
|
10
46
|
|
|
11
|
-
describe("mergeConfigUpdate", () => {
|
|
12
47
|
it("recursively merges nested plain objects, preserving sibling overrides", () => {
|
|
13
48
|
const prev = base({ launcher: { enabled: false, clearChat: { backgroundColor: "#123456" } } });
|
|
14
49
|
const next = mergeConfigUpdate(prev, { launcher: { title: "After" } });
|
|
@@ -304,3 +339,44 @@ describe("mergeConfigUpdate", () => {
|
|
|
304
339
|
expect(again).toEqual(merged);
|
|
305
340
|
});
|
|
306
341
|
});
|
|
342
|
+
|
|
343
|
+
|
|
344
|
+
describe("live defaults version changes", () => {
|
|
345
|
+
const base = (config: Partial<AgentWidgetConfig> = {}) =>
|
|
346
|
+
mergeWithDefaults({ apiUrl: "/api", ...config }) as AgentWidgetConfig;
|
|
347
|
+
|
|
348
|
+
it("moves inherited defaults in both directions and resets a cleared future flag", () => {
|
|
349
|
+
const v5 = mergeConfigUpdate(base(), { future: { v5Defaults: true } });
|
|
350
|
+
expect(v5.launcher?.headerIconSize).toBe("20px");
|
|
351
|
+
expect(v5.layout?.header?.showSubtitle).toBe(false);
|
|
352
|
+
expect(v5.composer?.layout).toBe("single-row");
|
|
353
|
+
expect(v5.statusIndicator?.mode).toBe("transient");
|
|
354
|
+
const v4 = mergeConfigUpdate(v5, { future: undefined });
|
|
355
|
+
expect(v4.launcher?.headerIconSize).toBe("40px");
|
|
356
|
+
expect(v4.layout?.header?.showSubtitle).toBe(true);
|
|
357
|
+
expect(v4.composer?.layout).toBeUndefined();
|
|
358
|
+
expect(v4.statusIndicator?.mode).toBeUndefined();
|
|
359
|
+
const v5Again = mergeConfigUpdate(v4, { future: { v5Defaults: true } });
|
|
360
|
+
expect(v5Again.layout?.messages?.assistant?.width).toBe("full");
|
|
361
|
+
expect(v5Again.composer?.layout).toBe("single-row");
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
it("retains host choices even when they equal a previous version default", () => {
|
|
365
|
+
const initial = base({ launcher: { headerIconSize: "40px" }, layout: { header: { showSubtitle: true } } });
|
|
366
|
+
const v5 = mergeConfigUpdate(initial, { future: { v5Defaults: true } });
|
|
367
|
+
expect(v5.launcher?.headerIconSize).toBe("40px");
|
|
368
|
+
expect(v5.layout?.header?.showSubtitle).toBe(true);
|
|
369
|
+
const reset = mergeConfigUpdate(v5, { launcher: { headerIconSize: undefined }, layout: { header: undefined } });
|
|
370
|
+
expect(reset.launcher?.headerIconSize).toBe("20px");
|
|
371
|
+
expect(reset.layout?.header?.showSubtitle).toBe(false);
|
|
372
|
+
expect(initial.launcher?.headerIconSize).toBe("40px");
|
|
373
|
+
});
|
|
374
|
+
|
|
375
|
+
it("preserves live overrides and normal config spreads across version changes", () => {
|
|
376
|
+
const edited = mergeConfigUpdate(base(), { composer: { layout: "stacked" }, statusIndicator: { mode: "always" } });
|
|
377
|
+
const v5 = mergeConfigUpdate({ ...edited }, { future: { v5Defaults: true } });
|
|
378
|
+
expect(v5.composer?.layout).toBe("stacked");
|
|
379
|
+
expect(v5.statusIndicator?.mode).toBe("always");
|
|
380
|
+
expect(v5.launcher?.headerIconSize).toBe("20px");
|
|
381
|
+
});
|
|
382
|
+
});
|
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import { mergeWithDefaults } from "../defaults";
|
|
1
|
+
import { DEFAULTS_V5, mergeWithDefaults } from "../defaults";
|
|
2
2
|
import type { AgentWidgetConfig, AgentWidgetConfigPatch } from "../types";
|
|
3
|
+
import { setPanelAliasProvenance, updatePanelAliasProvenance } from "./panel-config";
|
|
4
|
+
|
|
5
|
+
import { omitInheritedDefaults, setDefaultProvenance, updateDefaultProvenance, versionedDefaultPaths } from "./defaults-provenance";
|
|
3
6
|
|
|
4
7
|
// Same predicate as utils/deep-merge.ts: plain object, non-null, non-array.
|
|
5
8
|
// Class instances and DOM nodes also pass here, so replace-leaf paths guard the
|
|
@@ -63,6 +66,13 @@ export function mergeConfigUpdate(
|
|
|
63
66
|
previousConfig: AgentWidgetConfig,
|
|
64
67
|
patch: AgentWidgetConfigPatch
|
|
65
68
|
): AgentWidgetConfig {
|
|
66
|
-
const
|
|
67
|
-
|
|
69
|
+
const paths = versionedDefaultPaths(DEFAULTS_V5);
|
|
70
|
+
const explicit = updateDefaultProvenance(previousConfig, patch, paths);
|
|
71
|
+
const merged = setDefaultProvenance(omitInheritedDefaults(
|
|
72
|
+
mergePatch(previousConfig, patch, "") as Partial<AgentWidgetConfig>, paths, explicit
|
|
73
|
+
), explicit);
|
|
74
|
+
return setPanelAliasProvenance(
|
|
75
|
+
mergeWithDefaults(merged) as AgentWidgetConfig,
|
|
76
|
+
updatePanelAliasProvenance(previousConfig, patch)
|
|
77
|
+
);
|
|
68
78
|
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/** Tracks host intent for versioned defaults without comparing values to defaults. */
|
|
2
|
+
const DEFAULTS_PROVENANCE = Symbol("persona.defaultsProvenance");
|
|
3
|
+
type Provenance = { [DEFAULTS_PROVENANCE]?: readonly string[] };
|
|
4
|
+
const record = (value: unknown): value is Record<string, unknown> =>
|
|
5
|
+
typeof value === "object" && value !== null && !Array.isArray(value);
|
|
6
|
+
|
|
7
|
+
export function versionedDefaultPaths(overlay: object, prefix = ""): string[] {
|
|
8
|
+
return Object.entries(overlay).flatMap(([key, value]) => {
|
|
9
|
+
const path = prefix ? `${prefix}.${key}` : key;
|
|
10
|
+
return record(value) ? versionedDefaultPaths(value, path) : [path];
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function suppliedPath(value: unknown, path: string): "absent" | "clear" | "explicit" {
|
|
15
|
+
for (const key of path.split(".")) {
|
|
16
|
+
if (!record(value) || !Object.prototype.hasOwnProperty.call(value, key)) return "absent";
|
|
17
|
+
value = value[key];
|
|
18
|
+
if (value === undefined) return "clear";
|
|
19
|
+
}
|
|
20
|
+
return "explicit";
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function defaultProvenance(config: object | undefined, paths: readonly string[]): readonly string[] {
|
|
24
|
+
return (config as Provenance | undefined)?.[DEFAULTS_PROVENANCE]
|
|
25
|
+
?? paths.filter((path) => suppliedPath(config, path) === "explicit");
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function setDefaultProvenance<T extends object>(config: T, explicit: readonly string[]): T {
|
|
29
|
+
Object.defineProperty(config, DEFAULTS_PROVENANCE, {
|
|
30
|
+
value: explicit, enumerable: true, configurable: true,
|
|
31
|
+
});
|
|
32
|
+
return config;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function inheritDefaultProvenance<T extends object>(result: T, raw: object | undefined, overlay: object): T {
|
|
36
|
+
return setDefaultProvenance(result, defaultProvenance(raw, versionedDefaultPaths(overlay)));
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export function updateDefaultProvenance(previous: object, patch: object, paths: readonly string[]): readonly string[] {
|
|
40
|
+
const marked = (patch as Provenance)[DEFAULTS_PROVENANCE];
|
|
41
|
+
if (marked) return marked;
|
|
42
|
+
const explicit = new Set(defaultProvenance(previous, paths));
|
|
43
|
+
for (const path of paths) {
|
|
44
|
+
const supplied = suppliedPath(patch, path);
|
|
45
|
+
if (supplied === "clear") explicit.delete(path);
|
|
46
|
+
if (supplied === "explicit") explicit.add(path);
|
|
47
|
+
}
|
|
48
|
+
return [...explicit];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** Copy only traversed objects so deleting inherited defaults never mutates callers. */
|
|
52
|
+
export function omitInheritedDefaults<T extends object>(config: T, paths: readonly string[], explicit: readonly string[]): T {
|
|
53
|
+
const result = { ...config } as Record<string, unknown>;
|
|
54
|
+
for (const path of paths) {
|
|
55
|
+
if (explicit.includes(path)) continue;
|
|
56
|
+
const keys = path.split(".");
|
|
57
|
+
let target: Record<string, unknown> | undefined = result;
|
|
58
|
+
for (const key of keys.slice(0, -1)) {
|
|
59
|
+
const child: unknown = target[key];
|
|
60
|
+
if (!record(child)) { target = undefined; break; }
|
|
61
|
+
target[key] = { ...child };
|
|
62
|
+
target = target[key] as Record<string, unknown>;
|
|
63
|
+
}
|
|
64
|
+
if (target) delete target[keys[keys.length - 1]];
|
|
65
|
+
}
|
|
66
|
+
return result as T;
|
|
67
|
+
}
|
package/src/utils/dock.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import type { AgentWidgetConfig, AgentWidgetDockConfig } from "../types";
|
|
2
|
+
import { getPanelAliasProvenance } from "./panel-config";
|
|
3
|
+
import { getActiveTheme } from "./theme";
|
|
4
|
+
import { resolveTokenValue } from "./tokens";
|
|
2
5
|
|
|
3
6
|
const DEFAULT_DOCK_CONFIG: Required<AgentWidgetDockConfig> = {
|
|
4
7
|
side: "right",
|
|
@@ -25,9 +28,12 @@ export const resolveDockConfig = (
|
|
|
25
28
|
config?: AgentWidgetConfig
|
|
26
29
|
): Required<AgentWidgetDockConfig> => {
|
|
27
30
|
const dock = config?.launcher?.dock;
|
|
31
|
+
const width = getPanelAliasProvenance(config).dockWidth
|
|
32
|
+
? dock?.width
|
|
33
|
+
: resolveTokenValue(getActiveTheme(config), "components.panel.width");
|
|
28
34
|
return {
|
|
29
35
|
side: dock?.side ?? DEFAULT_DOCK_CONFIG.side,
|
|
30
|
-
width:
|
|
36
|
+
width: width ?? DEFAULT_DOCK_CONFIG.width,
|
|
31
37
|
animate: dock?.animate ?? DEFAULT_DOCK_CONFIG.animate,
|
|
32
38
|
reveal: dock?.reveal ?? DEFAULT_DOCK_CONFIG.reveal,
|
|
33
39
|
maxHeight: dock?.maxHeight ?? DEFAULT_DOCK_CONFIG.maxHeight,
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { AgentWidgetConfig } from "../types";
|
|
2
|
+
import type { LauncherTokens } from "../types/theme";
|
|
3
|
+
|
|
4
|
+
export const resolveLauncherVariant = (config?: Pick<AgentWidgetConfig, "launcher" | "future">): "pill" | "circle" =>
|
|
5
|
+
config?.launcher?.variant ?? (config?.future?.v5Defaults === true ? "circle" : "pill");
|
|
6
|
+
|
|
7
|
+
export const CIRCLE_LAUNCHER_TOKENS: LauncherTokens = {
|
|
8
|
+
background: "semantic.colors.primary",
|
|
9
|
+
foreground: "semantic.colors.textInverse",
|
|
10
|
+
border: "transparent",
|
|
11
|
+
size: "48px",
|
|
12
|
+
iconSize: "24px",
|
|
13
|
+
borderRadius: "palette.radius.full",
|
|
14
|
+
shadow: "0 8px 24px rgba(0,0,0,.16)",
|
|
15
|
+
offset: "20px",
|
|
16
|
+
};
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import type { AgentWidgetConfig, AgentWidgetConfigPatch } from "../types";
|
|
2
|
+
|
|
3
|
+
export const PANEL_WIDTH_VAR = "var(--persona-components-panel-width)";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Provenance for legacy panel geometry aliases. Config merging materializes
|
|
7
|
+
* launcher defaults, so values alone cannot distinguish a caller's alias from
|
|
8
|
+
* an inherited v4 default. The enumerable symbol survives object spreads while
|
|
9
|
+
* remaining absent from JSON, generated snippets, and the public config shape.
|
|
10
|
+
*/
|
|
11
|
+
export const PANEL_ALIAS_PROVENANCE = Symbol("persona.panelAliasProvenance");
|
|
12
|
+
|
|
13
|
+
export type PanelAliasProvenance = {
|
|
14
|
+
launcherWidth: boolean;
|
|
15
|
+
legacyLauncherWidth: boolean;
|
|
16
|
+
sidebarWidth: boolean;
|
|
17
|
+
dockWidth: boolean;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
type WithPanelAliasProvenance = {
|
|
21
|
+
[PANEL_ALIAS_PROVENANCE]?: PanelAliasProvenance;
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const hasOwn = (value: object | undefined, key: string): boolean =>
|
|
25
|
+
value != null && Object.prototype.hasOwnProperty.call(value, key);
|
|
26
|
+
|
|
27
|
+
const hasOwnDefined = (value: object | undefined, key: string): boolean =>
|
|
28
|
+
hasOwn(value, key) && (value as Record<string, unknown>)[key] !== undefined;
|
|
29
|
+
|
|
30
|
+
const fromRawConfig = (
|
|
31
|
+
config?: Partial<AgentWidgetConfig>
|
|
32
|
+
): PanelAliasProvenance => ({
|
|
33
|
+
launcherWidth: hasOwnDefined(config?.launcher, "width"),
|
|
34
|
+
legacyLauncherWidth: hasOwnDefined(config, "launcherWidth"),
|
|
35
|
+
sidebarWidth: hasOwnDefined(config?.launcher, "sidebarWidth"),
|
|
36
|
+
dockWidth: hasOwnDefined(config?.launcher?.dock, "width"),
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
export const getPanelAliasProvenance = (
|
|
40
|
+
config?: Partial<AgentWidgetConfig>
|
|
41
|
+
): PanelAliasProvenance => {
|
|
42
|
+
const stored = (config as WithPanelAliasProvenance | undefined)?.[PANEL_ALIAS_PROVENANCE];
|
|
43
|
+
return stored ? { ...stored } : fromRawConfig(config);
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
export const setPanelAliasProvenance = <T extends object>(
|
|
47
|
+
config: T,
|
|
48
|
+
provenance: PanelAliasProvenance
|
|
49
|
+
): T => {
|
|
50
|
+
Object.defineProperty(config, PANEL_ALIAS_PROVENANCE, {
|
|
51
|
+
value: provenance,
|
|
52
|
+
enumerable: true,
|
|
53
|
+
configurable: true,
|
|
54
|
+
});
|
|
55
|
+
return config;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
/** Stamp a defaulted config with aliases supplied by the raw input. */
|
|
59
|
+
export const inheritPanelAliasProvenance = <T extends object>(
|
|
60
|
+
result: T,
|
|
61
|
+
rawConfig?: Partial<AgentWidgetConfig>
|
|
62
|
+
): T => setPanelAliasProvenance(result, getPanelAliasProvenance(rawConfig));
|
|
63
|
+
|
|
64
|
+
/** Apply own alias keys in a live patch, including explicit-undefined resets. */
|
|
65
|
+
export const updatePanelAliasProvenance = (
|
|
66
|
+
previous: Partial<AgentWidgetConfig>,
|
|
67
|
+
patch: AgentWidgetConfigPatch
|
|
68
|
+
): PanelAliasProvenance => {
|
|
69
|
+
// A config spread remains marked. Treat it as an already-normalized full
|
|
70
|
+
// config rather than mistaking its materialized launcher defaults for a
|
|
71
|
+
// patch that explicitly supplied every alias.
|
|
72
|
+
const patchProvenance = (patch as WithPanelAliasProvenance)[PANEL_ALIAS_PROVENANCE];
|
|
73
|
+
if (patchProvenance) return { ...patchProvenance };
|
|
74
|
+
|
|
75
|
+
const next = getPanelAliasProvenance(previous);
|
|
76
|
+
if (hasOwn(patch, "launcherWidth")) {
|
|
77
|
+
next.legacyLauncherWidth = patch.launcherWidth !== undefined;
|
|
78
|
+
}
|
|
79
|
+
if (hasOwn(patch, "launcher") && patch.launcher === undefined) {
|
|
80
|
+
next.launcherWidth = false;
|
|
81
|
+
next.sidebarWidth = false;
|
|
82
|
+
next.dockWidth = false;
|
|
83
|
+
return next;
|
|
84
|
+
}
|
|
85
|
+
if (hasOwn(patch.launcher, "width")) {
|
|
86
|
+
next.launcherWidth = patch.launcher?.width !== undefined;
|
|
87
|
+
}
|
|
88
|
+
if (hasOwn(patch.launcher, "sidebarWidth")) {
|
|
89
|
+
next.sidebarWidth = patch.launcher?.sidebarWidth !== undefined;
|
|
90
|
+
}
|
|
91
|
+
if (hasOwn(patch.launcher, "dock") && patch.launcher?.dock === undefined) {
|
|
92
|
+
next.dockWidth = false;
|
|
93
|
+
} else if (hasOwn(patch.launcher?.dock, "width")) {
|
|
94
|
+
next.dockWidth = patch.launcher?.dock?.width !== undefined;
|
|
95
|
+
}
|
|
96
|
+
return next;
|
|
97
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { AgentWidgetStatusIndicatorConfig } from "../types";
|
|
2
|
+
|
|
3
|
+
/** Transient connection chrome excludes normal idle and streaming states. */
|
|
4
|
+
export const isStatusIndicatorVisible = (
|
|
5
|
+
config: AgentWidgetStatusIndicatorConfig,
|
|
6
|
+
status: string,
|
|
7
|
+
): boolean => config.visible !== false && (
|
|
8
|
+
config.mode !== "transient" ||
|
|
9
|
+
status === "connecting" || status === "paused" ||
|
|
10
|
+
status === "resuming" || status === "error"
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
export const applyStatusIndicatorState = (
|
|
14
|
+
element: HTMLElement,
|
|
15
|
+
config: AgentWidgetStatusIndicatorConfig,
|
|
16
|
+
status: string,
|
|
17
|
+
): void => {
|
|
18
|
+
const transient = config.mode === "transient";
|
|
19
|
+
const hasNotice = element.hasAttribute("data-persona-composer-reason") ||
|
|
20
|
+
element.hasAttribute("data-persona-composer-notice");
|
|
21
|
+
element.style.display = isStatusIndicatorVisible(config, status) ||
|
|
22
|
+
(transient && hasNotice && config.visible !== false) ? "" : "none";
|
|
23
|
+
element.classList.toggle("persona-mt-2", !transient);
|
|
24
|
+
element.classList.toggle("persona-mb-2", transient);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
/** Move only sibling regions, leaving a plugin's internal structure intact. */
|
|
28
|
+
export const placeStatusIndicator = (
|
|
29
|
+
element: HTMLElement,
|
|
30
|
+
form: HTMLElement,
|
|
31
|
+
config: AgentWidgetStatusIndicatorConfig,
|
|
32
|
+
): void => {
|
|
33
|
+
if (!form.parentElement || element.parentElement !== form.parentElement) return;
|
|
34
|
+
if (config.mode === "transient") {
|
|
35
|
+
if (element.nextElementSibling !== form) form.before(element);
|
|
36
|
+
} else if (form.nextElementSibling !== element) {
|
|
37
|
+
form.after(element);
|
|
38
|
+
}
|
|
39
|
+
};
|