@runtypelabs/persona 4.7.0 → 4.9.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 (185) hide show
  1. package/README.md +1 -0
  2. package/dist/animations/glyph-cycle.cjs +2 -2
  3. package/dist/animations/glyph-cycle.d.cts +1 -1
  4. package/dist/animations/glyph-cycle.d.ts +1 -1
  5. package/dist/animations/glyph-cycle.js +2 -2
  6. package/dist/animations/{types-CSmiKRVa.d.cts → types-4ROVJ1gA.d.cts} +85 -3
  7. package/dist/animations/{types-CSmiKRVa.d.ts → types-4ROVJ1gA.d.ts} +85 -3
  8. package/dist/animations/wipe.cjs +2 -2
  9. package/dist/animations/wipe.d.cts +1 -1
  10. package/dist/animations/wipe.d.ts +1 -1
  11. package/dist/chunk-5EIIHQLQ.js +1 -0
  12. package/dist/chunk-IO5VVUKP.js +3 -0
  13. package/dist/chunk-IPVK3KOM.js +1 -0
  14. package/dist/chunk-UPO4GUFC.js +1 -0
  15. package/dist/codegen.cjs +13 -13
  16. package/dist/codegen.js +14 -14
  17. package/dist/context-mentions-7S5KVUTG.js +169 -0
  18. package/dist/context-mentions-inline-TTCN7ZM2.js +4 -0
  19. package/dist/context-mentions-inline.cjs +4 -0
  20. package/dist/context-mentions-inline.d.cts +203 -0
  21. package/dist/context-mentions-inline.d.ts +203 -0
  22. package/dist/context-mentions-inline.js +4 -0
  23. package/dist/context-mentions.cjs +295 -0
  24. package/dist/context-mentions.d.cts +7025 -0
  25. package/dist/context-mentions.d.ts +7025 -0
  26. package/dist/context-mentions.js +295 -0
  27. package/dist/index.cjs +99 -68
  28. package/dist/index.cjs.map +1 -1
  29. package/dist/index.d.cts +1507 -182
  30. package/dist/index.d.ts +1507 -182
  31. package/dist/index.global.js +88 -56
  32. package/dist/index.global.js.map +1 -1
  33. package/dist/index.js +99 -68
  34. package/dist/index.js.map +1 -1
  35. package/dist/install.global.js +1 -1
  36. package/dist/install.global.js.map +1 -1
  37. package/dist/launcher.global.js +3 -2
  38. package/dist/launcher.global.js.map +1 -1
  39. package/dist/markdown-parsers.js +24 -24
  40. package/dist/plugin-kit.cjs +1 -1
  41. package/dist/plugin-kit.d.cts +17 -0
  42. package/dist/plugin-kit.d.ts +17 -0
  43. package/dist/plugin-kit.js +1 -1
  44. package/dist/runtype-tts-entry-UJAEF7NZ.js +1 -0
  45. package/dist/runtype-tts.js +1 -1
  46. package/dist/session-reconnect-JKIJBHS5.js +1 -0
  47. package/dist/smart-dom-reader.cjs +18 -17
  48. package/dist/smart-dom-reader.d.cts +1260 -16
  49. package/dist/smart-dom-reader.d.ts +1260 -16
  50. package/dist/smart-dom-reader.js +18 -17
  51. package/dist/testing.cjs +3 -3
  52. package/dist/testing.js +3 -3
  53. package/dist/theme-editor-preview.cjs +260 -58
  54. package/dist/theme-editor-preview.d.cts +1234 -15
  55. package/dist/theme-editor-preview.d.ts +1234 -15
  56. package/dist/theme-editor-preview.js +87 -58
  57. package/dist/theme-editor.cjs +10 -10
  58. package/dist/theme-editor.d.cts +1226 -15
  59. package/dist/theme-editor.d.ts +1226 -15
  60. package/dist/theme-editor.js +10 -10
  61. package/dist/theme-reference.cjs +1 -1
  62. package/dist/theme-reference.d.cts +76 -0
  63. package/dist/theme-reference.d.ts +76 -0
  64. package/dist/theme-reference.js +1 -1
  65. package/dist/voice-worklet-player.cjs +2 -2
  66. package/dist/voice-worklet-player.js +2 -2
  67. package/dist/webmcp-polyfill.js +2 -2
  68. package/dist/widget.css +1 -1
  69. package/package.json +16 -5
  70. package/src/artifacts-session.test.ts +178 -0
  71. package/src/client.test.ts +255 -1
  72. package/src/client.ts +157 -60
  73. package/src/components/artifact-card.test.ts +333 -0
  74. package/src/components/artifact-card.ts +75 -28
  75. package/src/components/artifact-inline.test.ts +1328 -0
  76. package/src/components/artifact-inline.ts +920 -0
  77. package/src/components/artifact-pane.test.ts +1042 -0
  78. package/src/components/artifact-pane.ts +440 -131
  79. package/src/components/artifact-preview.test.ts +1155 -0
  80. package/src/components/artifact-preview.ts +994 -0
  81. package/src/components/composer-parts.ts +3 -12
  82. package/src/components/context-mention-button.test.ts +70 -0
  83. package/src/components/context-mention-button.ts +82 -0
  84. package/src/components/context-mention-chip.ts +134 -0
  85. package/src/components/context-mention-menu.test.ts +508 -0
  86. package/src/components/context-mention-menu.ts +0 -0
  87. package/src/components/message-bubble.test.ts +175 -0
  88. package/src/components/message-bubble.ts +177 -19
  89. package/src/components/panel.ts +7 -10
  90. package/src/components/pill-composer-builder.test.ts +6 -2
  91. package/src/components/pill-composer-builder.ts +6 -6
  92. package/src/components/reasoning-bubble.ts +1 -13
  93. package/src/components/registry.ts +38 -3
  94. package/src/components/tool-bubble.ts +1 -13
  95. package/src/context-mentions-bundle.test.ts +163 -0
  96. package/src/context-mentions-entry.ts +185 -0
  97. package/src/context-mentions-inline-entry.test.ts +136 -0
  98. package/src/context-mentions-inline-entry.ts +226 -0
  99. package/src/context-mentions-inline-loader.test.ts +30 -0
  100. package/src/context-mentions-inline-loader.ts +36 -0
  101. package/src/context-mentions-inline.ts +15 -0
  102. package/src/context-mentions-loader.ts +32 -0
  103. package/src/context-mentions.ts +16 -0
  104. package/src/defaults.ts +1 -0
  105. package/src/generated/runtype-openapi-contract.ts +55 -3
  106. package/src/index-core.ts +46 -1
  107. package/src/index-global.ts +51 -0
  108. package/src/index.ts +8 -0
  109. package/src/markdown-parsers-loader.test.ts +158 -0
  110. package/src/markdown-parsers-loader.ts +83 -27
  111. package/src/plugin-kit.test.ts +40 -0
  112. package/src/plugin-kit.ts +39 -5
  113. package/src/runtime/host-layout.test.ts +163 -0
  114. package/src/runtime/host-layout.ts +110 -7
  115. package/src/runtime/init.ts +18 -61
  116. package/src/session.mentions.test.ts +175 -0
  117. package/src/session.test.ts +52 -4
  118. package/src/session.ts +197 -27
  119. package/src/smart-dom-reader.test.ts +129 -2
  120. package/src/smart-dom-reader.ts +127 -1
  121. package/src/styles/context-mention-menu-css.ts +176 -0
  122. package/src/styles/widget.css +1002 -152
  123. package/src/theme-editor/preview.ts +2 -0
  124. package/src/theme-editor/sections.test.ts +26 -1
  125. package/src/theme-editor/sections.ts +13 -5
  126. package/src/theme-reference.ts +2 -2
  127. package/src/tool-call-display-defaults.test.ts +1 -0
  128. package/src/types/theme.ts +79 -0
  129. package/src/types.ts +1041 -17
  130. package/src/ui.artifact-pane-gating.test.ts +636 -0
  131. package/src/ui.component-directive.test.ts +104 -0
  132. package/src/ui.composer-bar.test.ts +60 -2
  133. package/src/ui.detached-panel.test.ts +1049 -0
  134. package/src/ui.mention-submit.test.ts +235 -0
  135. package/src/ui.tool-display.test.ts +51 -0
  136. package/src/ui.ts +998 -159
  137. package/src/utils/artifact-custom-actions.ts +128 -0
  138. package/src/utils/artifact-display.test.ts +42 -0
  139. package/src/utils/artifact-display.ts +84 -0
  140. package/src/utils/artifact-file.test.ts +116 -0
  141. package/src/utils/artifact-file.ts +117 -0
  142. package/src/utils/artifact-gate.test.ts +112 -5
  143. package/src/utils/artifact-gate.ts +39 -14
  144. package/src/utils/artifact-loading-status.ts +55 -0
  145. package/src/utils/artifact-status-label.ts +190 -0
  146. package/src/utils/buttons.ts +7 -1
  147. package/src/utils/chunk-loader.test.ts +97 -0
  148. package/src/utils/chunk-loader.ts +88 -0
  149. package/src/utils/code-highlight.test.ts +186 -0
  150. package/src/utils/code-highlight.ts +400 -0
  151. package/src/utils/composer-contenteditable.test.ts +507 -0
  152. package/src/utils/composer-contenteditable.ts +626 -0
  153. package/src/utils/composer-document.test.ts +280 -0
  154. package/src/utils/composer-document.ts +293 -0
  155. package/src/utils/composer-history.test.ts +35 -7
  156. package/src/utils/composer-history.ts +30 -20
  157. package/src/utils/composer-input.ts +159 -0
  158. package/src/utils/context-mention-controller.test.ts +1215 -0
  159. package/src/utils/context-mention-controller.ts +1186 -0
  160. package/src/utils/context-mention-manager.test.ts +422 -0
  161. package/src/utils/context-mention-manager.ts +410 -0
  162. package/src/utils/context-mention-orchestrator.test.ts +538 -0
  163. package/src/utils/context-mention-orchestrator.ts +348 -0
  164. package/src/utils/icons.ts +2 -0
  165. package/src/utils/live-region.test.ts +108 -0
  166. package/src/utils/live-region.ts +94 -0
  167. package/src/utils/mention-channels.ts +63 -0
  168. package/src/utils/mention-llm-format.test.ts +91 -0
  169. package/src/utils/mention-llm-format.ts +79 -0
  170. package/src/utils/mention-matcher.test.ts +86 -0
  171. package/src/utils/mention-matcher.ts +221 -0
  172. package/src/utils/mention-token.ts +72 -0
  173. package/src/utils/mention-trigger.test.ts +155 -0
  174. package/src/utils/mention-trigger.ts +156 -0
  175. package/src/utils/roving-tablist.test.ts +152 -0
  176. package/src/utils/roving-tablist.ts +111 -0
  177. package/src/utils/spinner.ts +45 -0
  178. package/src/utils/theme.test.ts +76 -4
  179. package/src/utils/theme.ts +7 -0
  180. package/src/utils/tokens.ts +118 -11
  181. package/src/utils/tool-loading-animation.test.ts +32 -0
  182. package/src/utils/tool-loading-animation.ts +24 -0
  183. package/dist/chunk-DFBSCFYN.js +0 -1
  184. package/dist/runtype-tts-entry-HFUV2UF7.js +0 -1
  185. package/dist/session-reconnect-U77QFUR7.js +0 -1
