@runtypelabs/persona 4.8.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 (117) hide show
  1. package/README.md +1 -0
  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-BsZtXPKK.d.cts → types-4ROVJ1gA.d.cts} +42 -0
  5. package/dist/animations/{types-BsZtXPKK.d.ts → types-4ROVJ1gA.d.ts} +42 -0
  6. package/dist/animations/wipe.d.cts +1 -1
  7. package/dist/animations/wipe.d.ts +1 -1
  8. package/dist/chunk-IO5VVUKP.js +3 -0
  9. package/dist/chunk-IPVK3KOM.js +1 -0
  10. package/dist/chunk-UPO4GUFC.js +1 -0
  11. package/dist/codegen.cjs +6 -6
  12. package/dist/codegen.js +8 -8
  13. package/dist/context-mentions-7S5KVUTG.js +169 -0
  14. package/dist/context-mentions-inline-TTCN7ZM2.js +4 -0
  15. package/dist/context-mentions-inline.cjs +4 -0
  16. package/dist/context-mentions-inline.d.cts +203 -0
  17. package/dist/context-mentions-inline.d.ts +203 -0
  18. package/dist/context-mentions-inline.js +4 -0
  19. package/dist/context-mentions.cjs +295 -0
  20. package/dist/context-mentions.d.cts +7025 -0
  21. package/dist/context-mentions.d.ts +7025 -0
  22. package/dist/context-mentions.js +295 -0
  23. package/dist/index.cjs +59 -51
  24. package/dist/index.cjs.map +1 -1
  25. package/dist/index.d.cts +622 -2
  26. package/dist/index.d.ts +622 -2
  27. package/dist/index.global.js +54 -46
  28. package/dist/index.global.js.map +1 -1
  29. package/dist/index.js +59 -51
  30. package/dist/index.js.map +1 -1
  31. package/dist/launcher.global.js +2 -2
  32. package/dist/launcher.global.js.map +1 -1
  33. package/dist/plugin-kit.cjs +1 -1
  34. package/dist/plugin-kit.d.cts +17 -0
  35. package/dist/plugin-kit.d.ts +17 -0
  36. package/dist/plugin-kit.js +1 -1
  37. package/dist/smart-dom-reader.cjs +17 -16
  38. package/dist/smart-dom-reader.d.cts +507 -1
  39. package/dist/smart-dom-reader.d.ts +507 -1
  40. package/dist/smart-dom-reader.js +17 -16
  41. package/dist/theme-editor-preview.cjs +234 -55
  42. package/dist/theme-editor-preview.d.cts +473 -0
  43. package/dist/theme-editor-preview.d.ts +473 -0
  44. package/dist/theme-editor-preview.js +48 -42
  45. package/dist/theme-editor.cjs +7 -7
  46. package/dist/theme-editor.d.cts +473 -0
  47. package/dist/theme-editor.d.ts +473 -0
  48. package/dist/theme-editor.js +5 -5
  49. package/dist/theme-reference.cjs +1 -1
  50. package/dist/theme-reference.d.cts +2 -0
  51. package/dist/theme-reference.d.ts +2 -0
  52. package/dist/widget.css +1 -1
  53. package/package.json +15 -3
  54. package/src/client.test.ts +69 -0
  55. package/src/client.ts +65 -51
  56. package/src/components/composer-parts.ts +3 -12
  57. package/src/components/context-mention-button.test.ts +70 -0
  58. package/src/components/context-mention-button.ts +82 -0
  59. package/src/components/context-mention-chip.ts +134 -0
  60. package/src/components/context-mention-menu.test.ts +508 -0
  61. package/src/components/context-mention-menu.ts +0 -0
  62. package/src/components/message-bubble.test.ts +175 -0
  63. package/src/components/message-bubble.ts +177 -19
  64. package/src/components/panel.ts +7 -10
  65. package/src/context-mentions-bundle.test.ts +163 -0
  66. package/src/context-mentions-entry.ts +185 -0
  67. package/src/context-mentions-inline-entry.test.ts +136 -0
  68. package/src/context-mentions-inline-entry.ts +226 -0
  69. package/src/context-mentions-inline-loader.test.ts +30 -0
  70. package/src/context-mentions-inline-loader.ts +36 -0
  71. package/src/context-mentions-inline.ts +15 -0
  72. package/src/context-mentions-loader.ts +32 -0
  73. package/src/context-mentions.ts +16 -0
  74. package/src/index-core.ts +26 -0
  75. package/src/index-global.ts +51 -0
  76. package/src/markdown-parsers-loader.ts +35 -44
  77. package/src/plugin-kit.test.ts +40 -0
  78. package/src/plugin-kit.ts +39 -5
  79. package/src/session.mentions.test.ts +175 -0
  80. package/src/session.test.ts +52 -4
  81. package/src/session.ts +121 -5
  82. package/src/smart-dom-reader.test.ts +129 -2
  83. package/src/smart-dom-reader.ts +127 -1
  84. package/src/styles/context-mention-menu-css.ts +176 -0
  85. package/src/styles/widget.css +229 -126
  86. package/src/theme-editor/sections.ts +3 -3
  87. package/src/types/theme.ts +2 -0
  88. package/src/types.ts +525 -0
  89. package/src/ui.mention-submit.test.ts +235 -0
  90. package/src/ui.ts +301 -51
  91. package/src/utils/chunk-loader.test.ts +97 -0
  92. package/src/utils/chunk-loader.ts +88 -0
  93. package/src/utils/composer-contenteditable.test.ts +507 -0
  94. package/src/utils/composer-contenteditable.ts +626 -0
  95. package/src/utils/composer-document.test.ts +280 -0
  96. package/src/utils/composer-document.ts +293 -0
  97. package/src/utils/composer-history.test.ts +35 -7
  98. package/src/utils/composer-history.ts +30 -20
  99. package/src/utils/composer-input.ts +159 -0
  100. package/src/utils/context-mention-controller.test.ts +1215 -0
  101. package/src/utils/context-mention-controller.ts +1186 -0
  102. package/src/utils/context-mention-manager.test.ts +422 -0
  103. package/src/utils/context-mention-manager.ts +410 -0
  104. package/src/utils/context-mention-orchestrator.test.ts +538 -0
  105. package/src/utils/context-mention-orchestrator.ts +348 -0
  106. package/src/utils/live-region.test.ts +108 -0
  107. package/src/utils/live-region.ts +94 -0
  108. package/src/utils/mention-channels.ts +63 -0
  109. package/src/utils/mention-llm-format.test.ts +91 -0
  110. package/src/utils/mention-llm-format.ts +79 -0
  111. package/src/utils/mention-matcher.test.ts +86 -0
  112. package/src/utils/mention-matcher.ts +221 -0
  113. package/src/utils/mention-token.ts +72 -0
  114. package/src/utils/mention-trigger.test.ts +155 -0
  115. package/src/utils/mention-trigger.ts +156 -0
  116. package/src/utils/theme.test.ts +28 -4
  117. package/src/utils/tokens.ts +27 -11
