@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.
Files changed (158) hide show
  1. package/dist/animations/glyph-cycle.d.cts +1 -1
  2. package/dist/animations/glyph-cycle.d.ts +1 -1
  3. package/dist/animations/{types-9Vxm-PPz.d.cts → types-Db61aI9J.d.cts} +25 -2
  4. package/dist/animations/{types-9Vxm-PPz.d.ts → types-Db61aI9J.d.ts} +25 -2
  5. package/dist/animations/wipe.d.cts +1 -1
  6. package/dist/animations/wipe.d.ts +1 -1
  7. package/dist/animations-extra.d.cts +25 -2
  8. package/dist/animations-extra.d.ts +25 -2
  9. package/dist/approval-ui-3NMYNXBD.js +1 -0
  10. package/dist/approval-ui.cjs +2 -2
  11. package/dist/approval-ui.d.cts +261 -40
  12. package/dist/approval-ui.d.ts +261 -40
  13. package/dist/approval-ui.js +2 -2
  14. package/dist/{artifacts-ui-F4N6VMKF.js → artifacts-ui-IGB5YKLO.js} +4 -4
  15. package/dist/artifacts-ui.d.cts +256 -28
  16. package/dist/artifacts-ui.d.ts +256 -28
  17. package/dist/{chunk-NIKYKSNZ.js → chunk-7CO4T5KB.js} +1 -1
  18. package/dist/codegen.cjs +11 -11
  19. package/dist/codegen.js +13 -13
  20. package/dist/context-mentions.d.cts +256 -28
  21. package/dist/context-mentions.d.ts +256 -28
  22. package/dist/event-stream-view.d.cts +256 -28
  23. package/dist/event-stream-view.d.ts +256 -28
  24. package/dist/forms-ui.d.cts +291 -30
  25. package/dist/forms-ui.d.ts +291 -30
  26. package/dist/history-view.d.cts +25 -2
  27. package/dist/history-view.d.ts +25 -2
  28. package/dist/index.cjs +70 -69
  29. package/dist/index.cjs.map +1 -1
  30. package/dist/index.d.cts +337 -51
  31. package/dist/index.d.ts +337 -51
  32. package/dist/index.global.js +57 -54
  33. package/dist/index.global.js.map +1 -1
  34. package/dist/index.js +70 -69
  35. package/dist/index.js.map +1 -1
  36. package/dist/install.global.js +1 -1
  37. package/dist/install.global.js.map +1 -1
  38. package/dist/launcher.global.js +2 -2
  39. package/dist/launcher.global.js.map +1 -1
  40. package/dist/session-reconnect.d.cts +256 -28
  41. package/dist/session-reconnect.d.ts +256 -28
  42. package/dist/smart-dom-reader.d.cts +256 -28
  43. package/dist/smart-dom-reader.d.ts +256 -28
  44. package/dist/theme-editor-preview.cjs +74 -73
  45. package/dist/theme-editor-preview.d.cts +263 -28
  46. package/dist/theme-editor-preview.d.ts +263 -28
  47. package/dist/theme-editor-preview.js +51 -50
  48. package/dist/theme-editor.cjs +10 -10
  49. package/dist/theme-editor.d.cts +276 -30
  50. package/dist/theme-editor.d.ts +276 -30
  51. package/dist/theme-editor.js +10 -10
  52. package/dist/theme-reference.cjs +1 -1
  53. package/dist/theme-reference.d.cts +1522 -11
  54. package/dist/theme-reference.d.ts +1522 -11
  55. package/dist/theme-reference.js +1 -1
  56. package/dist/widget.css +1 -1
  57. package/package.json +3 -2
  58. package/src/__fixtures__/defaults-parity.v4.json +776 -0
  59. package/src/approval-ui-entry.ts +0 -1
  60. package/src/approval-ui-loader.ts +5 -4
  61. package/src/approval-ui.ts +0 -1
  62. package/src/client-identity-proof.test.ts +193 -0
  63. package/src/client-session-warm.test.ts +216 -0
  64. package/src/client-visitor-history.test.ts +6 -0
  65. package/src/client.tool-pair-replay.test.ts +325 -0
  66. package/src/client.ts +154 -61
  67. package/src/codegen.test.ts +24 -0
  68. package/src/components/activity-row.test.ts +100 -0
  69. package/src/components/activity-row.ts +136 -0
  70. package/src/components/approval-actions.test.ts +8 -5
  71. package/src/components/approval-actions.ts +19 -6
  72. package/src/components/approval-bubble.test.ts +56 -9
  73. package/src/components/approval-bubble.ts +36 -41
  74. package/src/components/composer-builder.ts +5 -2
  75. package/src/components/composer-parts.ts +10 -2
  76. package/src/components/header-builder.test.ts +32 -0
  77. package/src/components/header-builder.ts +7 -4
  78. package/src/components/header-layouts.test.ts +12 -0
  79. package/src/components/header-layouts.ts +3 -2
  80. package/src/components/launcher.ts +11 -2
  81. package/src/components/message-bubble.test.ts +22 -0
  82. package/src/components/message-bubble.ts +7 -3
  83. package/src/components/panel.test.ts +52 -1
  84. package/src/components/panel.ts +47 -8
  85. package/src/components/pill-composer-builder.test.ts +2 -0
  86. package/src/components/pill-composer-builder.ts +0 -1
  87. package/src/components/reasoning-bubble.ts +29 -17
  88. package/src/components/suggestions.test.ts +1 -0
  89. package/src/components/suggestions.ts +0 -16
  90. package/src/components/tool-bubble.ts +131 -114
  91. package/src/components/tool-details.test.ts +87 -0
  92. package/src/components/tool-details.ts +102 -0
  93. package/src/components/welcome.test.ts +8 -0
  94. package/src/components/welcome.ts +4 -2
  95. package/src/defaults-parity.test.ts +158 -0
  96. package/src/defaults.test.ts +69 -0
  97. package/src/defaults.ts +128 -49
  98. package/src/generated/runtype-openapi-contract.ts +28 -13
  99. package/src/index-core.ts +2 -0
  100. package/src/install.test.ts +24 -0
  101. package/src/install.ts +3 -1
  102. package/src/launcher-global.ts +2 -1
  103. package/src/launcher-variants.test.ts +52 -0
  104. package/src/runtime/init-update-reset.test.ts +21 -0
  105. package/src/session.ts +61 -9
  106. package/src/styles/widget-styles.test.ts +19 -0
  107. package/src/styles/widget.css +295 -23
  108. package/src/theme-editor/index.ts +2 -0
  109. package/src/theme-editor/preview-utils.test.ts +8 -0
  110. package/src/theme-editor/preview-utils.ts +7 -3
  111. package/src/theme-editor/sections.test.ts +31 -0
  112. package/src/theme-editor/sections.ts +55 -1
  113. package/src/theme-editor/state-preferences.test.ts +49 -0
  114. package/src/theme-editor/state.ts +73 -15
  115. package/src/theme-reference.test.ts +20 -0
  116. package/src/theme-reference.ts +11 -5
  117. package/src/tool-call-display-defaults.test.ts +33 -1
  118. package/src/types/theme.ts +80 -3
  119. package/src/types.ts +179 -23
  120. package/src/ui.activity-row.test.ts +91 -0
  121. package/src/ui.approval-plugin.test.ts +63 -0
  122. package/src/ui.composer-compact.test.ts +24 -2
  123. package/src/ui.composer-placement.test.ts +23 -0
  124. package/src/ui.detached-panel.test.ts +33 -6
  125. package/src/ui.docked.test.ts +19 -0
  126. package/src/ui.header-update-stability.test.ts +25 -0
  127. package/src/ui.identity-proof.test.ts +58 -0
  128. package/src/ui.launcher-teaser.test.ts +25 -0
  129. package/src/ui.message-width.test.ts +17 -2
  130. package/src/ui.reasoning-display.test.ts +92 -0
  131. package/src/ui.rebuild-composer.test.ts +20 -3
  132. package/src/ui.scroll.test.ts +32 -0
  133. package/src/ui.session-init.test.ts +312 -0
  134. package/src/ui.status-indicator.test.ts +82 -0
  135. package/src/ui.suggest-replies.test.ts +11 -0
  136. package/src/ui.ts +363 -74
  137. package/src/ui.v5-defaults.test.ts +144 -0
  138. package/src/ui.welcome.test.ts +92 -3
  139. package/src/utils/__snapshots__/tokens-parity.test.ts.snap +811 -140
  140. package/src/utils/code-generators.ts +14 -0
  141. package/src/utils/composer-compact.test.ts +0 -2
  142. package/src/utils/composer-compact.ts +2 -4
  143. package/src/utils/composer-placement.ts +2 -3
  144. package/src/utils/config-merge.test.ts +81 -5
  145. package/src/utils/config-merge.ts +13 -3
  146. package/src/utils/defaults-provenance.ts +67 -0
  147. package/src/utils/dock.ts +7 -1
  148. package/src/utils/launcher-variant.ts +16 -0
  149. package/src/utils/panel-config.ts +97 -0
  150. package/src/utils/status-indicator.ts +39 -0
  151. package/src/utils/theme.test.ts +182 -0
  152. package/src/utils/theme.ts +131 -18
  153. package/src/utils/tokens.test.ts +122 -1
  154. package/src/utils/tokens.ts +177 -17
  155. package/src/utils/tool-pair-replay.ts +100 -0
  156. package/src/welcome.test.ts +23 -0
  157. package/src/welcome.ts +16 -9
  158. package/dist/approval-ui-K7R5Q5OC.js +0 -1