@@ -341,27 +341,18 @@ export const createAttachmentControls = (config?: AgentWidgetConfig): Attachment
341
341
  "aria-label": attachmentsConfig.buttonTooltipText ?? "Attach file",
342
342
  },
343
343
  style: {
344
+ // Appearance is themed from the CSS rule for `.persona-attachment-button`
345
+ // via the `--persona-button-ghost-*` tokens (components.button.ghost).
346
+ // Only config-driven sizing is inline.
344
347
  width: attachIconSize,
345
348
  height: attachIconSize,
346
349
  minWidth: attachIconSize,
347
350
  minHeight: attachIconSize,
348
351
  fontSize: "18px",
349
352
  lineHeight: "1",
350
- backgroundColor: "transparent",
351
- color: "var(--persona-primary, #111827)",
352
- border: "none",
353
- borderRadius: "6px",
354
- transition: "background-color 0.15s ease",
355
353
  },
356
354
  });
357
355
 
358
- button.addEventListener("mouseenter", () => {
359
- button.style.backgroundColor = "var(--persona-palette-colors-black-alpha-50, rgba(0, 0, 0, 0.05))";
360
- });
361
- button.addEventListener("mouseleave", () => {
362
- button.style.backgroundColor = "transparent";
363
- });
364
-
365
356
  const attachIconSvg = renderLucideIcon(attachIconName, attachIconSizeNum, "currentColor", 1.5);
