@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.
- package/README.md +1 -0
- package/dist/animations/glyph-cycle.d.cts +1 -1
- package/dist/animations/glyph-cycle.d.ts +1 -1
- package/dist/animations/{types-BsZtXPKK.d.cts → types-4ROVJ1gA.d.cts} +42 -0
- package/dist/animations/{types-BsZtXPKK.d.ts → types-4ROVJ1gA.d.ts} +42 -0
- package/dist/animations/wipe.d.cts +1 -1
- package/dist/animations/wipe.d.ts +1 -1
- package/dist/chunk-IO5VVUKP.js +3 -0
- package/dist/chunk-IPVK3KOM.js +1 -0
- package/dist/chunk-UPO4GUFC.js +1 -0
- package/dist/codegen.cjs +6 -6
- package/dist/codegen.js +8 -8
- package/dist/context-mentions-7S5KVUTG.js +169 -0
- package/dist/context-mentions-inline-TTCN7ZM2.js +4 -0
- package/dist/context-mentions-inline.cjs +4 -0
- package/dist/context-mentions-inline.d.cts +203 -0
- package/dist/context-mentions-inline.d.ts +203 -0
- package/dist/context-mentions-inline.js +4 -0
- package/dist/context-mentions.cjs +295 -0
- package/dist/context-mentions.d.cts +7025 -0
- package/dist/context-mentions.d.ts +7025 -0
- package/dist/context-mentions.js +295 -0
- package/dist/index.cjs +59 -51
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +622 -2
- package/dist/index.d.ts +622 -2
- package/dist/index.global.js +54 -46
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +59 -51
- package/dist/index.js.map +1 -1
- package/dist/launcher.global.js +2 -2
- package/dist/launcher.global.js.map +1 -1
- package/dist/plugin-kit.cjs +1 -1
- package/dist/plugin-kit.d.cts +17 -0
- package/dist/plugin-kit.d.ts +17 -0
- package/dist/plugin-kit.js +1 -1
- package/dist/smart-dom-reader.cjs +17 -16
- package/dist/smart-dom-reader.d.cts +507 -1
- package/dist/smart-dom-reader.d.ts +507 -1
- package/dist/smart-dom-reader.js +17 -16
- package/dist/theme-editor-preview.cjs +234 -55
- package/dist/theme-editor-preview.d.cts +473 -0
- package/dist/theme-editor-preview.d.ts +473 -0
- package/dist/theme-editor-preview.js +48 -42
- package/dist/theme-editor.cjs +7 -7
- package/dist/theme-editor.d.cts +473 -0
- package/dist/theme-editor.d.ts +473 -0
- package/dist/theme-editor.js +5 -5
- package/dist/theme-reference.cjs +1 -1
- package/dist/theme-reference.d.cts +2 -0
- package/dist/theme-reference.d.ts +2 -0
- package/dist/widget.css +1 -1
- package/package.json +15 -3
- package/src/client.test.ts +69 -0
- package/src/client.ts +65 -51
- package/src/components/composer-parts.ts +3 -12
- package/src/components/context-mention-button.test.ts +70 -0
- package/src/components/context-mention-button.ts +82 -0
- package/src/components/context-mention-chip.ts +134 -0
- package/src/components/context-mention-menu.test.ts +508 -0
- package/src/components/context-mention-menu.ts +0 -0
- package/src/components/message-bubble.test.ts +175 -0
- package/src/components/message-bubble.ts +177 -19
- package/src/components/panel.ts +7 -10
- package/src/context-mentions-bundle.test.ts +163 -0
- package/src/context-mentions-entry.ts +185 -0
- package/src/context-mentions-inline-entry.test.ts +136 -0
- package/src/context-mentions-inline-entry.ts +226 -0
- package/src/context-mentions-inline-loader.test.ts +30 -0
- package/src/context-mentions-inline-loader.ts +36 -0
- package/src/context-mentions-inline.ts +15 -0
- package/src/context-mentions-loader.ts +32 -0
- package/src/context-mentions.ts +16 -0
- package/src/index-core.ts +26 -0
- package/src/index-global.ts +51 -0
- package/src/markdown-parsers-loader.ts +35 -44
- package/src/plugin-kit.test.ts +40 -0
- package/src/plugin-kit.ts +39 -5
- package/src/session.mentions.test.ts +175 -0
- package/src/session.test.ts +52 -4
- package/src/session.ts +121 -5
- package/src/smart-dom-reader.test.ts +129 -2
- package/src/smart-dom-reader.ts +127 -1
- package/src/styles/context-mention-menu-css.ts +176 -0
- package/src/styles/widget.css +229 -126
- package/src/theme-editor/sections.ts +3 -3
- package/src/types/theme.ts +2 -0
- package/src/types.ts +525 -0
- package/src/ui.mention-submit.test.ts +235 -0
- package/src/ui.ts +301 -51
- package/src/utils/chunk-loader.test.ts +97 -0
- package/src/utils/chunk-loader.ts +88 -0
- package/src/utils/composer-contenteditable.test.ts +507 -0
- package/src/utils/composer-contenteditable.ts +626 -0
- package/src/utils/composer-document.test.ts +280 -0
- package/src/utils/composer-document.ts +293 -0
- package/src/utils/composer-history.test.ts +35 -7
- package/src/utils/composer-history.ts +30 -20
- package/src/utils/composer-input.ts +159 -0
- package/src/utils/context-mention-controller.test.ts +1215 -0
- package/src/utils/context-mention-controller.ts +1186 -0
- package/src/utils/context-mention-manager.test.ts +422 -0
- package/src/utils/context-mention-manager.ts +410 -0
- package/src/utils/context-mention-orchestrator.test.ts +538 -0
- package/src/utils/context-mention-orchestrator.ts +348 -0
- package/src/utils/live-region.test.ts +108 -0
- package/src/utils/live-region.ts +94 -0
- package/src/utils/mention-channels.ts +63 -0
- package/src/utils/mention-llm-format.test.ts +91 -0
- package/src/utils/mention-llm-format.ts +79 -0
- package/src/utils/mention-matcher.test.ts +86 -0
- package/src/utils/mention-matcher.ts +221 -0
- package/src/utils/mention-token.ts +72 -0
- package/src/utils/mention-trigger.test.ts +155 -0
- package/src/utils/mention-trigger.ts +156 -0
- package/src/utils/theme.test.ts +28 -4
- 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
|
});
|
package/src/smart-dom-reader.ts
CHANGED
|
@@ -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
|
|
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
|
+
`;
|