@@ -0,0 +1,144 @@
1
+ // @vitest-environment jsdom
2
+ import { afterEach, describe, expect, it, vi } from "vitest";
3
+ import { createAgentExperience } from "./ui";
4
+
5
+ const controllers: ReturnType<typeof createAgentExperience>[] = [];
6
+ afterEach(() => {
7
+ controllers.splice(0).forEach((controller) => controller.destroy());
8
+ document.body.replaceChildren();
9
+ vi.restoreAllMocks();
10
+ });
11
+
12
+ describe.each([false, true])("core defaults lifecycle (v5=%s)", (v5Defaults) => {
13
+ it("applies a live defaults switch to existing messages and header", () => {
14
+ window.scrollTo = vi.fn();
15
+ const mount = document.createElement("div");
16
+ document.body.append(mount);
17
+ const controller = createAgentExperience(mount, {
18
+ apiUrl: "/dispatch", persistState: false,
19
+ future: { v5Defaults }, launcher: { autoExpand: true },
20
+ });
21
+ controllers.push(controller);
22
+ controller.injectUserMessage({ content: "Hello" });
23
+ controller.injectAssistantMessage({ content: "Welcome" });
24
+ const assertState = (v5: boolean) => {
25
+ expect(mount.dataset.personaDefaults).toBe(v5 ? "v5" : "v4");
26
+ const header = mount.querySelector<HTMLElement>(".persona-widget-header")!;
27
+ expect((header.firstElementChild as HTMLElement).style.width).toBe(v5 ? "20px" : "40px");
28
+ expect(mount.style.getPropertyValue("--persona-components-header-padding"))
29
+ .toBe(v5 ? "8px 8px 8px 16px" : "20px 24px");
30
+ const bubble = mount.querySelector<HTMLElement>(".persona-message-row-assistant .persona-message-bubble")!;
31
+ expect(bubble.style.lineHeight).toBe(v5 ? "var(--persona-message-assistant-line-height, 1.75)" : "");
32
+ const status = mount.querySelector<HTMLElement>("[data-persona-composer-status]")!;
33
+ expect(status.style.display).toBe(v5 ? "none" : "");
34
+ };
35
+ assertState(v5Defaults);
36
+ controller.update({ future: { v5Defaults: !v5Defaults } });
37
+ assertState(!v5Defaults);
38
+ controller.update({ future: { v5Defaults } });
39
+ assertState(v5Defaults);
40
+ });
41
+
42
+ it("exposes composer chrome and honors explicit glyph overrides", () => {
43
+ window.scrollTo = vi.fn();
44
+ const mount = document.createElement("div");
45
+ document.body.append(mount);
46
+ const controller = createAgentExperience(mount, {
47
+ apiUrl: "/dispatch", persistState: false, future: { v5Defaults },
48
+ launcher: { autoExpand: true },
49
+ });
50
+ controllers.push(controller);
51
+ expect(mount.style.getPropertyValue("--persona-components-composer-sendIconSize"))
52
+ .toBe(v5Defaults ? "18px" : "");
53
+ expect(mount.style.getPropertyValue("--persona-components-composer-sendButtonRadius"))
54
+ .toBe(v5Defaults ? "9999px" : "");
55
+ expect(mount.style.getPropertyValue("--persona-components-composer-footerBorder"))
56
+ .toBe(v5Defaults ? "none" : "");
57
+ controller.update({
58
+ sendButton: { iconName: "send", iconSize: "15px" },
59
+ theme: { components: { composer: {
60
+ sendIconSize: "22px", sendButtonRadius: "8px", footerBorder: "2px solid red",
61
+ } } },
62
+ });
63
+ const svg = mount.querySelector<SVGElement>("[data-persona-composer-submit] svg")!;
64
+ expect(svg.getAttribute("width")).toBe("15");
65
+ expect(svg.style.width).toBe("");
66
+ expect(mount.style.getPropertyValue("--persona-components-composer-sendButtonRadius")).toBe("8px");
67
+ expect(mount.style.getPropertyValue("--persona-components-composer-footerBorder")).toBe("2px solid red");
68
+ });
69
+
70
+ it("switches composer focus defaults live and retains explicit focus and resting chrome", () => {
71
+ window.scrollTo = vi.fn();
72
+ const mount = document.createElement("div");
73
+ document.body.append(mount);
74
+ const controller = createAgentExperience(mount, {
75
+ apiUrl: "/dispatch", persistState: false, future: { v5Defaults },
76
+ launcher: { autoExpand: true },
77
+ theme: { components: { composer: { borderColor: "#123456", shadow: "0 3px 8px #123456" } } },
78
+ });
79
+ controllers.push(controller);
80
+ const assertDefaults = (v5: boolean) => {
81
+ expect(mount.style.getPropertyValue("--persona-components-composer-focusBorderColor"))
82
+ .toBe(v5 ? "color-mix(in srgb, var(--persona-text) 28%, var(--persona-border))" : "");
83
+ expect(mount.style.getPropertyValue("--persona-components-composer-focusRing"))
84
+ .toBe(v5 ? "1px solid color-mix(in srgb, var(--persona-text) 12%, transparent)" : "");
85
+ expect(mount.style.getPropertyValue("--persona-composer-border-color")).toBe("#123456");
86
+ expect(mount.style.getPropertyValue("--persona-composer-shadow")).toBe("0 3px 8px #123456");
87
+ // No inline outline may suppress the stylesheet's ring.
88
+ expect(mount.querySelector<HTMLElement>("[data-persona-composer-form]")!.style.outline).toBe("");
89
+ };
90
+ assertDefaults(v5Defaults);
91
+ controller.update({ future: { v5Defaults: !v5Defaults } });
92
+ assertDefaults(!v5Defaults);
93
+ controller.update({ theme: { components: { composer: {
94
+ focusBorderColor: "#abcdef", focusRing: "2px solid #fedcba",
95
+ } } } });
96
+ for (const state of [v5Defaults, !v5Defaults]) {
97
+ controller.update({ future: { v5Defaults: state } });
98
+ expect(mount.style.getPropertyValue("--persona-components-composer-focusBorderColor")).toBe("#abcdef");
99
+ expect(mount.style.getPropertyValue("--persona-components-composer-focusRing")).toBe("2px solid #fedcba");
100
+ }
101
+ controller.update({ theme: { components: { composer: { focusRing: "none" } } } });
102
+ expect(mount.style.getPropertyValue("--persona-components-composer-focusRing")).toBe("none");
103
+ });
104
+
105
+ it("switches default placement and fade while retaining explicit choices", () => {
106
+ window.scrollTo = vi.fn();
107
+ const mount = document.createElement("div");
108
+ document.body.append(mount);
109
+ const controller = createAgentExperience(mount, {
110
+ apiUrl: "/dispatch", persistState: false, future: { v5Defaults },
111
+ launcher: { autoExpand: true },
112
+ });
113
+ controllers.push(controller);
114
+ for (const state of [v5Defaults, !v5Defaults]) {
115
+ controller.update({ future: { v5Defaults: state } });
116
+ expect(mount.dataset.personaComposerPlacement).toBe(state ? "overlay" : "block");
117
+ expect(mount.style.getPropertyValue("--persona-composer-overlay-band"))
118
+ .toBe(state ? "linear-gradient(to bottom, transparent, var(--persona-container) 24px)" : "transparent");
119
+ }
120
+ controller.update({ composer: { placement: "block" }, theme: { components: { composer: { overlayBand: "transparent" } } } });
121
+ controller.update({ future: { v5Defaults: !v5Defaults } });
122
+ expect(mount.dataset.personaComposerPlacement).toBe("block");
123
+ expect(mount.style.getPropertyValue("--persona-composer-overlay-band")).toBe("transparent");
124
+ });
125
+
126
+ it("uses fullscreen turn spacing in full-height embeds and honors explicit spacing", () => {
127
+ window.scrollTo = vi.fn();
128
+ const mount = document.createElement("div");
129
+ document.body.append(mount);
130
+ const controller = createAgentExperience(mount, {
131
+ apiUrl: "/dispatch", persistState: false, future: { v5Defaults },
132
+ launcher: { enabled: false, fullHeight: true },
133
+ });
134
+ controllers.push(controller);
135
+ const messages = mount.querySelector<HTMLElement>(".persona-widget-messages")!;
136
+ expect(messages.style.gap).toContain("--persona-components-message-fullscreenGap");
137
+ expect(mount.style.getPropertyValue("--persona-components-message-fullscreenGap"))
138
+ .toBe(v5Defaults ? "28px" : "");
139
+ controller.update({ theme: { components: { message: { gap: "33px" } } } });
140
+ expect(mount.style.getPropertyValue("--persona-components-message-fullscreenGap")).toBe("33px");
141
+ controller.update({ theme: { components: { message: { fullscreenGap: "42px" } } } });
142
+ expect(mount.style.getPropertyValue("--persona-components-message-fullscreenGap")).toBe("42px");
143
+ });
144
+ });
@@ -328,8 +328,8 @@ describe("welcome visibility state machine", () => {
328
328
  });
