@runtypelabs/persona 3.34.1 → 3.35.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 (186) hide show
  1. package/README.md +11 -11
  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-CthJFfNx.d.cts → types-DgYsuwXL.d.cts} +15 -14
  5. package/dist/animations/{types-CthJFfNx.d.ts → types-DgYsuwXL.d.ts} +15 -14
  6. package/dist/animations/wipe.d.cts +1 -1
  7. package/dist/animations/wipe.d.ts +1 -1
  8. package/dist/codegen.cjs +11 -11
  9. package/dist/codegen.js +10 -10
  10. package/dist/index.cjs +46 -44
  11. package/dist/index.cjs.map +1 -1
  12. package/dist/index.d.cts +514 -158
  13. package/dist/index.d.ts +514 -158
  14. package/dist/index.global.js +41 -89
  15. package/dist/index.global.js.map +1 -1
  16. package/dist/index.js +46 -44
  17. package/dist/index.js.map +1 -1
  18. package/dist/install.global.js +1 -1
  19. package/dist/install.global.js.map +1 -1
  20. package/dist/launcher.global.js +2 -2
  21. package/dist/launcher.global.js.map +1 -1
  22. package/dist/markdown-parsers.js +53 -0
  23. package/dist/plugin-kit.d.cts +4 -4
  24. package/dist/plugin-kit.d.ts +4 -4
  25. package/dist/runtype-tts.js +1 -0
  26. package/dist/smart-dom-reader.cjs +1 -1
  27. package/dist/smart-dom-reader.d.cts +352 -103
  28. package/dist/smart-dom-reader.d.ts +352 -103
  29. package/dist/smart-dom-reader.js +1 -1
  30. package/dist/theme-editor-preview.cjs +203 -0
  31. package/dist/theme-editor-preview.d.cts +5748 -0
  32. package/dist/theme-editor-preview.d.ts +5748 -0
  33. package/dist/theme-editor-preview.js +203 -0
  34. package/dist/theme-editor.cjs +14 -131
  35. package/dist/theme-editor.d.cts +362 -574
  36. package/dist/theme-editor.d.ts +362 -574
  37. package/dist/theme-editor.js +14 -131
  38. package/dist/theme-reference.cjs +1 -1
  39. package/dist/theme-reference.d.cts +1 -1
  40. package/dist/theme-reference.d.ts +1 -1
  41. package/dist/theme-reference.js +1 -1
  42. package/dist/voice-worklet-player.cjs +80 -0
  43. package/dist/voice-worklet-player.d.cts +215 -0
  44. package/dist/voice-worklet-player.d.ts +215 -0
  45. package/dist/voice-worklet-player.js +80 -0
  46. package/dist/widget.css +54 -36
  47. package/package.json +16 -2
  48. package/src/animations/glyph-cycle.ts +11 -14
  49. package/src/animations/wipe.ts +1 -1
  50. package/src/ask-user-question-tool.test.ts +1 -1
  51. package/src/ask-user-question-tool.ts +6 -8
  52. package/src/client.test.ts +12 -12
  53. package/src/client.ts +30 -31
  54. package/src/codegen.test.ts +1 -1
  55. package/src/codegen.ts +1 -1
  56. package/src/components/approval-bubble.test.ts +1 -1
  57. package/src/components/approval-bubble.ts +3 -3
  58. package/src/components/artifact-pane.ts +3 -3
  59. package/src/components/ask-user-question-bubble.test.ts +14 -14
  60. package/src/components/ask-user-question-bubble.ts +10 -10
  61. package/src/components/composer-builder.ts +17 -16
  62. package/src/components/composer-parts.test.ts +54 -0
  63. package/src/components/composer-parts.ts +95 -126
  64. package/src/components/demo-carousel.ts +1 -1
  65. package/src/components/event-stream-view.test.ts +7 -7
  66. package/src/components/event-stream-view.ts +5 -5
  67. package/src/components/header-builder.ts +33 -27
  68. package/src/components/header-layouts.ts +1 -1
  69. package/src/components/header-parts.test.ts +90 -0
  70. package/src/components/header-parts.ts +62 -93
  71. package/src/components/launcher.test.ts +11 -0
  72. package/src/components/launcher.ts +1 -1
  73. package/src/components/message-bubble.test.ts +74 -2
  74. package/src/components/message-bubble.ts +25 -11
  75. package/src/components/messages.ts +2 -2
  76. package/src/components/panel.test.ts +1 -1
  77. package/src/components/panel.ts +92 -97
  78. package/src/components/pill-composer-builder.ts +28 -24
  79. package/src/components/reasoning-bubble.ts +2 -2
  80. package/src/components/suggestions.ts +1 -1
  81. package/src/components/tool-bubble.ts +1 -1
  82. package/src/components/widget-view.test.ts +181 -0
  83. package/src/components/widget-view.ts +234 -0
  84. package/src/defaults.ts +54 -51
  85. package/src/generated/runtype-openapi-contract.ts +5 -0
  86. package/src/index-core.ts +30 -4
  87. package/src/index-global.ts +73 -6
  88. package/src/index.ts +7 -1
  89. package/src/install.test.ts +11 -11
  90. package/src/install.ts +28 -18
  91. package/src/launcher-global.ts +25 -14
  92. package/src/markdown-parsers-eager.ts +20 -0
  93. package/src/markdown-parsers-entry.ts +4 -0
  94. package/src/markdown-parsers-loader.ts +49 -0
  95. package/src/plugin-kit.test.ts +1 -1
  96. package/src/plugin-kit.ts +7 -7
  97. package/src/plugins/types.ts +5 -5
  98. package/src/postprocessors.ts +27 -14
  99. package/src/runtime/host-layout.test.ts +1 -1
  100. package/src/runtime/host-layout.ts +4 -5
  101. package/src/runtime/init.ts +2 -2
  102. package/src/session.test.ts +5 -6
  103. package/src/session.ts +333 -227
  104. package/src/session.voice.test.ts +146 -0
  105. package/src/session.webmcp.test.ts +24 -25
  106. package/src/smart-dom-reader.ts +3 -4
  107. package/src/styles/widget.css +54 -36
  108. package/src/suggest-replies-tool.test.ts +3 -3
  109. package/src/suggest-replies-tool.ts +5 -5
  110. package/src/testing/index.ts +1 -1
  111. package/src/theme-editor/color-utils.ts +2 -2
  112. package/src/theme-editor/index.ts +4 -13
  113. package/src/theme-editor/presets.ts +1 -1
  114. package/src/theme-editor/preview-utils.ts +22 -20
  115. package/src/theme-editor/preview.ts +4 -1
  116. package/src/theme-editor/role-mappings.ts +3 -3
  117. package/src/theme-editor/sections.ts +21 -20
  118. package/src/theme-editor/types.ts +1 -1
  119. package/src/theme-editor/webmcp/index.ts +1 -1
  120. package/src/theme-editor/webmcp/summary.ts +1 -2
  121. package/src/theme-editor/webmcp/tools.ts +30 -30
  122. package/src/theme-editor/webmcp/types.ts +2 -2
  123. package/src/theme-editor-preview.ts +10 -0
  124. package/src/theme-reference.ts +26 -26
  125. package/src/types.ts +368 -99
  126. package/src/ui.approval-plugin.test.ts +3 -3
  127. package/src/ui.ask-user-question-plugin.test.ts +9 -9
  128. package/src/ui.component-directive.test.ts +4 -4
  129. package/src/ui.composer-bar.test.ts +10 -11
  130. package/src/ui.composer-keyboard.test.ts +5 -5
  131. package/src/ui.header-icon-color.test.ts +59 -0
  132. package/src/ui.scroll.test.ts +5 -6
  133. package/src/ui.stop-button.test.ts +3 -4
  134. package/src/ui.suggest-replies.test.ts +2 -2
  135. package/src/ui.ts +292 -131
  136. package/src/utils/buttons.ts +1 -1
  137. package/src/utils/code-generators.ts +1 -1
  138. package/src/utils/composer-history.ts +3 -3
  139. package/src/utils/dom-context.test.ts +2 -2
  140. package/src/utils/dom-context.ts +7 -7
  141. package/src/utils/dom.test.ts +67 -0
  142. package/src/utils/dom.ts +75 -0
  143. package/src/utils/dropdown.ts +2 -2
  144. package/src/utils/event-stream-buffer.test.ts +2 -2
  145. package/src/utils/message-fingerprint.test.ts +9 -0
  146. package/src/utils/message-fingerprint.ts +5 -0
  147. package/src/utils/morph.ts +1 -1
  148. package/src/utils/sanitize.ts +32 -16
  149. package/src/utils/sequence-buffer.test.ts +8 -8
  150. package/src/utils/sequence-buffer.ts +4 -4
  151. package/src/utils/smart-dom-adapter.test.ts +1 -1
  152. package/src/utils/smart-dom-adapter.ts +4 -5
  153. package/src/utils/speech-text.test.ts +122 -0
  154. package/src/utils/speech-text.ts +101 -0
  155. package/src/utils/stream-animation.test.ts +3 -3
  156. package/src/utils/stream-animation.ts +6 -6
  157. package/src/utils/theme.test.ts +1 -1
  158. package/src/utils/throughput-tracker.test.ts +11 -11
  159. package/src/utils/throughput-tracker.ts +9 -9
  160. package/src/utils/tokens.ts +13 -13
  161. package/src/utils/virtual-scroller.ts +1 -1
  162. package/src/vendor/smart-dom-reader/README.md +4 -4
  163. package/src/vendor/smart-dom-reader/index.d.ts +1 -1
  164. package/src/vendor/smart-dom-reader/index.js +2 -2
  165. package/src/version.ts +1 -1
  166. package/src/voice/audio-playback-manager.test.ts +159 -0
  167. package/src/voice/audio-playback-manager.ts +145 -30
  168. package/src/voice/browser-speech-engine.ts +130 -0
  169. package/src/voice/fallback-speech-engine.ts +89 -0
  170. package/src/voice/index.ts +21 -0
  171. package/src/voice/read-aloud-controller.test.ts +143 -0
  172. package/src/voice/read-aloud-controller.ts +136 -0
  173. package/src/voice/runtype-speech-engine.test.ts +280 -0
  174. package/src/voice/runtype-speech-engine.ts +211 -0
  175. package/src/voice/runtype-tts-entry.ts +10 -0
  176. package/src/voice/runtype-tts-loader.test.ts +47 -0
  177. package/src/voice/runtype-tts-loader.ts +42 -0
  178. package/src/voice/runtype-voice-provider.ts +332 -537
  179. package/src/voice/voice-factory.ts +26 -4
  180. package/src/voice/voice.test.ts +321 -7
  181. package/src/voice/worklet-playback-engine.ts +272 -0
  182. package/src/voice-worklet-player.ts +39 -0
  183. package/src/webmcp-bridge.test.ts +6 -6
  184. package/src/webmcp-bridge.ts +24 -25
  185. package/src/webmcp-polyfill.ts +1 -1
  186. package/src/voice/voice-activity-detector.ts +0 -90
