@runtypelabs/persona 3.17.0 → 3.19.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 (61) hide show
  1. package/README.md +143 -1
  2. package/dist/animations/glyph-cycle.d.cts +1 -1
  3. package/dist/animations/glyph-cycle.d.ts +1 -1
  4. package/dist/animations/{types-HPZY7oAI.d.cts → types-cwY5HaFD.d.cts} +25 -0
  5. package/dist/animations/{types-HPZY7oAI.d.ts → types-cwY5HaFD.d.ts} +25 -0
  6. package/dist/animations/wipe.d.cts +1 -1
  7. package/dist/animations/wipe.d.ts +1 -1
  8. package/dist/index.cjs +47 -47
  9. package/dist/index.cjs.map +1 -1
  10. package/dist/index.d.cts +580 -4
  11. package/dist/index.d.ts +580 -4
  12. package/dist/index.global.js +102 -1636
  13. package/dist/index.global.js.map +1 -1
  14. package/dist/index.js +45 -45
  15. package/dist/index.js.map +1 -1
  16. package/dist/theme-editor.cjs +2844 -752
  17. package/dist/theme-editor.d.cts +337 -1
  18. package/dist/theme-editor.d.ts +337 -1
  19. package/dist/theme-editor.js +2958 -752
  20. package/dist/theme-reference.cjs +1 -1
  21. package/dist/theme-reference.d.cts +14 -0
  22. package/dist/theme-reference.d.ts +14 -0
  23. package/dist/widget.css +780 -0
  24. package/package.json +1 -1
  25. package/src/client.test.ts +134 -0
  26. package/src/client.ts +71 -0
  27. package/src/components/ask-user-question-bubble.test.ts +583 -0
  28. package/src/components/ask-user-question-bubble.ts +924 -0
  29. package/src/components/composer-builder.test.ts +52 -0
  30. package/src/components/composer-builder.ts +67 -490
  31. package/src/components/composer-parts.test.ts +152 -0
  32. package/src/components/composer-parts.ts +452 -0
  33. package/src/components/header-builder.ts +22 -299
  34. package/src/components/header-parts.ts +360 -0
  35. package/src/components/messages.ts +33 -1
  36. package/src/components/panel.test.ts +61 -0
  37. package/src/components/panel.ts +303 -9
  38. package/src/components/pill-composer-builder.test.ts +85 -0
  39. package/src/components/pill-composer-builder.ts +183 -0
  40. package/src/defaults.ts +21 -0
  41. package/src/index.ts +20 -1
  42. package/src/plugins/types.ts +57 -0
  43. package/src/runtime/init.ts +4 -2
  44. package/src/runtime/persist-state.test.ts +152 -0
  45. package/src/session.test.ts +183 -0
  46. package/src/session.ts +242 -3
  47. package/src/styles/widget.css +780 -0
  48. package/src/types/theme.ts +15 -0
  49. package/src/types.ts +271 -1
  50. package/src/ui.ask-user-question-plugin.test.ts +649 -0
  51. package/src/ui.component-directive.test.ts +183 -0
  52. package/src/ui.composer-bar.test.ts +1009 -0
  53. package/src/ui.ts +1439 -76
  54. package/src/utils/attachment-manager.ts +1 -1
  55. package/src/utils/dock.test.ts +45 -0
  56. package/src/utils/dock.ts +3 -0
  57. package/src/utils/icons.ts +314 -58
  58. package/src/utils/storage.ts +10 -2
  59. package/src/utils/stream-animation.ts +7 -2
  60. package/src/utils/theme.test.ts +36 -0
  61. package/src/utils/tokens.ts +23 -0