@@ -4,10 +4,11 @@
4
4
  // vendored library under jsdom. The pure-mapper correctness guarantee lives in
5
5
  // utils/smart-dom-adapter.test.ts (no DOM, no library); this test confirms the
6
6
  // vendored runtime loads and the provider wires up against a real document.
7
- import { describe, it, expect, beforeEach, afterEach } from "vitest";
7
+ import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
8
8
  import {
9
9
  collectSmartDomContext,
10
- createSmartDomReaderContextProvider
10
+ createSmartDomReaderContextProvider,
11
+ createSmartDomMentionSource
11
12
  } from "./smart-dom-reader";
12
13
 
13
14
  // jsdom implements no layout, so getBoundingClientRect()/offsetParent report the
@@ -116,6 +117,66 @@ describe("smart-dom-reader entry (jsdom)", () => {
116
117
  expect(texts).toContain("Scoped shadow action");
117
118
  });
118
119
 
120
+ it("mention source surfaces page elements as items and resolves text at submit", async () => {
121
+ document.body.innerHTML = `<main><button id="go">Continue to checkout</button></main>`;
122
+ const source = createSmartDomMentionSource({ label: "Page", ...JSDOM_OPTS });
123
+ expect(source.id).toBe("page");
124
+ expect(source.resolveOn).toBe("submit");
125
+
126
+ const items = await source.search("", { messages: [], config: {} as never, signal: new AbortController().signal });
127
+ const go = items.find((i) => i.label.includes("Continue"));
128
+ expect(go).toBeTruthy();
129
+ expect(go!.id).toMatch(/#go|go/); // selector is the item id
130
+
131
+ // Filtering narrows the snapshot client-side.
132
+ const filtered = await source.search("checkout", { messages: [], config: {} as never, signal: new AbortController().signal });
133
+ expect(filtered.some((i) => i.label.includes("Continue"))).toBe(true);
134
+
135
+ // resolve() reads the live element text at submit.
136
+ const payload = await source.resolve(go!, {
137
+ messages: [],
138
+ config: {} as never,
139
+ composerText: "",
140
+ args: "",
141
+ signal: new AbortController().signal,
142
+ });
143
+ expect(payload.llmAppend).toContain("Continue to checkout");
144
+ expect(payload.context).toMatchObject({ selector: go!.id });
145
+ });
146
+
147
+ it("applies mapItem to reshape surfaced items without breaking resolve", async () => {
148
+ document.body.innerHTML = `<main><button id="go">Continue to checkout</button></main>`;
149
+ const source = createSmartDomMentionSource({
150
+ label: "Page",
151
+ ...JSDOM_OPTS,
152
+ mapItem: (el, defaultItem) => ({
153
+ ...defaultItem,
154
+ iconName: "star",
155
+ description: `custom:${el.tagName}`,
156
+ }),
157
+ });
158
+
159
+ const items = await source.search("", {
160
+ messages: [],
161
+ config: {} as never,
162
+ signal: new AbortController().signal,
163
+ });
164
+ const go = items.find((i) => i.label.includes("Continue"));
165
+ expect(go).toBeTruthy();
166
+ expect(go!.iconName).toBe("star");
167
+ expect(go!.description).toMatch(/^custom:/i);
168
+
169
+ // id stays the selector, so submit-time resolve still reads the live element.
170
+ const payload = await source.resolve(go!, {
171
+ messages: [],
172
+ config: {} as never,
173
+ composerText: "",
174
+ args: "",
175
+ signal: new AbortController().signal,
176
+ });
177
+ expect(payload.llmAppend).toContain("Continue to checkout");
178
+ });
179
+
119
180
  it("provider returns formatted context under the configured key", async () => {
120
181
  document.body.innerHTML = `<main><button id="go">Continue</button></main>`;
121
182
  const provider = createSmartDomReaderContextProvider({
@@ -132,4 +193,70 @@ describe("smart-dom-reader entry (jsdom)", () => {
132
193
  document.body.innerHTML = "";
133
194
  expect(collectSmartDomContext(JSDOM_OPTS)).toEqual([]);
134
195
  });
196
+
197
+ const searchCtx = () => ({
198
+ messages: [],
199
+ config: {} as never,
200
+ signal: new AbortController().signal,
201
+ });
202
+
203
+ it("reuses the snapshot across empty-query searches within the TTL (scans once)", async () => {
204
+ document.body.innerHTML = `<main><button id="go">Continue to checkout</button></main>`;
205
+ const source = createSmartDomMentionSource({ label: "Page", ...JSDOM_OPTS });
206
+
207
+ // Count extractions by observing DOM reads: a spy on querySelectorAll fires
208
+ // per scan. Simpler and TTL-independent: mutate the DOM between the two empty
209
+ // searches and assert the second still returns the pre-mutation snapshot.
210
+ const first = await source.search("", searchCtx());
211
+ expect(first.some((i) => i.label.includes("Continue"))).toBe(true);
212
+
213
+ // Change the page; a rescan would surface the new element.
214
+ document.body.innerHTML = `<main><button id="new">Brand new action</button></main>`;
215
+ const second = await source.search("", searchCtx());
216
+ // Still the cached snapshot: no rescan happened within the TTL.
217
+ expect(second.some((i) => i.label.includes("Continue"))).toBe(true);
218
+ expect(second.some((i) => i.label.includes("Brand new"))).toBe(false);
219
+ });
220
+
221
+ it("rescans on an empty query once the TTL has elapsed", async () => {
222
+ vi.useFakeTimers();
223
+ try {
224
+ document.body.innerHTML = `<main><button id="go">Continue to checkout</button></main>`;
225
+ const source = createSmartDomMentionSource({ label: "Page", ...JSDOM_OPTS });
226
+
227
+ const first = await source.search("", searchCtx());
228
+ expect(first.some((i) => i.label.includes("Continue"))).toBe(true);
229
+
230
+ // Mutate the page, then advance past the 2s TTL so the next empty query rescans.
231
+ document.body.innerHTML = `<main><button id="new">Brand new action</button></main>`;
232
+ vi.advanceTimersByTime(2500);
233
+
234
+ const second = await source.search("", searchCtx());
235
+ expect(second.some((i) => i.label.includes("Brand new"))).toBe(true);
236
+ expect(second.some((i) => i.label.includes("Continue"))).toBe(false);
237
+ } finally {
238
+ vi.useRealTimers();
239
+ }
240
+ });
241
+
242
+ it("resolves an item from a cached snapshot against the live element", async () => {
243
+ document.body.innerHTML = `<main><button id="go">Continue to checkout</button></main>`;
244
+ const source = createSmartDomMentionSource({ label: "Page", ...JSDOM_OPTS });
245
+
246
+ // Prime the snapshot, then re-search (cached) to get the item without rescanning.
247
+ await source.search("", searchCtx());
248
+ const items = await source.search("checkout", searchCtx());
249
+ const go = items.find((i) => i.label.includes("Continue"));
250
+ expect(go).toBeTruthy();
251
+
252
+ const payload = await source.resolve(go!, {
253
+ messages: [],
254
+ config: {} as never,
255
+ composerText: "",
256
+ args: "",
257
+ signal: new AbortController().signal,
258
+ });
259
+ expect(payload.llmAppend).toContain("Continue to checkout");
260
+ expect(payload.context).toMatchObject({ selector: go!.id });
261
+ });
135
262
  });
@@ -40,10 +40,16 @@ import {
40
40
  type SmartDomAdapterOptions
41
41
  } from "./utils/smart-dom-adapter";
42
42
  import { formatEnrichedContext, type EnrichedPageElement } from "./utils/dom-context";
43
- import type { AgentWidgetContextProvider } from "./types";
43
+ import { defaultMentionFilter } from "./utils/mention-matcher";
44
+ import type {
45
+ AgentWidgetContextProvider,
46
+ AgentWidgetContextMentionItem,
47
+ AgentWidgetContextMentionSource,
48
+ } from "./types";
44
49
 
45
50
  export { smartDomResultToEnriched };
46
51
  export type { SmartDomAdapterOptions } from "./utils/smart-dom-adapter";
52
+ export type { EnrichedPageElement } from "./utils/dom-context";
47
53
 
48
54
  /** Options for {@link collectSmartDomContext} and {@link createSmartDomReaderContextProvider}. */
49
55
  export interface SmartDomContextOptions extends SmartDomAdapterOptions {
@@ -132,3 +138,123 @@ export function createSmartDomReaderContextProvider(
132
138
  return { [contextKey]: formatEnrichedContext(elements) };
133
139
  };
134
140
  }
141
+
142
+ /** Options for {@link createSmartDomMentionSource}. */
143
+ export interface SmartDomMentionSourceOptions extends SmartDomContextOptions {
144
+ /** Source id. Default: "page". */
145
+ id?: string;
146
+ /** Group header shown in the menu. Default: "Page". */
147
+ label?: string;
148
+ /**
149
+ * Reshape each surfaced element into a mention item. Receives the raw
150
+ * {@link EnrichedPageElement} and the default-mapped item, so you can tweak
151
+ * one field (`{ ...defaultItem, iconName: "star" }`) or build from scratch.
152
+ * The returned item's `id` MUST stay a selector `resolve()` can read at submit
153
+ * (default: `el.selector`) unless you also override resolution. @default built-in mapping
154
+ */
155
+ mapItem?: (
156
+ el: EnrichedPageElement,
157
+ defaultItem: AgentWidgetContextMentionItem
158
+ ) => AgentWidgetContextMentionItem;
159
+ }
160
+
161
+ // Empty-query snapshot reuse window: the full Shadow-DOM-piercing scan is
162
+ // synchronous and runs inside the keystroke handler, so rescanning on every `@`
163
+ // / backspace-to-empty stalls the input. Reuse a snapshot this fresh; rescan
164
+ // past it so a menu opened after page changes still sees current content.
165
+ const SNAPSHOT_TTL_MS = 2000;
166
+
167
+ const iconForInteractivity = (kind: EnrichedPageElement["interactivity"]): string => {
168
+ switch (kind) {
169
+ case "clickable":
170
+ return "mouse-pointer-click";
171
+ case "input":
172
+ return "text-cursor-input";
173
+ case "navigable":
174
+ return "link";
175
+ default:
176
+ return "text";
177
+ }
178
+ };
179
+
180
+ const elementToMentionItem = (
181
+ el: EnrichedPageElement
182
+ ): AgentWidgetContextMentionItem => {
183
+ const ariaLabel = el.attributes["aria-label"];
184
+ const raw = (ariaLabel || el.text || el.tagName).trim();
185
+ const label = raw.length > 48 ? `${raw.slice(0, 47)}…` : raw || el.tagName;
186
+ const descParts = [el.role ?? el.tagName, el.interactivity].filter(Boolean);
187
+ return {
188
+ id: el.selector, // stable key + the selector resolve() reads at submit
189
+ label,
190
+ description: descParts.join(" · "),
191
+ iconName: iconForInteractivity(el.interactivity),
192
+ group: undefined,
193
+ };
194
+ };
195
+
196
+ /**
197
+ * First-class **supported** mention source backed by smart-dom-reader: surfaces
198
+ * visible page elements (Shadow-DOM-piercing) as mentionable items, resolving a
199
+ * fresh snapshot of the chosen element's text at SUBMIT (`resolveOn: "submit"`),
200
+ * since the page is time-sensitive. The element list is snapshotted when the
201
+ * menu opens (empty query) and filtered client-side with `defaultMentionFilter`
202
+ * as the user types.
203
+ *
204
+ * @example
205
+ * ```ts
206
+ * import { createSmartDomMentionSource } from "@runtypelabs/persona/smart-dom-reader";
207
+ *
208
+ * initAgentWidget({
209
+ * contextMentions: { enabled: true, sources: [createSmartDomMentionSource()] },
210
+ * });
211
+ * ```
212
+ */
213
+ export function createSmartDomMentionSource(
214
+ opts: SmartDomMentionSourceOptions = {}
215
+ ): AgentWidgetContextMentionSource {
216
+ const id = opts.id ?? "page";
217
+ const label = opts.label ?? "Page";
218
+ let snapshot: AgentWidgetContextMentionItem[] | null = null;
219
+ let capturedAt = 0;
220
+
221
+ const rescan = (): AgentWidgetContextMentionItem[] => {
222
+ const items = collectSmartDomContext(opts).map((el) => {
223
+ const defaultItem = elementToMentionItem(el);
224
+ return opts.mapItem ? opts.mapItem(el, defaultItem) : defaultItem;
225
+ });
226
+ capturedAt = Date.now();
227
+ return items;
228
+ };
229
+
230
+ return {
231
+ id,
232
+ label,
233
+ resolveOn: "submit",
234
+ search: (query) => {
235
+ // Rescan on empty query only when there's no snapshot or it's staler than
236
+ // the TTL; reopening the menu quickly reuses the snapshot, keystrokes always
237
+ // filter the existing one, so we never re-scan mid-typing.
238
+ const stale = !snapshot || Date.now() - capturedAt > SNAPSHOT_TTL_MS;
239
+ if (!snapshot || (query === "" && stale)) {
240
+ snapshot = rescan();
241
+ }
242
+ return defaultMentionFilter(snapshot, query);
243
+ },
244
+ resolve: (item) => {
245
+ const doc =
246
+ opts.document ?? (typeof document !== "undefined" ? document : undefined);
247
+ let text = "";
248
+ try {
249
+ const el = doc?.querySelector(item.id);
250
+ text = el?.textContent?.trim() ?? "";
251
+ } catch {
252
+ /* invalid selector at resolve time — fall through to label only */
253
+ }
254
+ return {
255
+ llmAppend: `Page element "${item.label}" (${item.id}):\n${text || "(no text)"}`,
256
+ context: { selector: item.id },
257
+ };
258
+ },
259
+ };
260
+ }
@@ -0,0 +1,176 @@
1
+ /**
2
+ * CSS for the context-mention MENU (floating popover): search field, grouped
3
+ * result list, options, status/empty/error rows, retry button, and the
4
+ * coarse-pointer touch sizing for its rows.
5
+ *
6
+ * This lives in the lazy `context-mentions` chunk (not `widget.css`) and is
7
+ * injected on first menu open via `injectStyles`. Two reasons:
8
+ * 1. it keeps ~1 kB of menu-only CSS out of the eager core stylesheet;
9
+ * 2. the menu mounts into the popover container (document.body by default),
10
+ * which is OUTSIDE a `useShadowDom` widget's shadow root — where the
11
+ * shadow-scoped `widget.css` never reached it. Injecting at mount time puts
12
+ * the rules in whichever root the menu actually lives in.
13
+ *
14
+ * The chip / context-row / affordance-button rules stay in `widget.css`: they
15
+ * render eagerly (restored-message chips, the always-visible add-context button)
16
+ * before this chunk loads.
17
+ */
18
+ export const MENTION_MENU_CSS = `
19
+ .persona-mention-menu {
20
+ box-sizing: border-box;
21
+ display: flex;
22
+ flex-direction: column;
23
+ max-height: var(--persona-mention-menu-max-height, 280px);
24
+ overflow: hidden;
25
+ background: var(--persona-mention-menu-bg, var(--persona-surface, #ffffff));
26
+ border: 1px solid var(--persona-mention-menu-border, var(--persona-border, #e5e7eb));
27
+ border-radius: var(--persona-mention-menu-radius, 10px);
28
+ box-shadow: var(--persona-mention-menu-shadow, 0 8px 28px rgba(0, 0, 0, 0.12));
29
+ font-family: var(--persona-font-family, inherit);
30
+ }
31
+ .persona-mention-list {
32
+ min-height: 0;
33
+ overflow-y: auto;
34
+ padding: 4px;
35
+ }
36
+ .persona-mention-search {
37
+ display: flex;
38
+ align-items: center;
39
+ gap: 6px;
40
+ padding: 8px 10px;
41
+ border-bottom: 1px solid var(--persona-mention-menu-border, var(--persona-border, #e5e7eb));
42
+ }
43
+ .persona-mention-search-icon {
44
+ display: inline-flex;
45
+ align-items: center;
46
+ flex: 0 0 auto;
47
+ color: var(--persona-mention-group-fg, var(--persona-muted, #6b7280));
48
+ }
49
+ .persona-mention-search-input {
50
+ flex: 1 1 auto;
51
+ min-width: 0;
52
+ padding: 0;
53
+ border: none;
54
+ outline: none;
55
+ background: transparent;
56
+ font-family: inherit;
57
+ /* 16px, not 14px: iOS Safari zooms the page when focusing an input under 16px. */
58
+ font-size: 16px;
59
+ line-height: 1.4;
60
+ color: var(--persona-text, #111827);
61
+ }
62
+ .persona-mention-search-input::placeholder {
63
+ color: var(--persona-mention-group-fg, var(--persona-muted, #6b7280));
64
+ }
65
+ .persona-mention-group + .persona-mention-group {
66
+ margin-top: 2px;
67
+ border-top: 1px solid var(--persona-mention-menu-border, var(--persona-border, #f1f1f1));
68
+ padding-top: 2px;
69
+ }
70
+ .persona-mention-group-header {
71
+ padding: 6px 8px 4px;
72
+ font-size: 11px;
73
+ font-weight: 600;
74
+ text-transform: uppercase;
75
+ letter-spacing: 0.04em;
76
+ color: var(--persona-mention-group-fg, var(--persona-muted, #6b7280));
77
+ }
78
+ .persona-mention-option {
79
+ display: flex;
80
+ align-items: center;
81
+ gap: 8px;
82
+ padding: 7px 8px;
83
+ border-radius: 6px;
84
+ cursor: pointer;
85
+ color: var(--persona-text, #111827);
86
+ }
87
+ .persona-mention-option[data-active="true"] {
88
+ background: var(--persona-mention-option-active-bg, var(--persona-container, #f1f5f9));
89
+ }
90
+ .persona-mention-option-icon {
91
+ display: inline-flex;
92
+ align-items: center;
93
+ flex: 0 0 auto;
94
+ opacity: 0.7;
95
+ }
96
+ .persona-mention-option-text {
97
+ display: flex;
98
+ flex-direction: column;
99
+ min-width: 0;
100
+ }
101
+ .persona-mention-option-labelline {
102
+ display: flex;
103
+ align-items: baseline;
104
+ gap: 6px;
105
+ min-width: 0;
106
+ }
107
+ .persona-mention-option-label {
108
+ font-size: 13px;
109
+ line-height: 1.3;
110
+ overflow: hidden;
111
+ text-overflow: ellipsis;
112
+ white-space: nowrap;
113
+ }
114
+ .persona-mention-option-arghint {
115
+ flex: 0 0 auto;
116
+ font-size: 12px;
117
+ line-height: 1.3;
118
+ color: var(--persona-muted, #6b7280);
119
+ opacity: 0.85;
120
+ font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
121
+ }
122
+ .persona-mention-option-desc {
123
+ font-size: 11px;
124
+ line-height: 1.3;
125
+ color: var(--persona-muted, #6b7280);
126
+ overflow: hidden;
127
+ text-overflow: ellipsis;
128
+ white-space: nowrap;
129
+ }
130
+ .persona-mention-status,
131
+ .persona-mention-empty,
132
+ .persona-mention-hint {
133
+ padding: 7px 8px;
134
+ font-size: 12px;
135
+ color: var(--persona-muted, #6b7280);
136
+ }
137
+ .persona-mention-error {
138
+ display: flex;
139
+ align-items: center;
140
+ justify-content: space-between;
141
+ gap: 8px;
142
+ color: var(--persona-mention-error, #dc2626);
143
+ }
144
+ .persona-mention-error-text {
145
+ min-width: 0;
146
+ overflow: hidden;
147
+ text-overflow: ellipsis;
148
+ white-space: nowrap;
149
+ }
150
+ .persona-mention-retry {
151
+ flex: 0 0 auto;
152
+ padding: 4px 8px;
153
+ border: 1px solid var(--persona-mention-error, #dc2626);
154
+ border-radius: 6px;
155
+ background: transparent;
156
+ color: var(--persona-mention-error, #dc2626);
157
+ font: inherit;
158
+ font-size: 12px;
159
+ cursor: pointer;
160
+ }
161
+ .persona-mention-retry:hover {
162
+ background: var(--persona-palette-colors-black-alpha-50, rgba(0, 0, 0, 0.06));
163
+ }
164
+ .persona-mention-hint {
165
+ font-style: italic;
166
+ opacity: 0.8;
167
+ }
168
+ @media (pointer: coarse) {
169
+ .persona-mention-option {
170
+ min-height: 44px;
171
+ }
172
+ .persona-mention-retry {
173
+ min-height: 36px;
174
+ }
175
+ }
176
+ `;