329
329
 
330
330
  describe("welcome greeting bubble", () => {
331
- it("renders an assistant-styled bubble above the transcript", () => {
332
- const { mount } = makeController({ welcome: { message: "Hi, I'm Ada." } });
331
+ it.each([false, true])("renders an assistant-styled bubble above the transcript (v5=%s)", (v5Defaults) => {
332
+ const { mount } = makeController({ future: { v5Defaults }, welcome: { message: "Hi, I'm Ada." } });
333
333
  const greeting = greetingHost(mount);
334
334
  expect(isVisible(greeting)).toBe(true);
335
335
  expect(greeting.textContent).toBe("Hi, I'm Ada.");
@@ -342,7 +342,7 @@ describe("welcome greeting bubble", () => {
342
342
  const body = mount.querySelector<HTMLElement>("#persona-scroll-container")!;
343
343
  const children = Array.from(body.children);
344
344
  const transcript = body.querySelector<HTMLElement>(
345
- ":scope > .persona-gap-3"
345
+ ":scope > .persona-widget-messages"
346
346
  )!;
347
347
  expect(children.indexOf(greeting)).toBeLessThan(
348
348
  children.indexOf(transcript)
@@ -508,3 +508,92 @@ describe("welcome live updates", () => {
508
508
  expect(isVisible(welcomeHost(mount))).toBe(true);
509
509
  });
510
510
  });
511
+
512
+ describe("centered welcome", () => {
513
+ it("groups legacy starters in the V5 welcome and restores them after clear", () => {
514
+ const { mount, controller } = makeController({ future: { v5Defaults: true }, suggestionChips: ["One", "Two", "Three", "Four", "Five"] });
515
+ const host = welcomeHost(mount);
516
+ expect(host.getAttribute("data-persona-welcome-layout")).toBe("centered");
517
+ expect(host.querySelector("h2")?.textContent).toBe("What can I help with?");
518
+ expect(host.querySelectorAll(".persona-suggestion")).toHaveLength(3);
519
+ expect(host.querySelector(".persona-suggestions")?.getAttribute("data-variant")).toBe("chip");
520
+ controller.injectTestMessage({ type: "message", message: assistantMessage() });
521
+ expect(isVisible(host)).toBe(true);
522
+ sendUserMessage(controller);
523
+ expect(isVisible(host)).toBe(false);
524
+ controller.clearChat();
525
+ expect(isVisible(host)).toBe(true);
526
+ expect(host.querySelectorAll(".persona-suggestion")).toHaveLength(3);
527
+ });
528
+
529
+ it("raises fullscreen empty composers and defaults starters to four cards", () => {
530
+ const { mount } = makeController({ future: { v5Defaults: true }, launcher: { enabled: false, fullHeight: true }, suggestionChips: ["One", "Two", "Three", "Four", "Five"] });
531
+ expect(mount.getAttribute("data-persona-welcome-anchor")).toBe("center");
532
+ expect(welcomeHost(mount).querySelectorAll(".persona-suggestion")).toHaveLength(4);
533
+ expect(welcomeHost(mount).querySelector(".persona-suggestions")?.getAttribute("data-variant")).toBe("card");
534
+ });
535
+ });
536
+
537
+ it("updates starter density when the floating panel becomes mobile fullscreen", () => {
538
+ const originalWidth = window.innerWidth;
539
+ try {
540
+ Object.defineProperty(window, "innerWidth", { configurable: true, value: 1000 });
541
+ const { mount } = makeController({ future: { v5Defaults: true }, launcher: { enabled: true }, suggestionChips: ["One", "Two", "Three", "Four", "Five"] });
542
+ expect(welcomeHost(mount).querySelectorAll(".persona-suggestion")).toHaveLength(3);
543
+ Object.defineProperty(window, "innerWidth", { configurable: true, value: 500 });
544
+ window.dispatchEvent(new Event("resize"));
545
+ expect(mount.getAttribute("data-persona-welcome-anchor")).toBe("center");
546
+ expect(welcomeHost(mount).querySelectorAll(".persona-suggestion")).toHaveLength(4);
547
+ Object.defineProperty(window, "innerWidth", { configurable: true, value: 1000 });
548
+ window.dispatchEvent(new Event("resize"));
549
+ expect(mount.getAttribute("data-persona-welcome-anchor")).toBe("bottom");
550
+ expect(welcomeHost(mount).querySelectorAll(".persona-suggestion")).toHaveLength(3);
551
+ } finally {
552
+ Object.defineProperty(window, "innerWidth", { configurable: true, value: originalWidth });
553
+ }
554
+ });
555
+
556
+ describe.each([false, true])("explicit welcome layout (V5 defaults: %s)", (v5Defaults) => {
557
+ it("makes top override hero centering in empty and active states", () => {
558
+ const { mount, controller } = makeController({ future: { v5Defaults }, welcome: { layout: "top", variant: "hero", dismiss: "never" } });
559
+ expect(mount.getAttribute("data-persona-welcome-layout-explicit")).toBe("top");
560
+ expect(isVisible(welcomeHost(mount))).toBe(true);
561
+ sendUserMessage(controller);
562
+ expect(mount.getAttribute("data-persona-welcome-layout-explicit")).toBe("top");
563
+ expect(isVisible(welcomeHost(mount))).toBe(true);
564
+ });
565
+
566
+ it("centers and resets explicit greeting/starters while honoring overrides", () => {
567
+ const { mount, controller } = makeController({
568
+ future: { v5Defaults },
569
+ welcome: { layout: "centered", title: "Custom", subtitle: "Scope", dismiss: "on-first-message", align: "start", anchor: "bottom" },
570
+ suggestions: { starters: { items: ["One", "Two", "Three", "Four", "Five"], maxItems: 2, variant: "list", behavior: "fill" } },
571
+ });
572
+ expect(welcomeHost(mount).getAttribute("data-persona-welcome-layout")).toBe("centered");
573
+ expect(welcomeHost(mount).getAttribute("data-persona-welcome-align")).toBe("start");
574
+ expect(mount.getAttribute("data-persona-welcome-anchor")).toBe("bottom");
575
+ expect(welcomeHost(mount).querySelector("h2")?.textContent).toBe("Custom");
576
+ expect(welcomeHost(mount).querySelectorAll(".persona-suggestion")).toHaveLength(2);
577
+ controller.injectTestMessage({ type: "message", message: assistantMessage() });
578
+ expect(isVisible(welcomeHost(mount))).toBe(true);
579
+ sendUserMessage(controller);
580
+ expect(isVisible(welcomeHost(mount))).toBe(false);
581
+ controller.clearChat();
582
+ expect(isVisible(welcomeHost(mount))).toBe(true);
583
+ });
584
+
585
+ it("centers the measured fullscreen group and preserves an explicit percentage", () => {
586
+ const { mount, controller } = makeController({ future: { v5Defaults }, launcher: { enabled: false, fullHeight: true }, composer: { placement: "overlay" }, welcome: { layout: "centered" } });
587
+ const footer = mount.querySelector<HTMLElement>(".persona-widget-footer")!;
588
+ const container = footer.parentElement!;
589
+ Object.defineProperty(container, "clientHeight", { configurable: true, value: 800 });
590
+ Object.defineProperty(footer, "offsetHeight", { configurable: true, value: 100 });
591
+ Object.defineProperty(welcomeHost(mount), "offsetHeight", { configurable: true, value: 200 });
592
+ window.dispatchEvent(new Event("resize"));
593
+ // jsdom has no stylesheet geometry: the zero gap/body origin gives
594
+ // (800 - 200 - 100) / 2 rather than the old 44%-top anchoring.
595
+ expect(mount.style.getPropertyValue("--persona-composer-lift")).toBe("250px");
596
+ controller.update({ welcome: { layout: "centered", anchorComposerTop: "40%" } });
597
+ expect(mount.style.getPropertyValue("--persona-composer-lift")).toBe("380px");
598
+ });
599
+ });