@@ -0,0 +1,183 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
4
+
5
+ import { createAgentExperience } from "./ui";
6
+ import { componentRegistry, type ComponentRenderer } from "./components/registry";
7
+
8
+ const createMount = () => {
9
+ const mount = document.createElement("div");
10
+ document.body.appendChild(mount);
11
+ return mount;
12
+ };
13
+
14
+ const directiveMessage = (
15
+ controller: ReturnType<typeof createAgentExperience>,
16
+ {
17
+ id,
18
+ rawContent,
19
+ content = "",
20
+ createdAt = "2026-04-28T00:00:00.000Z",
21
+ }: {
22
+ id: string;
23
+ rawContent: string;
24
+ content?: string;
25
+ createdAt?: string;
26
+ }
27
+ ) => {
28
+ controller.injectTestMessage({
29
+ type: "message",
30
+ message: {
31
+ id,
32
+ role: "assistant",
33
+ content,
34
+ rawContent,
35
+ createdAt,
36
+ streaming: false,
37
+ },
38
+ });
39
+ };
40
+
41
+ describe("component directive bubble — listener preservation across morphs", () => {
42
+ beforeEach(() => {
43
+ // The component registry is a singleton; clear it between tests so each
44
+ // test starts from a clean slate.
45
+ componentRegistry.clear();
46
+ });
47
+
48
+ afterEach(() => {
49
+ document.body.innerHTML = "";
50
+ componentRegistry.clear();
51
+ vi.restoreAllMocks();
52
+ });
53
+
54
+ it("keeps the renderer's submit listener alive after subsequent transcript updates", () => {
55
+ const submitSpy = vi.fn();
56
+
57
+ const mount = createMount();
58
+ const controller = createAgentExperience(mount, {
59
+ apiUrl: "https://api.example.com/chat",
60
+ launcher: { enabled: false },
61
+ parserType: "json",
62
+ enableComponentStreaming: true,
63
+ components: {
64
+ TestForm: ((props) => {
65
+ const root = document.createElement("div");
66
+ root.setAttribute("data-test-id", "test-form");
67
+ const form = document.createElement("form");
68
+ form.setAttribute("data-test-id", "test-form-el");
69
+ form.addEventListener("submit", (event) => {
70
+ event.preventDefault();
71
+ submitSpy(String(props.label ?? ""));
72
+ });
73
+ const button = document.createElement("button");
74
+ button.type = "submit";
75
+ button.setAttribute("data-test-id", "test-form-submit");
76
+ button.textContent = "Submit";
77
+ form.appendChild(button);
78
+ root.appendChild(form);
79
+ return root;
80
+ }) satisfies ComponentRenderer,
81
+ },
82
+ } as unknown as Parameters<typeof createAgentExperience>[1]);
83
+
84
+ directiveMessage(controller, {
85
+ id: "msg-1",
86
+ rawContent: JSON.stringify({ component: "TestForm", props: { label: "alpha" } }),
87
+ });
88
+
89
+ const formBefore = mount.querySelector<HTMLFormElement>('[data-test-id="test-form-el"]');
90
+ expect(formBefore).not.toBeNull();
91
+
92
+ // Force another render pass — this is what triggered the bug pre-fix:
93
+ // Idiomorph would replace the form via innerHTML serialization, dropping
94
+ // the addEventListener-attached submit handler.
95
+ controller.injectTestMessage({
96
+ type: "message",
97
+ message: {
98
+ id: "msg-2",
99
+ role: "user",
100
+ content: "follow-up",
101
+ createdAt: "2026-04-28T00:00:01.000Z",
102
+ streaming: false,
103
+ },
104
+ });
105
+
106
+ // The form node should be the SAME instance — preserved by the live
107
+ // wrapper's `data-preserve-runtime` and the post-morph hydrate skipping
108
+ // when fingerprint is unchanged.
109
+ const formAfter = mount.querySelector<HTMLFormElement>('[data-test-id="test-form-el"]');
110
+ expect(formAfter).toBe(formBefore);
111
+
112
+ const submitBtn = mount.querySelector<HTMLButtonElement>('[data-test-id="test-form-submit"]');
113
+ expect(submitBtn).not.toBeNull();
114
+ submitBtn!.click();
115
+
116
+ expect(submitSpy).toHaveBeenCalledTimes(1);
117
+ expect(submitSpy).toHaveBeenCalledWith("alpha");
118
+
119
+ controller.destroy();
120
+ });
121
+
122
+ it("rebuilds and re-hydrates the bubble when the directive props change", () => {
123
+ const calls: string[] = [];
124
+
125
+ const mount = createMount();
126
+ const controller = createAgentExperience(mount, {
127
+ apiUrl: "https://api.example.com/chat",
128
+ launcher: { enabled: false },
129
+ parserType: "json",
130
+ enableComponentStreaming: true,
131
+ components: {
132
+ TestBadge: ((props) => {
133
+ const el = document.createElement("div");
134
+ el.setAttribute("data-test-id", "test-badge");
135
+ el.textContent = String(props.label ?? "");
136
+ calls.push(String(props.label ?? ""));
137
+ return el;
138
+ }) satisfies ComponentRenderer,
139
+ },
140
+ } as unknown as Parameters<typeof createAgentExperience>[1]);
141
+
142
+ directiveMessage(controller, {
143
+ id: "badge-1",
144
+ rawContent: JSON.stringify({ component: "TestBadge", props: { label: "first" } }),
145
+ });
146
+ expect(mount.querySelector('[data-test-id="test-badge"]')?.textContent).toBe("first");
147
+
148
+ // Same id, new props — fingerprint changes, so renderer should be invoked
149
+ // again and the new element hydrated into the live wrapper.
150
+ directiveMessage(controller, {
151
+ id: "badge-1",
152
+ rawContent: JSON.stringify({ component: "TestBadge", props: { label: "second" } }),
153
+ });
154
+ expect(mount.querySelector('[data-test-id="test-badge"]')?.textContent).toBe("second");
155
+ expect(calls).toEqual(["first", "second"]);
156
+
157
+ controller.destroy();
158
+ });
159
+
160
+ it("falls back to the standard render path when the directive component is not registered", () => {
161
+ const mount = createMount();
162
+ const controller = createAgentExperience(mount, {
163
+ apiUrl: "https://api.example.com/chat",
164
+ launcher: { enabled: false },
165
+ parserType: "json",
166
+ enableComponentStreaming: true,
167
+ components: {},
168
+ } as unknown as Parameters<typeof createAgentExperience>[1]);
169
+
170
+ directiveMessage(controller, {
171
+ id: "missing-1",
172
+ rawContent: JSON.stringify({ component: "NotRegistered", props: {} }),
173
+ content: "fallback text",
174
+ });
175
+
176
+ // No directive stub left in the DOM.
177
+ expect(mount.querySelector('[data-component-directive-stub="true"]')).toBeNull();
178
+ // Standard bubble renders the message text.
179
+ expect(mount.textContent).toContain("fallback text");
180
+
181
+ controller.destroy();
182
+ });
183
+ });