366
357
  if (attachIconSvg) {
367
358
  button.appendChild(attachIconSvg);
@@ -0,0 +1,70 @@
1
+ // @vitest-environment jsdom
2
+
3
+ import { describe, it, expect, vi } from "vitest";
4
+ import { createMentionButton } from "./context-mention-button";
5
+ import { renderLucideIcon } from "../utils/icons";
6
+ import type { AgentWidgetContextMentionConfig } from "../types";
7
+
8
+ // Icon rendered inside the default 40px button: iconSize = round(40 * 0.6) = 24,
9
+ // strokeWidth 1.5 (see createMentionButton). Compare against the registry's own
10
+ // output so the assertion tracks lucide glyph changes instead of hard-coding SVG.
11
+ const refIcon = (name: string) =>
12
+ renderLucideIcon(name, 24, "currentColor", 1.5)!.innerHTML;
13
+
14
+ const config = (
15
+ overrides: Partial<AgentWidgetContextMentionConfig> = {}
16
+ ): AgentWidgetContextMentionConfig => ({ enabled: true, sources: [], ...overrides });
17
+
18
+ describe("createMentionButton", () => {
19
+ it("invokes onOpen on click and labels itself from config", () => {
20
+ const onOpen = vi.fn();
21
+ const { button, wrapper } = createMentionButton({
22
+ config: config({ buttonTooltipText: "Add context" }),
23
+ onOpen,
24
+ });
25
+ expect(button.getAttribute("aria-label")).toBe("Add context");
26
+ expect(wrapper.querySelector(".persona-send-button-tooltip")?.textContent).toBe(
27
+ "Add context"
28
+ );
29
+
30
+ button.dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
31
+ expect(onOpen).toHaveBeenCalledTimes(1);
32
+ });
33
+
34
+ it("defaults to a '+' add-context icon, not an '@' glyph", () => {
35
+ const { button } = createMentionButton({ config: config(), onOpen: vi.fn() });
36
+ const svg = button.querySelector("svg");
37
+ expect(svg).not.toBeNull();
38
+ // "@" reads as power-user; the consumer-recognized affordance is "+".
39
+ expect(svg!.innerHTML).toBe(refIcon("plus"));
40
+ expect(svg!.innerHTML).not.toBe(refIcon("at-sign"));
41
+ expect(button.textContent).not.toContain("@");
42
+ });
43
+
44
+ it("honors a buttonIconName override for power-user surfaces", () => {
45
+ const { button } = createMentionButton({
46
+ config: config({ buttonIconName: "at-sign" }),
47
+ onOpen: vi.fn(),
48
+ });
49
+ expect(button.querySelector("svg")!.innerHTML).toBe(refIcon("at-sign"));
50
+ });
51
+
52
+ it("stops the click from bubbling so the composer's focus-textarea handler never runs", () => {
53
+ const parent = document.createElement("form");
54
+ const parentClick = vi.fn();
55
+ parent.addEventListener("click", parentClick);
56
+ document.body.appendChild(parent);
57
+
58
+ const { wrapper } = createMentionButton({ config: config(), onOpen: vi.fn() });
59
+ parent.appendChild(wrapper);
60
+
61
+ wrapper
62
+ .querySelector("button")!
63
+ .dispatchEvent(new MouseEvent("click", { bubbles: true, cancelable: true }));
64
+
65
+ // The button's handler calls stopPropagation, so the parent (which stands in
66
+ // for the composer form's "click anywhere → focus textarea" listener) is
67
+ // never reached — the picker's search field keeps focus.
68
+ expect(parentClick).not.toHaveBeenCalled();
69
+ });
70
+ });
@@ -0,0 +1,82 @@
1
+ import { createElement, createNode } from "../utils/dom";
2
+ import { renderLucideIcon } from "../utils/icons";
3
+ import type { AgentWidgetContextMentionConfig } from "../types";
4
+
5
+ export interface MentionButtonParts {
6
+ button: HTMLButtonElement;
7
+ wrapper: HTMLElement;
8
+ }
9
+
10
+ /**
11
+ * The visible composer affordance ("add context") — the discoverable entry
12
+ * point that opens the mention menu. Lives in the CORE bundle (not the lazy
13
+ * chunk) so the button paints immediately when `contextMentions.enabled`, before
14
+ * the heavy menu runtime loads on first interaction.
15
+ *
16
+ * Styled to match `createAttachmentControls`' icon button for visual parity.
17
+ */
18
+ export function createMentionButton(opts: {
19
+ config: AgentWidgetContextMentionConfig;
20
+ buttonSize?: string;
21
+ onOpen: () => void;
22
+ }): MentionButtonParts {
23
+ const { config, onOpen } = opts;
24
+ const size = opts.buttonSize ?? "40px";
25
+ const sizeNum = parseFloat(size) || 40;
26
+ const iconSize = Math.round(sizeNum * 0.6);
27
+ // Default to a "+" signifier, not an "@" glyph: no major consumer chat app
28
+ // (ChatGPT, Claude, Gemini, Perplexity) puts a literal "@" button in the
29
+ // composer — "@" is a typed power-user accelerator, while "+"/"add context"
30
+ // is the recognized consumer affordance. Override via `buttonIconName`
31
+ // (e.g. "at-sign") on a power-user surface.
32
+ const iconName = config.buttonIconName ?? "plus";
33
+ const tooltipText = config.buttonTooltipText ?? "Add context";
34
+
35
+ const wrapper = createElement("div", "persona-send-button-wrapper");
36
+ const button = createNode("button", {
37
+ className:
38
+ "persona-rounded-button persona-flex persona-items-center persona-justify-center disabled:persona-opacity-50 persona-cursor-pointer persona-mention-button",
39
+ attrs: {
40
+ type: "button",
41
+ "data-persona-composer-mention-button": "",
42
+ "aria-label": tooltipText,
43
+ "aria-haspopup": "listbox",
44
+ // Reflects the picker's open state: the orchestrator flips aria-expanded
45
+ // and adds aria-controls (the menu's listbox id) via `onPickerOpenChange`
46
+ // when this button opens/closes the picker. Starts collapsed.
47
+ "aria-expanded": "false",
48
+ },
49
+ style: {
50
+ // Appearance (bg / fg / border / radius / hover) is themed from the CSS
51
+ // rule for `.persona-mention-button` via the `--persona-button-ghost-*`
52
+ // tokens (components.button.ghost). Only config-driven sizing is inline.
53
+ width: size,
54
+ height: size,
55
+ minWidth: size,
56
+ minHeight: size,
57
+ fontSize: "18px",
58
+ lineHeight: "1",
59
+ },
60
+ }) as HTMLButtonElement;
61
+
62
+ const icon = renderLucideIcon(iconName, iconSize, "currentColor", 1.5);
63
+ if (icon) button.appendChild(icon);
64
+ else button.textContent = "+";
65
+
66
+ button.addEventListener("click", (e) => {
67
+ e.preventDefault();
68
+ // Stop the click from bubbling to the composer form's "click anywhere →
69
+ // focus textarea" handler, which would otherwise steal focus from the
70
+ // picker's search field the moment it opens.
71
+ e.stopPropagation();
72
+ onOpen();
73
+ });
74
+
75
+ wrapper.appendChild(button);
76
+
77
+ const tooltip = createElement("div", "persona-send-button-tooltip");
78
+ tooltip.textContent = tooltipText;
79
+ wrapper.appendChild(tooltip);
80
+
81
+ return { button, wrapper };
82
+ }
@@ -0,0 +1,134 @@
1
+ import { createElement, createNode } from "../utils/dom";
2
+ import { renderLucideIcon } from "../utils/icons";
3
+ import type {
4
+ AgentWidgetContextMentionConfig,
5
+ AgentWidgetContextMentionPayload,
6
+ AgentWidgetContextMentionRef,
7
+ } from "../types";
8
+
9
+ export type MentionChipStatus = "resolving" | "ready" | "error";
10
+
11
+ export interface MentionChipParts {
12
+ el: HTMLElement;
13
+ /** Update lifecycle state; `payload` is forwarded to `renderMentionChip` once resolved. */
14
+ setStatus: (
15
+ status: MentionChipStatus,
16
+ payload?: AgentWidgetContextMentionPayload
17
+ ) => void;
18
+ }
19
+
20
+ /**
21
+ * A single compact pill chip (icon + label + ×, ~26px) for the composer context
22
+ * row. Mirrors the attachment chip's remove affordance but is a text pill, not a
23
+ * 48×48 thumbnail. Honors `config.renderMentionChip` when provided.
24
+ */
25
+ export function createMentionChip(opts: {
26
+ ref: AgentWidgetContextMentionRef;
27
+ config: AgentWidgetContextMentionConfig;
28
+ onRemove: () => void;
29
+ }): MentionChipParts {
30
+ const { ref, config, onRemove } = opts;
31
+
32
+ if (config.renderMentionChip) {
33
+ // Mid-level override: the host owns the markup. Status (and the resolved
34
+ // payload, once available) is reflected by re-invoking the renderer.
35
+ let status: MentionChipStatus = "resolving";
36
+ let payload: AgentWidgetContextMentionPayload | undefined;
37
+ let el = config.renderMentionChip({ ref, status, payload, remove: onRemove });
38
+ const setStatus = (
39
+ next: MentionChipStatus,
40
+ nextPayload?: AgentWidgetContextMentionPayload
41
+ ) => {
42
+ if (next === status && nextPayload === payload) return;
43
+ status = next;
44
+ payload = nextPayload;
45
+ const replacement = config.renderMentionChip!({
46
+ ref,
47
+ status,
48
+ payload,
49
+ remove: onRemove,
50
+ });
51
+ el.replaceWith(replacement);
52
+ el = replacement;
53
+ };
54
+ // Getter, not a snapshot: setStatus swaps the node, and the manager's
55
+ // remove()/clear() must target the live element, not the detached original.
56
+ return {
57
+ get el() {
58
+ return el;
59
+ },
60
+ setStatus,
61
+ };
62
+ }
63
+
64
+ const iconName = ref.iconName ?? config.chipIconName ?? "at-sign";
65
+
66
+ const chip = createNode("div", {
67
+ className: "persona-mention-chip",
68
+ attrs: {
69
+ "data-persona-mention-chip": "",
70
+ "data-status": "resolving",
71
+ // Full label on hover, since the pill truncates long labels with ellipsis.
72
+ title: ref.label,
73
+ },
74
+ });
75
+
76
+ const iconHost = createElement("span", "persona-mention-chip-icon");
77
+ const setIcon = (name: string) => {
78
+ iconHost.replaceChildren();
79
+ const svg = renderLucideIcon(name, 13, "currentColor", 2);
80
+ if (svg) iconHost.appendChild(svg);
81
+ };
82
+ setIcon(iconName);
83
+
84
+ // The spinner shown while the mention resolves; swapped for the icon on ready.
85
+ const spinner = createElement("span", "persona-mention-chip-spinner");
86
+
87
+ const label = createNode("span", {
88
+ className: "persona-mention-chip-label",
89
+ text: ref.label,
90
+ });
91
+
92
+ const removeBtn = createNode("button", {
93
+ className: "persona-mention-chip-remove",
94
+ attrs: {
95
+ type: "button",
96
+ "aria-label": `Remove ${ref.label} context`,
97
+ },
98
+ }) as HTMLButtonElement;
99
+ const x = renderLucideIcon("x", 11, "currentColor", 2.5);
100
+ if (x) removeBtn.appendChild(x);
101
+ else removeBtn.textContent = "×";
102
+ removeBtn.addEventListener("click", (e) => {
103
+ e.preventDefault();
104
+ e.stopPropagation();
105
+ onRemove();
106
+ });
107
+
108
+ chip.appendChild(spinner);
109
+ chip.appendChild(label);
110
+ chip.appendChild(removeBtn);
111
+
112
+ const setStatus = (status: MentionChipStatus) => {
113
+ chip.setAttribute("data-status", status);
114
+ if (status === "resolving") {
115
+ if (spinner.parentNode !== chip) chip.insertBefore(spinner, label);
116
+ if (iconHost.parentNode === chip) iconHost.remove();
117
+ chip.setAttribute("title", ref.label);
118
+ } else {
119
+ if (spinner.parentNode === chip) spinner.remove();
120
+ if (iconHost.parentNode !== chip) chip.insertBefore(iconHost, label);
121
+ if (status === "error") {
122
+ // Error is not color-only: swap to an alert icon and explain in the
123
+ // hover title (and the aria-label reflects it via the title too).
124
+ setIcon("triangle-alert");
125
+ chip.setAttribute("title", `Couldn't add ${ref.label} to context`);
126
+ } else {
127
+ setIcon(iconName);
128
+ chip.setAttribute("title", ref.label);
129
+ }
130
+ }
131
+ };
132
+
133
+ return { el: chip, setStatus };
134
+ }