@@ -0,0 +1,122 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { stripMarkdownForSpeech, resolveSpeakableText, extractActionMessageText } from "./speech-text";
3
+
4
+ describe("stripMarkdownForSpeech", () => {
5
+ it("returns empty string for empty/undefined input", () => {
6
+ expect(stripMarkdownForSpeech("")).toBe("");
7
+ // @ts-expect-error testing defensive undefined handling
8
+ expect(stripMarkdownForSpeech(undefined)).toBe("");
9
+ });
10
+
11
+ it("strips bold and italic markers but keeps the words", () => {
12
+ expect(stripMarkdownForSpeech("This is **bold** and *italic* text")).toBe(
13
+ "This is bold and italic text"
14
+ );
15
+ expect(stripMarkdownForSpeech("__strong__ and _em_")).toBe("strong and em");
16
+ });
17
+
18
+ it("keeps inline code text without backticks", () => {
19
+ expect(stripMarkdownForSpeech("Run `npm install` first")).toBe("Run npm install first");
20
+ });
21
+
22
+ it("drops fenced code blocks entirely", () => {
23
+ const md = "Here is code:\n```js\nconst x = 1;\n```\nDone.";
24
+ const out = stripMarkdownForSpeech(md);
25
+ expect(out).not.toContain("const x");
26
+ expect(out).toContain("Here is code:");
27
+ expect(out).toContain("Done.");
28
+ });
29
+
30
+ it("speaks link text, not the URL", () => {
31
+ expect(stripMarkdownForSpeech("See [the docs](https://example.com/x) now")).toBe(
32
+ "See the docs now"
33
+ );
34
+ });
35
+
36
+ it("speaks image alt text", () => {
37
+ expect(stripMarkdownForSpeech("![a red car](car.png)")).toBe("a red car");
38
+ });
39
+
40
+ it("strips heading, blockquote, and list markers", () => {
41
+ expect(stripMarkdownForSpeech("# Title")).toBe("Title");
42
+ expect(stripMarkdownForSpeech("> quoted")).toBe("quoted");
43
+ expect(stripMarkdownForSpeech("- item one\n- item two")).toBe("item one\nitem two");
44
+ expect(stripMarkdownForSpeech("1. first\n2. second")).toBe("first\nsecond");
45
+ });
46
+
47
+ it("strips raw HTML tags", () => {
48
+ expect(stripMarkdownForSpeech("Hello <strong>world</strong>!")).toBe("Hello world !");
49
+ });
50
+
51
+ it("decodes common HTML entities", () => {
52
+ expect(stripMarkdownForSpeech("Tom &amp; Jerry &lt;3")).toBe("Tom & Jerry <3");
53
+ });
54
+
55
+ it("collapses excess whitespace and trims", () => {
56
+ expect(stripMarkdownForSpeech(" lots of\n\n\nspace ")).toBe("lots of\nspace");
57
+ });
58
+
59
+ it("handles a realistic mixed message", () => {
60
+ const md = [
61
+ "## Summary",
62
+ "",
63
+ "I'd **ship** `approval.choices.alwaysAllow` and [this one](https://x.y).",
64
+ "",
65
+ "```ts",
66
+ "const skip = true;",
67
+ "```",
68
+ "",
69
+ "- point A",
70
+ "- point B",
71
+ ].join("\n");
72
+ const out = stripMarkdownForSpeech(md);
73
+ expect(out).toContain("Summary");
74
+ expect(out).toContain("I'd ship approval.choices.alwaysAllow and this one.");
75
+ expect(out).toContain("point A");
76
+ expect(out).not.toContain("```");
77
+ expect(out).not.toContain("const skip");
78
+ expect(out).not.toContain("https://x.y");
79
+ });
80
+ });
81
+
82
+ describe("extractActionMessageText", () => {
83
+ it("extracts text from a bare action envelope", () => {
84
+ expect(extractActionMessageText('{"action":"message","text":"Hello there"}')).toBe("Hello there");
85
+ });
86
+
87
+ it("extracts text from a fenced ```json envelope", () => {
88
+ const raw = '```json\n{\n "action": "message",\n "text": "Why don\'t scientists trust atoms?"\n}\n```';
89
+ expect(extractActionMessageText(raw)).toBe("Why don't scientists trust atoms?");
90
+ });
91
+
92
+ it("returns null for plain prose", () => {
93
+ expect(extractActionMessageText("Just a normal reply")).toBeNull();
94
+ });
95
+
96
+ it("returns null for JSON without a string text field", () => {
97
+ expect(extractActionMessageText('{"action":"navigate","url":"/x"}')).toBeNull();
98
+ });
99
+
100
+ it("returns null for malformed JSON", () => {
101
+ expect(extractActionMessageText('{"action":"message", "text":')).toBeNull();
102
+ });
103
+ });
104
+
105
+ describe("resolveSpeakableText", () => {
106
+ it("speaks the action text, not the JSON envelope", () => {
107
+ const raw = '```json\n{"action":"message","text":"Why don\'t scientists trust atoms?"}\n```';
108
+ expect(resolveSpeakableText(raw)).toBe("Why don't scientists trust atoms?");
109
+ });
110
+
111
+ it("strips markdown from action text", () => {
112
+ expect(resolveSpeakableText('{"action":"message","text":"Ship **it** now"}')).toBe("Ship it now");
113
+ });
114
+
115
+ it("falls back to markdown stripping for plain prose", () => {
116
+ expect(resolveSpeakableText("This is **bold**")).toBe("This is bold");
117
+ });
118
+
119
+ it("returns empty string for empty input", () => {
120
+ expect(resolveSpeakableText("")).toBe("");
121
+ });
122
+ });
@@ -0,0 +1,101 @@
1
+ /**
2
+ * Resolve the text that should be spoken for an assistant message.
3
+ *
4
+ * Some flows return an action-format envelope as the message body — e.g.
5
+ * `{"action":"message","text":"Hello"}`, often inside a ```json fence — rather
6
+ * than plain prose. Speaking the raw JSON (or, after fenced-code stripping,
7
+ * nothing at all) is useless, so we first try to extract the human `text` field
8
+ * (mirroring the widget's `defaultJsonActionParser`, which also keys on
9
+ * `.text`), and otherwise fall back to stripping Markdown from the raw content.
10
+ */
11
+ export function resolveSpeakableText(raw: string): string {
12
+ if (!raw) return "";
13
+ const actionText = extractActionMessageText(raw);
14
+ if (actionText !== null) return stripMarkdownForSpeech(actionText);
15
+ return stripMarkdownForSpeech(raw);
16
+ }
17
+
18
+ /**
19
+ * If `raw` is (or wraps, in a ```json fence) a JSON object with a string `text`
20
+ * property, return that text; otherwise return `null`. Matches the action
21
+ * envelope shape `{ action, text, ... }` used by the widget's action system.
22
+ */
23
+ export function extractActionMessageText(raw: string): string | null {
24
+ let body = raw.trim();
25
+ const fence = body.match(/^```(?:json)?\s*([\s\S]*?)\s*```$/i);
26
+ if (fence) body = fence[1].trim();
27
+ if (!body.startsWith("{")) return null;
28
+ try {
29
+ const parsed = JSON.parse(body);
30
+ if (parsed && typeof parsed === "object" && typeof (parsed as { text?: unknown }).text === "string") {
31
+ return (parsed as { text: string }).text;
32
+ }
33
+ } catch {
34
+ // Not valid JSON — fall through to the Markdown path.
35
+ }
36
+ return null;
37
+ }
38
+
39
+ /**
40
+ * Convert Markdown to plain text suitable for text-to-speech.
41
+ *
42
+ * Read-aloud should speak the prose a user sees, not the markup: code fences,
43
+ * backticks, emphasis markers, link/image syntax and raw HTML are all noise
44
+ * when spoken. This is intentionally lightweight (regex-based, no full Markdown
45
+ * parser) — it favours predictable, dependency-free output over perfect
46
+ * fidelity. Speech engines receive the result of this function, never raw
47
+ * Markdown.
48
+ */
49
+ export function stripMarkdownForSpeech(markdown: string): string {
50
+ if (!markdown) return "";
51
+ let text = markdown;
52
+
53
+ // Fenced code blocks: drop entirely (reading source aloud is noise).
54
+ text = text.replace(/```[\s\S]*?```/g, " ");
55
+ text = text.replace(/~~~[\s\S]*?~~~/g, " ");
56
+
57
+ // Inline code: keep the inner text, drop the backticks.
58
+ text = text.replace(/`([^`]+)`/g, "$1");
59
+
60
+ // Images: speak the alt text only.
61
+ text = text.replace(/!\[([^\]]*)\]\([^)]*\)/g, "$1");
62
+
63
+ // Inline links: speak the link text, drop the URL.
64
+ text = text.replace(/\[([^\]]+)\]\([^)]*\)/g, "$1");
65
+
66
+ // Reference-style links/images: [text][ref] -> text.
67
+ text = text.replace(/\[([^\]]+)\]\[[^\]]*\]/g, "$1");
68
+
69
+ // Raw HTML tags.
70
+ text = text.replace(/<\/?[a-zA-Z][^>]*>/g, " ");
71
+
72
+ // Line-leading markers: headings, blockquotes, list bullets, ordered items.
73
+ text = text.replace(/^[ \t]*#{1,6}[ \t]+/gm, "");
74
+ text = text.replace(/^[ \t]*>[ \t]?/gm, "");
75
+ text = text.replace(/^[ \t]*[-*+][ \t]+/gm, "");
76
+ text = text.replace(/^[ \t]*\d+\.[ \t]+/gm, "");
77
+
78
+ // Horizontal rules.
79
+ text = text.replace(/^[ \t]*([-*_])([ \t]*\1){2,}[ \t]*$/gm, " ");
80
+
81
+ // Emphasis / strikethrough markers (bold before italic so ** is consumed).
82
+ text = text.replace(/(\*\*|__)(.*?)\1/g, "$2");
83
+ text = text.replace(/(\*|_)(.*?)\1/g, "$2");
84
+ text = text.replace(/~~(.*?)~~/g, "$1");
85
+
86
+ // Decode a few common HTML entities so they aren't spoken literally.
87
+ text = text
88
+ .replace(/&amp;/g, "&")
89
+ .replace(/&lt;/g, "<")
90
+ .replace(/&gt;/g, ">")
91
+ .replace(/&quot;/g, '"')
92
+ .replace(/&#39;/g, "'")
93
+ .replace(/&nbsp;/g, " ");
94
+
95
+ // Collapse whitespace.
96
+ text = text.replace(/[ \t]+/g, " ");
97
+ text = text.replace(/[ \t]*\n[ \t]*/g, "\n");
98
+ text = text.replace(/\n{2,}/g, "\n");
99
+
100
+ return text.trim();
101
+ }
@@ -22,7 +22,7 @@ import type { AgentWidgetMessage, StreamAnimationPlugin } from "../types";
22
22
  import "../animations/wipe";
23
23
  import "../animations/glyph-cycle";
24
24
 
25
- describe("wrapStreamAnimation char mode", () => {
25
+ describe("wrapStreamAnimation: char mode", () => {
26
26
  it("wraps every character in a plain paragraph into a stream-char span", () => {
27
27
  const out = wrapStreamAnimation("<p>Hi!</p>", "char", "m1");
28
28
  const parser = document.createElement("div");
@@ -145,7 +145,7 @@ describe("wrapStreamAnimation — char mode", () => {
145
145
  };
146
146
  const firstSpans = parse(first);
147
147
  const secondSpans = parse(second);
148
- // First two ids are stable idiomorph match contract. The space between
148
+ // First two ids are stable: idiomorph match contract. The space between
149
149
  // "Hi" and "there" is a plain text node, not a span, so the next wrapped
150
150
  // char after "Hi" jumps to index 2 for "t" in "there".
151
151
  expect(firstSpans[0].id).toBe(secondSpans[0].id);
@@ -161,7 +161,7 @@ describe("wrapStreamAnimation — char mode", () => {
161
161
  });
162
162
  });
163
163
 
164
- describe("wrapStreamAnimation word mode", () => {
164
+ describe("wrapStreamAnimation: word mode", () => {
165
165
  it("splits on whitespace and wraps each non-whitespace token", () => {
166
166
  const out = wrapStreamAnimation("<p>Hello brave world</p>", "word", "m1");
167
167
  const parser = document.createElement("div");
@@ -42,11 +42,11 @@ export const resolveStreamAnimation = (
42
42
  ============================================================ */
43
43
 
44
44
  /**
45
- * Built-in animations ship with the core widget CSS lives in widget.css
45
+ * Built-in animations ship with the core widget: CSS lives in widget.css
46
46
  * and no subpath import is required. They register automatically.
47
47
  *
48
48
  * Other animations (`letter-rise`, `word-fade`, `wipe`, `glyph-cycle`) are
49
- * tree-shakeable subpath plugins consumers import them from
49
+ * tree-shakeable subpath plugins: consumers import them from
50
50
  * `@runtypelabs/persona/animations/<name>` and they auto-register on load.
51
51
  */
52
52
  const BUILTIN_PLUGINS: StreamAnimationPlugin[] = [
@@ -268,7 +268,7 @@ const wrapTextNodeWords = (
268
268
  *
269
269
  * Each wrapped span carries a stable `id` (`stream-c-{messageId}-{N}` or
270
270
  * `stream-w-{messageId}-{N}`) so idiomorph preserves existing spans across
271
- * token-by-token re-renders animations on already-streamed characters never
271
+ * token-by-token re-renders: animations on already-streamed characters never
272
272
  * restart.
273
273
  */
274
274
  export const wrapStreamAnimation = (
@@ -297,7 +297,7 @@ export const wrapStreamAnimation = (
297
297
  // `startIndex` lets callers number spans by their absolute position in a
298
298
  // larger string, even when only a slice is being wrapped. The peek banner
299
299
  // uses this so per-char span IDs stay stable as the trailing-100-char
300
- // window shifts each chunk idiomorph then preserves animations on
300
+ // window shifts each chunk: idiomorph then preserves animations on
301
301
  // already-revealed chars instead of restarting them.
302
302
  const counterRef = { value: options?.startIndex ?? 0 };
303
303
  const wrap = mode === "char" ? wrapTextNodeChars : wrapTextNodeWords;
@@ -326,7 +326,7 @@ export const createStreamCaret = (doc: Document = document): HTMLElement => {
326
326
  };
327
327
 
328
328
  /**
329
- * Shimmer placeholder shown before the first token arrives and, when the
329
+ * Shimmer placeholder shown before the first token arrives, and, when the
330
330
  * `"line"` buffer strategy is active, reshown between lines. A single
331
331
  * full-width bar; we don't know ahead of time how wide the next line will be,
332
332
  * so committing to one width avoids implying structure the stream won't match.
@@ -415,7 +415,7 @@ export const detachAllPlugins = (root: HTMLElement | ShadowRoot): void => {
415
415
 
416
416
  /**
417
417
  * Ensure the plugin's one-time side effects (style injection, onAttach) have
418
- * run for this widget root. Idempotent safe to call on every render.
418
+ * run for this widget root. Idempotent: safe to call on every render.
419
419
  */
420
420
  export const ensurePluginActive = (
421
421
  plugin: StreamAnimationPlugin,
@@ -297,7 +297,7 @@ describe('theme utils', () => {
297
297
  toolBubble: { shadow: '0 1px 2px rgba(255,0,0,0.5)' },
298
298
  },
299
299
  },
300
- // config.toolCall.shadow no longer rewrites the root variable the
300
+ // config.toolCall.shadow no longer rewrites the root variable: the
301
301
  // override is applied inline by createToolBubble (see tool-bubble tests).
302
302
  toolCall: { shadow: 'none' },
303
303
  });
@@ -35,7 +35,7 @@ describe("estimateOutputTokens", () => {
35
35
  });
36
36
  });
37
37
 
38
- describe("ThroughputTracker live estimate", () => {
38
+ describe("ThroughputTracker: live estimate", () => {
39
39
  it("estimates output tokens live from visible text deltas", () => {
40
40
  const h = makeTracker();
41
41
 
@@ -97,7 +97,7 @@ describe("ThroughputTracker — live estimate", () => {
97
97
  });
98
98
  });
99
99
 
100
- describe("ThroughputTracker exact usage finalization", () => {
100
+ describe("ThroughputTracker: exact usage finalization", () => {
101
101
  it("prefers exact output tokens from the terminal event over the estimate", () => {
102
102
  const h = makeTracker();
103
103
  h.at(1000).processEvent("step_delta", { type: "step_delta", text: text(40) });
@@ -160,7 +160,7 @@ describe("ThroughputTracker — exact usage finalization", () => {
160
160
  });
161
161
  });
162
162
 
163
- describe("ThroughputTracker non-visible deltas ignored", () => {
163
+ describe("ThroughputTracker: non-visible deltas ignored", () => {
164
164
  it("ignores agent thinking and tool_input deltas", () => {
165
165
  const h = makeTracker();
166
166
 
@@ -223,7 +223,7 @@ describe("ThroughputTracker — non-visible deltas ignored", () => {
223
223
  });
224
224
  });
225
225
 
226
- describe("ThroughputTracker intermediate completes do not finalize", () => {
226
+ describe("ThroughputTracker: intermediate completes do not finalize", () => {
227
227
  it("keeps the run running across step_complete / agent_turn_complete", () => {
228
228
  const h = makeTracker();
229
229
  h.at(1000).processEvent("step_delta", { type: "step_delta", text: text(40) });
@@ -245,7 +245,7 @@ describe("ThroughputTracker — intermediate completes do not finalize", () => {
245
245
  });
246
246
  });
247
247
 
248
- describe("ThroughputTracker error handling", () => {
248
+ describe("ThroughputTracker: error handling", () => {
249
249
  it.each(["step_error", "flow_error", "agent_error", "error"])(
250
250
  "marks the metric unavailable on %s",
251
251
  (errorType: string) => {
@@ -278,7 +278,7 @@ describe("ThroughputTracker — error handling", () => {
278
278
  });
279
279
  });
280
280
 
281
- describe("ThroughputTracker reset & re-run", () => {
281
+ describe("ThroughputTracker: reset & re-run", () => {
282
282
  it("reset() returns to idle", () => {
283
283
  const h = makeTracker();
284
284
  h.at(1000).processEvent("step_delta", { type: "step_delta", text: text(40) });
@@ -302,7 +302,7 @@ describe("ThroughputTracker — reset & re-run", () => {
302
302
  it("resets a stale run on the next request's start event (no bleed)", () => {
303
303
  const h = makeTracker();
304
304
  // First request streams visible output but never terminates (e.g. the
305
- // user cancels mid-stream no flow_complete / error frame is emitted).
305
+ // user cancels mid-stream: no flow_complete / error frame is emitted).
306
306
  h.at(1000).processEvent("step_delta", { type: "step_delta", text: text(120) });
307
307
  expect(h.metric().outputTokens).toBe(30);
308
308
  expect(h.metric().status).toBe("running");
@@ -328,7 +328,7 @@ describe("ThroughputTracker — reset & re-run", () => {
328
328
  });
329
329
  });
330
330
 
331
- describe("ThroughputTracker exact usage never drops mid-run", () => {
331
+ describe("ThroughputTracker: exact usage never drops mid-run", () => {
332
332
  it("keeps exact tokens as a floor when later steps stream more text", () => {
333
333
  const h = makeTracker();
334
334
  h.at(1000).processEvent("step_delta", { type: "step_delta", text: text(40) });
@@ -342,7 +342,7 @@ describe("ThroughputTracker — exact usage never drops mid-run", () => {
342
342
  expect(m.outputTokens).toBe(50);
343
343
  expect(m.source).toBe("usage");
344
344
 
345
- // Step 2 streams more visible text the total must grow from 50, not
345
+ // Step 2 streams more visible text: the total must grow from 50, not
346
346
  // collapse back to a bare 10-token estimate of the new text.
347
347
  h.at(2000).processEvent("step_delta", { type: "step_delta", text: text(40) });
348
348
  m = h.metric();
@@ -351,14 +351,14 @@ describe("ThroughputTracker — exact usage never drops mid-run", () => {
351
351
  });
352
352
  });
353
353
 
354
- describe("ThroughputTracker live rate decays while paused", () => {
354
+ describe("ThroughputTracker: live rate decays while paused", () => {
355
355
  it("recomputes duration/tok-s from the clock between events", () => {
356
356
  const h = makeTracker();
357
357
  h.at(1000).processEvent("step_delta", { type: "step_delta", text: text(400) });
358
358
  // 100 tokens over a 1s window read at t=2000 → ~100 tok/s.
359
359
  h.at(2000);
360
360
  expect(h.metric().tokensPerSecond).toBeCloseTo(100);
361
- // Same tokens, but the model has paused reading at t=5000 (4s window)
361
+ // Same tokens, but the model has paused: reading at t=5000 (4s window)
362
362
  // must decay the displayed rate even though no new event arrived.
363
363
  h.at(5000);
364
364
  expect(h.metric().tokensPerSecond).toBeCloseTo(25);
@@ -5,7 +5,7 @@
5
5
  // Derives an output tokens-per-second metric from the widget's existing SSE
6
6
  // event stream, for display in the Events diagnostics screen. This is a passive
7
7
  // consumer: it never mutates dispatch payloads, never forces debug mode, and
8
- // never changes the wire contract it only inspects the `(type, payload)`
8
+ // never changes the wire contract: it only inspects the `(type, payload)`
9
9
  // events that already flow through the SSE tap.
10
10
  //
11
11
  // Throughput is estimated live from visible text deltas while a run streams,
@@ -62,7 +62,7 @@ const REQUEST_START_EVENTS = new Set([
62
62
  ]);
63
63
 
64
64
  // Per-step markers that fire repeatedly WITHIN a single request (a flow emits
65
- // one per step). These only lazily begin a run they must never reset, or a
65
+ // one per step). These only lazily begin a run: they must never reset, or a
66
66
  // multi-step response would restart the metric between steps. If no request- or
67
67
  // step-start event is emitted, the first visible delta lazily starts the run.
68
68
  const STEP_START_EVENTS = new Set(["step_start", "execution_start"]);
@@ -151,8 +151,8 @@ function getTextDelta(payload: Record<string, unknown>): string {
151
151
  * other value, and a missing contentType are ignored so throughput never
152
152
  * includes deltas the chat UI doesn't render.
153
153
  *
154
- * For `step_delta` / `step_chunk`, skip tool and context steps those carry
155
- * tool I/O, not model-visible text mirroring the widget's own renderer.
154
+ * For `step_delta` / `step_chunk`, skip tool and context steps: those carry
155
+ * tool I/O, not model-visible text: mirroring the widget's own renderer.
156
156
  */
157
157
  function isVisibleTextDelta(
158
158
  type: string,
@@ -298,13 +298,13 @@ export class ThroughputTracker {
298
298
  const now = this.now();
299
299
 
300
300
  if (REQUEST_START_EVENTS.has(type)) {
301
- // New request start fresh, discarding any incomplete prior run.
301
+ // New request: start fresh, discarding any incomplete prior run.
302
302
  this.startRun(now);
303
303
  return;
304
304
  }
305
305
 
306
306
  if (STEP_START_EVENTS.has(type)) {
307
- // Mid-request step marker only begin a run if none is active.
307
+ // Mid-request step marker: only begin a run if none is active.
308
308
  if (!this.run) this.startRun(now);
309
309
  return;
310
310
  }
@@ -323,7 +323,7 @@ export class ThroughputTracker {
323
323
 
324
324
  // Add the live char estimate of the CURRENT (not-yet-completed) step on
325
325
  // top of any exact usage already booked from completed steps, so the
326
- // count only grows it never drops back to a bare estimate mid-run.
326
+ // count only grows: it never drops back to a bare estimate mid-run.
327
327
  const outputTokens =
328
328
  stats.exactOutputTokens +
329
329
  estimateTokensFromCharCount(stats.visibleCharCount);
@@ -339,7 +339,7 @@ export class ThroughputTracker {
339
339
  }
340
340
 
341
341
  if (INTERMEDIATE_COMPLETE_EVENTS.has(type)) {
342
- // Accumulate exact usage but keep the run going these fire per
342
+ // Accumulate exact usage but keep the run going: these fire per
343
343
  // step/turn, not at the end of the whole run.
344
344
  if (!this.run) return;
345
345
  const stats = this.run;
@@ -347,7 +347,7 @@ export class ThroughputTracker {
347
347
  if (exact !== undefined) {
348
348
  stats.exactOutputTokens += exact;
349
349
  // This step's visible text is now represented exactly by provider
350
- // usage drop it from the running char estimate so the two don't
350
+ // usage: drop it from the running char estimate so the two don't
351
351
  // double-count once the next step starts streaming.
352
352
  stats.visibleCharCount = 0;
353
353
  }
@@ -194,23 +194,23 @@ export const DEFAULT_SEMANTIC: SemanticTokens = {
194
194
  colors: {
195
195
  primary: 'palette.colors.primary.500',
196
196
  secondary: 'palette.colors.secondary.500',
197
- // Links/Focus role solid primary
197
+ // Links/Focus role: solid primary
198
198
  accent: 'palette.colors.primary.600',
199
- // Surfaces role soft gray
199
+ // Surfaces role: soft gray
200
200
  surface: 'palette.colors.gray.50',
201
201
  background: 'palette.colors.gray.50',
202
202
  container: 'palette.colors.gray.50',
203
203
  text: 'palette.colors.gray.900',
204
204
  textMuted: 'palette.colors.gray.500',
205
205
  textInverse: 'palette.colors.gray.50',
206
- // Borders role soft gray
206
+ // Borders role: soft gray
207
207
  border: 'palette.colors.gray.200',
208
208
  divider: 'palette.colors.gray.200',
209
209
  interactive: {
210
- // Primary Actions role solid primary
210
+ // Primary Actions role: solid primary
211
211
  default: 'palette.colors.primary.600',
212
212
  hover: 'palette.colors.primary.700',
213
- // Links/Focus role solid primary
213
+ // Links/Focus role: solid primary
214
214
  focus: 'palette.colors.primary.600',
215
215
  active: 'palette.colors.primary.600',
216
216
  disabled: 'palette.colors.gray.300',
@@ -241,7 +241,7 @@ export const DEFAULT_SEMANTIC: SemanticTokens = {
241
241
  export const DEFAULT_COMPONENTS: ComponentTokens = {
242
242
  button: {
243
243
  primary: {
244
- // Primary Actions role solid primary
244
+ // Primary Actions role: solid primary
245
245
  background: 'palette.colors.primary.500',
246
246
  foreground: 'palette.colors.primary.50',
247
247
  borderRadius: 'palette.radius.lg',
@@ -261,7 +261,7 @@ export const DEFAULT_COMPONENTS: ComponentTokens = {
261
261
  },
262
262
  },
263
263
  input: {
264
- // Input role soft gray
264
+ // Input role: soft gray
265
265
  background: 'palette.colors.gray.50',
266
266
  placeholder: 'palette.colors.gray.400',
267
267
  borderRadius: 'palette.radius.lg',
@@ -289,7 +289,7 @@ export const DEFAULT_COMPONENTS: ComponentTokens = {
289
289
  shadow: 'palette.shadows.xl',
290
290
  },
291
291
  header: {
292
- // Header role solid primary
292
+ // Header role: solid primary
293
293
  background: 'palette.colors.primary.500',
294
294
  border: 'palette.colors.primary.600',
295
295
  borderRadius: 'palette.radius.xl palette.radius.xl 0 0',
@@ -302,14 +302,14 @@ export const DEFAULT_COMPONENTS: ComponentTokens = {
302
302
  },
303
303
  message: {
304
304
  user: {
305
- // User Messages role solid primary
305
+ // User Messages role: solid primary
306
306
  background: 'palette.colors.primary.500',
307
307
  text: 'palette.colors.primary.50',
308
308
  borderRadius: 'palette.radius.lg',
309
309
  shadow: 'palette.shadows.sm',
310
310
  },
311
311
  assistant: {
312
- // Assistant Messages role soft gray
312
+ // Assistant Messages role: soft gray
313
313
  background: 'palette.colors.gray.50',
314
314
  text: 'palette.colors.gray.900',
315
315
  borderRadius: 'palette.radius.lg',
@@ -341,7 +341,7 @@ export const DEFAULT_COMPONENTS: ComponentTokens = {
341
341
  foreground: 'palette.colors.gray.900',
342
342
  },
343
343
  link: {
344
- // Links/Focus role solid primary
344
+ // Links/Focus role: solid primary
345
345
  foreground: 'palette.colors.primary.600',
346
346
  },
347
347
  prose: {
@@ -755,7 +755,7 @@ export function themeToCssVariables(theme: PersonaTheme): Record<string, string>
755
755
  cssVars['--persona-radius-lg'] ??
756
756
  '0.5rem';
757
757
 
758
- // Component-level color overrides these map component tokens to
758
+ // Component-level color overrides: these map component tokens to
759
759
  // dedicated CSS variables that the widget CSS reads for individual elements.
760
760
  cssVars['--persona-header-bg'] =
761
761
  cssVars['--persona-components-header-background'] ?? cssVars['--persona-surface'];
@@ -776,7 +776,7 @@ export function themeToCssVariables(theme: PersonaTheme): Record<string, string>
776
776
  if (headerTokens?.shadow) cssVars['--persona-header-shadow'] = headerTokens.shadow;
777
777
  if (headerTokens?.borderBottom) cssVars['--persona-header-border-bottom'] = headerTokens.borderBottom;
778
778
 
779
- // Intro card aliases short names the panel inline-styles read directly.
779
+ // Intro card aliases: short names the panel inline-styles read directly.
780
780
  // The full-path `--persona-components-introCard-*` variables auto-emit above;
781
781
  // these mirror them with sensible fallbacks so existing pages keep their look.
782
782
  const introCardTokens = theme.components?.introCard;
@@ -55,7 +55,7 @@ export class VirtualScroller {
55
55
  setTotalCount(count: number): void {
56
56
  this.totalCount = count;
57
57
  this.spacer.style.height = `${count * this.rowHeight}px`;
58
- // Always invalidate cached rows data may have changed even at the same count
58
+ // Always invalidate cached rows: data may have changed even at the same count
59
59
  for (const [, el] of this.visibleRows) {
60
60
  el.remove();
61
61
  }
@@ -34,15 +34,15 @@ and never touches a package resolver. Consumers who never import the
34
34
 
35
35
  ## Files
36
36
 
37
- - `index.js` upstream `dist/index.mjs`, with two local edits (see header comment).
38
- - `index.d.ts` upstream `dist/index.d.mts` verbatim (sourceMappingURL stripped).
39
- - `LICENSE` upstream MIT license.
37
+ - `index.js`: upstream `dist/index.mjs`, with two local edits (see header comment).
38
+ - `index.d.ts`: upstream `dist/index.d.mts` verbatim (sourceMappingURL stripped).
39
+ - `LICENSE`: upstream MIT license.
40
40
 
41
41
  ## Local modifications to `index.js`
42
42
 
43
43
  The **only** changes from upstream `dist/index.mjs` are:
44
44
 
45
- 1. Removed the top-level `import { createRequire } from "node:module";` a
45
+ 1. Removed the top-level `import { createRequire } from "node:module";`: a
46
46
  Node-only builtin that breaks browser bundling.
47
47
  2. Replaced `var __require = createRequire(import.meta.url);` with
48
48
  `var __require = void 0;`. `__require` is referenced only inside a guarded
@@ -1,5 +1,5 @@
1
1
  /**
2
- * VENDORED type declarations @mcp-b/smart-dom-reader v2.3.1 (MIT).
2
+ * VENDORED type declarations: @mcp-b/smart-dom-reader v2.3.1 (MIT).
3
3
  * Copied verbatim from upstream dist/index.d.mts (only the trailing sourceMappingURL
4
4
  * comment removed). See ./index.js and ./README.md for why this is vendored.
5
5
  */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * VENDORED @mcp-b/smart-dom-reader v2.3.1 (MIT, (c) 2025 mcp-b contributors).
2
+ * VENDORED: @mcp-b/smart-dom-reader v2.3.1 (MIT, (c) 2025 mcp-b contributors).
3
3
  * Source: https://github.com/WebMCP-org/npm-packages/tree/main/packages/smart-dom-reader
4
4
  *
5
5
  * Vendored rather than installed as a dependency because every published version
@@ -864,7 +864,7 @@ function renderRegionInfo(region) {
864
864
  if (region.linkCount) stats.push(`${region.linkCount} links`);
865
865
  if (region.inputCount) stats.push(`${region.inputCount} inputs`);
866
866
  if (region.textPreview) stats.push(`“${truncate(region.textPreview, 80)}”`);
867
- const statsLine = stats.length ? ` ${stats.join(", ")}` : "";
867
+ const statsLine = stats.length ? `: ${stats.join(", ")}` : "";
868
868
  return `${icon} ${label} → \`${region.selector}\` [${id}]${statsLine}`;
869
869
  }
870
870
  function wrapXml(body, meta, type = "section") {
package/src/version.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  // Named import (not default) so esbuild tree-shakes the JSON module down to
2
- // the one field we read a default import inlines the entire package.json
2
+ // the one field we read: a default import inlines the entire package.json
3
3
  // (~4.8 kB minified) into every bundle.
4
4
  import { version } from "../package.json";
5
5