@runtypelabs/persona 4.8.0 → 4.10.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 +14 -1
- 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 +72 -64
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +634 -3
- package/dist/index.d.ts +634 -3
- package/dist/index.global.js +59 -51
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +64 -56
- 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 +236 -57
- package/dist/theme-editor-preview.d.cts +485 -1
- package/dist/theme-editor-preview.d.ts +485 -1
- package/dist/theme-editor-preview.js +53 -47
- 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/artifact-pane.test.ts +47 -0
- package/src/components/artifact-pane.ts +25 -2
- 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 +27 -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/runtime/init-update-reset.test.ts +81 -0
- package/src/runtime/init.test.ts +62 -0
- package/src/runtime/init.ts +7 -14
- 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 +243 -126
- package/src/theme-editor/sections.ts +3 -3
- package/src/types/theme.ts +2 -0
- package/src/types.ts +542 -0
- package/src/ui.artifact-pane-gating.test.ts +11 -1
- package/src/ui.attachments-drop.test.ts +90 -0
- package/src/ui.header-update-stability.test.ts +149 -0
- package/src/ui.launcher-update-merge.test.ts +83 -0
- package/src/ui.mention-submit.test.ts +235 -0
- package/src/ui.send-button-stream-update.test.ts +69 -0
- package/src/ui.ts +379 -84
- 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/config-merge.test.ts +131 -0
- package/src/utils/config-merge.ts +61 -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 +54 -4
- package/src/utils/theme.ts +6 -3
- package/src/utils/tokens.ts +27 -11
package/src/types.ts
CHANGED
|
@@ -95,6 +95,500 @@ export type AgentWidgetContextProvider = (
|
|
|
95
95
|
| void
|
|
96
96
|
| Promise<Record<string, unknown> | void>;
|
|
97
97
|
|
|
98
|
+
// ============================================================================
|
|
99
|
+
// Context Mentions (composer @-mentions / context attachment)
|
|
100
|
+
// ============================================================================
|
|
101
|
+
//
|
|
102
|
+
// Two entry points (a visible affordance button and the `@` trigger) over one
|
|
103
|
+
// shared menu. Selecting a mention strips the typed `@query` and adds a compact
|
|
104
|
+
// removable pill chip to the composer context row; the resolved content reaches
|
|
105
|
+
// the model via `llmAppend`/`contentParts` (default, no backend changes) or the
|
|
106
|
+
// opt-in structured `context` channel. See `docs/context-mentions-plan.md`.
|
|
107
|
+
|
|
108
|
+
/** A single selectable item in the mention menu (host-provided). */
|
|
109
|
+
export type AgentWidgetContextMentionItem = {
|
|
110
|
+
/** Stable key within its source. */
|
|
111
|
+
id: string;
|
|
112
|
+
/** Shown in the menu and on the chip (e.g. "App.tsx"). */
|
|
113
|
+
label: string;
|
|
114
|
+
/** Subtitle line in the menu. */
|
|
115
|
+
description?: string;
|
|
116
|
+
/** Lucide icon name. */
|
|
117
|
+
iconName?: string;
|
|
118
|
+
/**
|
|
119
|
+
* Accent color for this item's inline token (`display: "inline"`), any CSS
|
|
120
|
+
* color. Sets `--persona-mention-token-accent` on the token, tinting its pill
|
|
121
|
+
* background + icon/label — the per-item/per-type color knob (Slack/Linear
|
|
122
|
+
* style). Prefer setting the same color across a source's items for a "color
|
|
123
|
+
* per type" look, or theme by `[data-mention-source]` in CSS instead. Ignored
|
|
124
|
+
* in chip mode.
|
|
125
|
+
*/
|
|
126
|
+
color?: string;
|
|
127
|
+
/** Group header override; defaults to the source's `label`. */
|
|
128
|
+
group?: string;
|
|
129
|
+
/** Optional recency/popularity hint the default matcher boosts on. */
|
|
130
|
+
recencyScore?: number;
|
|
131
|
+
/**
|
|
132
|
+
* SKILLS / SLASH-COMMANDS. When set, this item is a COMMAND (a verb) rather
|
|
133
|
+
* than a context mention (a noun), so selecting it dispatches a behavior
|
|
134
|
+
* instead of adding a context chip. Absent for ordinary mentions.
|
|
135
|
+
*
|
|
136
|
+
* - `"prompt"`: a prompt macro. `resolve()` returns `insertText` (or
|
|
137
|
+
* `llmAppend`) which is written into the composer (see `insertMode`);
|
|
138
|
+
* `submitOnSelect` optionally sends it immediately. No chip.
|
|
139
|
+
* - `"action"`: a client action. `action()` runs in the browser; no resolve,
|
|
140
|
+
* no chip, no message sent (e.g. `/clear`, `/theme dark`).
|
|
141
|
+
* - `"server"`: a server-invoked skill. Routed like a `resolveOn:"submit"`
|
|
142
|
+
* mention whose `resolve().context` reaches the backend via request
|
|
143
|
+
* `context.mentions.<sourceId>.<itemId>` (a flow/agent reads it).
|
|
144
|
+
*/
|
|
145
|
+
command?: "prompt" | "action" | "server";
|
|
146
|
+
/**
|
|
147
|
+
* `command:"action"` handler. Receives the parsed `args` (text after the
|
|
148
|
+
* command name), plus a small `composer` capability + `config`/`messages`.
|
|
149
|
+
* Short-circuits the mention flow (no chip, no send). Ignored otherwise.
|
|
150
|
+
*/
|
|
151
|
+
action?: (ctx: AgentWidgetContextMentionCommandContext) => void | Promise<void>;
|
|
152
|
+
/**
|
|
153
|
+
* `command:"prompt"` insertion mode. `"replace"` (default) swaps the whole
|
|
154
|
+
* composer value for the resolved text; `"insert-at-caret"` replaces only the
|
|
155
|
+
* `/token` span, keeping surrounding prose. Ignored otherwise.
|
|
156
|
+
*/
|
|
157
|
+
insertMode?: "replace" | "insert-at-caret";
|
|
158
|
+
/**
|
|
159
|
+
* When true, a `command:"prompt"` submits the composer immediately after
|
|
160
|
+
* inserting its text (a one-shot macro). Ignored for other kinds.
|
|
161
|
+
*/
|
|
162
|
+
submitOnSelect?: boolean;
|
|
163
|
+
/**
|
|
164
|
+
* COMMANDS only. Ghost hint for a free-text argument (e.g. "order id" →
|
|
165
|
+
* `/lookup ‹order id›` in the menu). Its presence switches the command to
|
|
166
|
+
* INLINE COMPLETION (Slack-style): selecting it fills `/name ` into the
|
|
167
|
+
* composer for inline arg entry instead of dispatching on select, and the
|
|
168
|
+
* command is executed at SUBMIT with the typed args. `command:"server"` items
|
|
169
|
+
* always use inline completion (their chip had no way to add an argument);
|
|
170
|
+
* for `"prompt"`/`"action"` items this is opt-in. Ignored for non-commands.
|
|
171
|
+
*/
|
|
172
|
+
commandArgsPlaceholder?: string;
|
|
173
|
+
};
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Capability object handed to a `command:"action"` handler. The widget exposes
|
|
177
|
+
* only composer-local operations; broader actions (clear the transcript, change
|
|
178
|
+
* theme) are wired by the host via closures over its own controller.
|
|
179
|
+
*/
|
|
180
|
+
export type AgentWidgetContextMentionComposerCapability = {
|
|
181
|
+
/** Current composer text. */
|
|
182
|
+
getValue: () => string;
|
|
183
|
+
/** Replace composer text (fires `input`, moves caret to end, refocuses). */
|
|
184
|
+
setValue: (value: string) => void;
|
|
185
|
+
/** Submit the composer (same path as pressing send). */
|
|
186
|
+
submit: () => void;
|
|
187
|
+
};
|
|
188
|
+
|
|
189
|
+
/** Context passed to a `command:"action"` handler. */
|
|
190
|
+
export type AgentWidgetContextMentionCommandContext = {
|
|
191
|
+
/** Text typed after the command name (e.g. `staging` for `/deploy staging`). */
|
|
192
|
+
args: string;
|
|
193
|
+
config: AgentWidgetConfig;
|
|
194
|
+
messages: AgentWidgetMessage[];
|
|
195
|
+
composer: AgentWidgetContextMentionComposerCapability;
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
/** Reference stored on a sent message for transcript fidelity + chip rendering. */
|
|
199
|
+
export type AgentWidgetContextMentionRef = {
|
|
200
|
+
sourceId: string;
|
|
201
|
+
itemId: string;
|
|
202
|
+
label: string;
|
|
203
|
+
iconName?: string;
|
|
204
|
+
/** Accent color for the inline token (see `AgentWidgetContextMentionItem.color`). */
|
|
205
|
+
color?: string;
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* One ordered segment of a sent message's prose, for inline mention rendering
|
|
210
|
+
* (`contextMentions.display: "inline"`). Text runs and atomic mention tokens
|
|
211
|
+
* interleave in document order so the sent bubble re-renders `@tokens` in place
|
|
212
|
+
* and composer history can round-trip them. Display/transcript concern only —
|
|
213
|
+
* the model still sees resolved bodies via `llmContent`/`contentParts`.
|
|
214
|
+
*/
|
|
215
|
+
export type AgentWidgetContentSegment =
|
|
216
|
+
| { kind: "text"; text: string }
|
|
217
|
+
| { kind: "mention"; ref: AgentWidgetContextMentionRef };
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* The payload a source returns from `resolve()` for one selected item.
|
|
221
|
+
* Lead with `llmAppend` — it is the only channel guaranteed to reach the model
|
|
222
|
+
* with no backend changes.
|
|
223
|
+
*/
|
|
224
|
+
export type AgentWidgetContextMentionPayload = {
|
|
225
|
+
/**
|
|
226
|
+
* PRIMARY, model-visible channel. Appended to this user message's LLM content
|
|
227
|
+
* so the model actually sees the context with no backend changes.
|
|
228
|
+
*/
|
|
229
|
+
llmAppend?: string;
|
|
230
|
+
/**
|
|
231
|
+
* `command:"prompt"` macros only. Text written into the COMPOSER on select
|
|
232
|
+
* (see `AgentWidgetContextMentionItem.insertMode`) — distinct from the hidden
|
|
233
|
+
* `llmAppend` context channel. Falls back to `llmAppend` when omitted.
|
|
234
|
+
*/
|
|
235
|
+
insertText?: string;
|
|
236
|
+
/** Extra content parts (e.g. file text / image) via the multi-modal path. */
|
|
237
|
+
contentParts?: ContentPart[];
|
|
238
|
+
/**
|
|
239
|
+
* OPT-IN structured channel. Merged into request `context` under
|
|
240
|
+
* `mentions.<sourceId>.<itemId>` for flows/agents that read it. NOTE: ambient
|
|
241
|
+
* `context` is NOT guaranteed to reach the model — use `llmAppend` if you need
|
|
242
|
+
* the model to see it.
|
|
243
|
+
*/
|
|
244
|
+
context?: Record<string, unknown>;
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* One resolved mention, handed to `contextMentions.llmFormat` to build the
|
|
249
|
+
* model-visible block. `text` is the source's `llmAppend`; `ref`/`item` carry
|
|
250
|
+
* the selection identity/metadata for a custom formatter (e.g. a source-specific
|
|
251
|
+
* wrapper or a path from `item`).
|
|
252
|
+
*/
|
|
253
|
+
export type AgentWidgetMentionLlmEntry = {
|
|
254
|
+
/** The mention's display label (from `ref.label`). */
|
|
255
|
+
label: string;
|
|
256
|
+
/** The resolved model-visible body (the source's `llmAppend`). */
|
|
257
|
+
text: string;
|
|
258
|
+
ref: AgentWidgetContextMentionRef;
|
|
259
|
+
item: AgentWidgetContextMentionItem;
|
|
260
|
+
};
|
|
261
|
+
|
|
262
|
+
export type AgentWidgetContextMentionSearchContext = {
|
|
263
|
+
messages: AgentWidgetMessage[];
|
|
264
|
+
config: AgentWidgetConfig;
|
|
265
|
+
/** Aborts when the next keystroke supersedes this in-flight search. */
|
|
266
|
+
signal: AbortSignal;
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
export type AgentWidgetContextMentionResolveContext =
|
|
270
|
+
AgentWidgetContextMentionSearchContext & {
|
|
271
|
+
/** Plain-text composer value at resolve time. */
|
|
272
|
+
composerText: string;
|
|
273
|
+
/**
|
|
274
|
+
* For `command` items: the text typed after the command name (e.g.
|
|
275
|
+
* `"123"` for `/lookup 123`). Empty string for ordinary mentions.
|
|
276
|
+
*/
|
|
277
|
+
args: string;
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
export type AgentWidgetContextMentionSource = {
|
|
281
|
+
id: string;
|
|
282
|
+
/** Group header shown in the menu. */
|
|
283
|
+
label: string;
|
|
284
|
+
/**
|
|
285
|
+
* Filter items for the current `@query` (empty query → recent/popular).
|
|
286
|
+
* Implement server-side search for large/remote sets, or return all items and
|
|
287
|
+
* let the widget rank them with `defaultMentionFilter`
|
|
288
|
+
* (see `createStaticMentionSource` for the common case).
|
|
289
|
+
*/
|
|
290
|
+
search: (
|
|
291
|
+
query: string,
|
|
292
|
+
ctx: AgentWidgetContextMentionSearchContext
|
|
293
|
+
) =>
|
|
294
|
+
| AgentWidgetContextMentionItem[]
|
|
295
|
+
| Promise<AgentWidgetContextMentionItem[]>;
|
|
296
|
+
/**
|
|
297
|
+
* Fetch the payload for dispatch. Called once when the user SELECTS the item
|
|
298
|
+
* (eagerly, cached on the chip, abortable on removal) unless `resolveOn` is
|
|
299
|
+
* `"submit"`. A throw/abort drops the mention via `onMentionResolveError`.
|
|
300
|
+
*/
|
|
301
|
+
resolve: (
|
|
302
|
+
item: AgentWidgetContextMentionItem,
|
|
303
|
+
ctx: AgentWidgetContextMentionResolveContext
|
|
304
|
+
) =>
|
|
305
|
+
| AgentWidgetContextMentionPayload
|
|
306
|
+
| Promise<AgentWidgetContextMentionPayload>;
|
|
307
|
+
/**
|
|
308
|
+
* When to call `resolve()`:
|
|
309
|
+
* - `"select"` (default): eager on pick; parallelizes the fetch with the user
|
|
310
|
+
* finishing their sentence so submit stays instant.
|
|
311
|
+
* - `"submit"`: defer to send time, for time-sensitive sources (e.g. current
|
|
312
|
+
* page state that may change between select and send).
|
|
313
|
+
* @default "select"
|
|
314
|
+
*/
|
|
315
|
+
resolveOn?: "select" | "submit";
|
|
316
|
+
/**
|
|
317
|
+
* COMMAND sources only. Resolve a `/name` (the first token of a command line)
|
|
318
|
+
* to its menu item, for submit-time inline-command dispatch and the menu's
|
|
319
|
+
* "typing args now" suppression. `createSlashCommandsSource` sets this; plain
|
|
320
|
+
* `@`-mention sources leave it undefined.
|
|
321
|
+
*/
|
|
322
|
+
matchCommand?: (name: string) => AgentWidgetContextMentionItem | undefined;
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
/** Context passed to the optional `renderMentionMenu` override. */
|
|
326
|
+
export type AgentWidgetContextMentionMenuRenderContext = {
|
|
327
|
+
/** Current trigger query ("" when freshly opened). */
|
|
328
|
+
query: string;
|
|
329
|
+
/** Grouped, ranked, capped results ready to paint. */
|
|
330
|
+
groups: {
|
|
331
|
+
source: AgentWidgetContextMentionSource;
|
|
332
|
+
items: AgentWidgetContextMentionItem[];
|
|
333
|
+
}[];
|
|
334
|
+
/** Per-source async state for loading/empty/error rendering, keyed by source id. */
|
|
335
|
+
status: Record<string, "loading" | "ready" | "empty" | "error">;
|
|
336
|
+
/** Index into the flat keyboard-traversal order. */
|
|
337
|
+
activeIndex: number;
|
|
338
|
+
/** Commit a selection (strips `@query`, adds chip, resolves on select). */
|
|
339
|
+
select: (item: AgentWidgetContextMentionItem) => void;
|
|
340
|
+
/** Close the menu, keeping a literal `@`. */
|
|
341
|
+
close: () => void;
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
/** Context passed to the optional `renderMentionItem` per-row override. */
|
|
345
|
+
export type AgentWidgetContextMentionItemRenderContext = {
|
|
346
|
+
/** The item to render. */
|
|
347
|
+
item: AgentWidgetContextMentionItem;
|
|
348
|
+
/** The source this item came from (its group). */
|
|
349
|
+
source: AgentWidgetContextMentionSource;
|
|
350
|
+
/** Current trigger query, for highlighting matches. */
|
|
351
|
+
query: string;
|
|
352
|
+
/** True when this row is the keyboard-highlighted one at render time. */
|
|
353
|
+
active: boolean;
|
|
354
|
+
/** Position in the flat keyboard-traversal order. */
|
|
355
|
+
index: number;
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
/** Context passed to the optional `renderMentionChip` override. */
|
|
359
|
+
export type AgentWidgetContextMentionChipRenderContext = {
|
|
360
|
+
ref: AgentWidgetContextMentionRef;
|
|
361
|
+
/** Resolve-on-select lifecycle for spinner→ready→error UI. */
|
|
362
|
+
status: "resolving" | "ready" | "error";
|
|
363
|
+
/**
|
|
364
|
+
* The resolved payload, once available (select-resolved sources only). Use it
|
|
365
|
+
* to preview already-fetched content on hover. Undefined while `status` is
|
|
366
|
+
* `"resolving"`, on `"error"`, and for `resolveOn:"submit"` sources (which
|
|
367
|
+
* resolve at send time, not while the chip sits in the composer — for those,
|
|
368
|
+
* `ref.itemId` carries the source key you can re-read on demand, e.g. a CSS
|
|
369
|
+
* selector for the smart-dom source).
|
|
370
|
+
*/
|
|
371
|
+
payload?: AgentWidgetContextMentionPayload;
|
|
372
|
+
/** Remove the chip; aborts any in-flight resolve. */
|
|
373
|
+
remove: () => void;
|
|
374
|
+
};
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* Where a trigger char is allowed to open the menu:
|
|
378
|
+
* - `"anywhere"` (default): after any whitespace or start (the `@` rule).
|
|
379
|
+
* - `"line-start"`: only when the char before the trigger is a newline or the
|
|
380
|
+
* start of input — the natural rule for `/` slash-commands.
|
|
381
|
+
* - `"input-start"`: only at index 0 of the composer.
|
|
382
|
+
*/
|
|
383
|
+
export type AgentWidgetMentionTriggerPosition =
|
|
384
|
+
| "anywhere"
|
|
385
|
+
| "line-start"
|
|
386
|
+
| "input-start";
|
|
387
|
+
|
|
388
|
+
/**
|
|
389
|
+
* A SECOND (or Nth) trigger channel sharing the same menu engine — e.g. `/` for
|
|
390
|
+
* slash-commands alongside `@` for context. The legacy `trigger`/`sources`
|
|
391
|
+
* fields form the implicit first channel; `triggers[]` adds more. At any caret
|
|
392
|
+
* at most one channel is active, so all channels drive one menu/controller.
|
|
393
|
+
*/
|
|
394
|
+
export type AgentWidgetMentionTriggerChannel = {
|
|
395
|
+
/** Trigger character (e.g. "/"). */
|
|
396
|
+
trigger: string;
|
|
397
|
+
/** Sources for this channel (searched only when this trigger is active). */
|
|
398
|
+
sources: AgentWidgetContextMentionSource[];
|
|
399
|
+
/** Where the trigger may open. @default "anywhere" */
|
|
400
|
+
triggerPosition?: AgentWidgetMentionTriggerPosition;
|
|
401
|
+
/**
|
|
402
|
+
* Let the query span spaces so a command can take ARGS (`/deploy staging`).
|
|
403
|
+
* A newline still ends it. Pair with `triggerPosition: "line-start"` for
|
|
404
|
+
* slash-commands. @default false
|
|
405
|
+
*/
|
|
406
|
+
allowSpaces?: boolean;
|
|
407
|
+
/** Show a composer affordance button for this channel. @default false */
|
|
408
|
+
showButton?: boolean;
|
|
409
|
+
/** Icon for this channel's affordance button. */
|
|
410
|
+
buttonIconName?: string;
|
|
411
|
+
/** Tooltip / aria-label for this channel's affordance button. */
|
|
412
|
+
buttonTooltipText?: string;
|
|
413
|
+
/** Placeholder for this channel's picker search field. */
|
|
414
|
+
searchPlaceholder?: string;
|
|
415
|
+
};
|
|
416
|
+
|
|
417
|
+
export type AgentWidgetContextMentionConfig = {
|
|
418
|
+
/** @default false */
|
|
419
|
+
enabled?: boolean;
|
|
420
|
+
/**
|
|
421
|
+
* Show the visible composer affordance button (the discoverable entry point).
|
|
422
|
+
* Strongly recommended; the bare `@` trigger alone is hard to discover for
|
|
423
|
+
* non-technical users. @default true
|
|
424
|
+
*/
|
|
425
|
+
showButton?: boolean;
|
|
426
|
+
/**
|
|
427
|
+
* Icon for the affordance button (any registered Lucide name). Defaults to a
|
|
428
|
+
* "+" signifier — the consumer-recognized "add context" affordance — rather
|
|
429
|
+
* than an "@" glyph, which reads as power-user. Use `"at-sign"` on a
|
|
430
|
+
* developer/power-user surface. @default "plus"
|
|
431
|
+
*/
|
|
432
|
+
buttonIconName?: string;
|
|
433
|
+
/** Tooltip / aria-label for the affordance button. @default "Add context" */
|
|
434
|
+
buttonTooltipText?: string;
|
|
435
|
+
/**
|
|
436
|
+
* Placeholder for the picker's search field, shown when the menu is opened
|
|
437
|
+
* from the affordance button (a Cursor/Copilot-style picker that inserts no
|
|
438
|
+
* trigger character). The typed-trigger path keeps its query in the textarea
|
|
439
|
+
* and never shows this field. @default "Search context…"
|
|
440
|
+
*/
|
|
441
|
+
searchPlaceholder?: string;
|
|
442
|
+
/** Trigger character. @default "@" */
|
|
443
|
+
trigger?: string;
|
|
444
|
+
/**
|
|
445
|
+
* Where the primary `trigger` may open the menu. @default "anywhere"
|
|
446
|
+
*/
|
|
447
|
+
triggerPosition?: AgentWidgetMentionTriggerPosition;
|
|
448
|
+
/**
|
|
449
|
+
* ADDITIONAL trigger channels sharing this engine — e.g. a `/` slash-command
|
|
450
|
+
* channel next to `@` context. Each channel has its own trigger char,
|
|
451
|
+
* sources, and position rule. See `AgentWidgetMentionTriggerChannel`.
|
|
452
|
+
*/
|
|
453
|
+
triggers?: AgentWidgetMentionTriggerChannel[];
|
|
454
|
+
/** Max mentions per message. @default 8 */
|
|
455
|
+
maxMentions?: number;
|
|
456
|
+
/** Max items shown per source group before "keep typing to narrow". @default 6 */
|
|
457
|
+
maxItemsPerGroup?: number;
|
|
458
|
+
/**
|
|
459
|
+
* Debounce for ASYNC source search only (ms). Synchronous sources and the
|
|
460
|
+
* menu's first paint are never debounced. @default 150
|
|
461
|
+
*/
|
|
462
|
+
searchDebounceMs?: number;
|
|
463
|
+
/** Registered mention sources (search + resolve). */
|
|
464
|
+
sources: AgentWidgetContextMentionSource[];
|
|
465
|
+
/** Chip icon fallback when a source/item omits one. @default "at-sign" */
|
|
466
|
+
chipIconName?: string;
|
|
467
|
+
/**
|
|
468
|
+
* How each resolved mention's `llmAppend` text is wrapped into a delimited
|
|
469
|
+
* block before it is prepended to the model-visible message content. Blocks
|
|
470
|
+
* are joined with a blank line and the typed prose follows last.
|
|
471
|
+
*
|
|
472
|
+
* - `"fenced"` (default): a fenced code block with the label in the info
|
|
473
|
+
* string, e.g. selecting `App.tsx` emits:
|
|
474
|
+
* ```text
|
|
475
|
+
* ```App.tsx
|
|
476
|
+
* <file body>
|
|
477
|
+
* ```
|
|
478
|
+
* ```
|
|
479
|
+
* The fence auto-escalates (four+ backticks) when the body itself contains a
|
|
480
|
+
* fence, so a body can never terminate its own wrapper.
|
|
481
|
+
* - `"document"`: Anthropic's long-context document shape, e.g.:
|
|
482
|
+
* ```text
|
|
483
|
+
* <document index="1">
|
|
484
|
+
* <source>App.tsx</source>
|
|
485
|
+
* <document_content>
|
|
486
|
+
* <file body>
|
|
487
|
+
* </document_content>
|
|
488
|
+
* </document>
|
|
489
|
+
* ```
|
|
490
|
+
* Prefer this for prose/document-shaped sources per Anthropic's long-context
|
|
491
|
+
* guidance (indexed `<document>` blocks improve grounding and citation). A
|
|
492
|
+
* body containing the literal `</document_content>` closing tag falls back to
|
|
493
|
+
* the fenced block for that entry to avoid a broken XML boundary.
|
|
494
|
+
* - function form: fully owns the block for one mention — its return string is
|
|
495
|
+
* used verbatim (no extra wrapping), joined with the other blocks. `index` is
|
|
496
|
+
* the 0-based position among the message's mention blocks.
|
|
497
|
+
*
|
|
498
|
+
* @default "fenced"
|
|
499
|
+
*/
|
|
500
|
+
llmFormat?:
|
|
501
|
+
| "fenced"
|
|
502
|
+
| "document"
|
|
503
|
+
| ((entry: AgentWidgetMentionLlmEntry, index: number) => string);
|
|
504
|
+
/**
|
|
505
|
+
* How `@` selections appear in the composer and sent user bubble.
|
|
506
|
+
*
|
|
507
|
+
* - `"chip"` (default): strip `@query` on select; show a compact pill in the
|
|
508
|
+
* composer context row; `content` is prose-only.
|
|
509
|
+
* - `"inline"`: insert an atomic styled token in the sentence (Slack/Linear/
|
|
510
|
+
* Cursor style), backed by a contenteditable composer. Slash-command channels
|
|
511
|
+
* (`triggers[]` with `/`) stay inline-text regardless — they already use the
|
|
512
|
+
* `/name args` model, not chips. `renderMentionChip` is ignored for `@`
|
|
513
|
+
* mentions in this mode.
|
|
514
|
+
*
|
|
515
|
+
* The resolved-context channel (`llmContent`/`contentParts`) is unchanged
|
|
516
|
+
* across both modes — inline tokens are a display concern.
|
|
517
|
+
*
|
|
518
|
+
* @default "chip"
|
|
519
|
+
*/
|
|
520
|
+
display?: "chip" | "inline";
|
|
521
|
+
/**
|
|
522
|
+
* Fired when a pick is rejected: it duplicates an existing chip in chip display
|
|
523
|
+
* (`"duplicate"` — inline display allows the same item as multiple tokens, so it
|
|
524
|
+
* never rejects for this reason), hits `maxMentions` (`"limit"`), or (inline
|
|
525
|
+
* display) the composer text changed between parse and commit so the token could
|
|
526
|
+
* not be inserted (`"stale"`).
|
|
527
|
+
*/
|
|
528
|
+
onMentionRejected?: (
|
|
529
|
+
item: AgentWidgetContextMentionItem,
|
|
530
|
+
reason: "duplicate" | "limit" | "stale"
|
|
531
|
+
) => void;
|
|
532
|
+
/**
|
|
533
|
+
* A `resolve()` that throws/aborts: the mention is dropped and the message
|
|
534
|
+
* still sends. Surface a non-blocking notice here if desired.
|
|
535
|
+
*/
|
|
536
|
+
onMentionResolveError?: (
|
|
537
|
+
item: AgentWidgetContextMentionItem,
|
|
538
|
+
error: unknown
|
|
539
|
+
) => void;
|
|
540
|
+
/**
|
|
541
|
+
* MID-LEVEL render override for the autocomplete menu. The widget still owns
|
|
542
|
+
* trigger detection, search, debounce/abort, keyboard nav, and positioning;
|
|
543
|
+
* you only supply the menu markup and drive selection through `ctx`. Omit to
|
|
544
|
+
* use the built-in grouped menu. @default built-in
|
|
545
|
+
*/
|
|
546
|
+
renderMentionMenu?: (
|
|
547
|
+
ctx: AgentWidgetContextMentionMenuRenderContext
|
|
548
|
+
) => HTMLElement;
|
|
549
|
+
/**
|
|
550
|
+
* NARROW render override for a single result row's INNER content, keeping the
|
|
551
|
+
* built-in menu chrome (group headers, loading/empty/error, keyboard nav, the
|
|
552
|
+
* "keep typing" hint). The widget keeps the `role="option"` wrapper and its
|
|
553
|
+
* click/hover wiring; you return only the row's contents. For full menu
|
|
554
|
+
* control use `renderMentionMenu` instead. Highlight is CSS-driven via the
|
|
555
|
+
* wrapper's `[data-active]` attribute, so this is called once per render, not
|
|
556
|
+
* on every arrow key. Ignored when `renderMentionMenu` is set. @default built-in
|
|
557
|
+
*/
|
|
558
|
+
renderMentionItem?: (
|
|
559
|
+
ctx: AgentWidgetContextMentionItemRenderContext
|
|
560
|
+
) => HTMLElement;
|
|
561
|
+
/**
|
|
562
|
+
* MID-LEVEL render override for a single context chip. Return your own pill
|
|
563
|
+
* (must include an accessible remove control wired to `ctx.remove`); reflect
|
|
564
|
+
* `ctx.status` for resolve-on-select. Omit to use the built-in compact pill.
|
|
565
|
+
* @default built-in
|
|
566
|
+
*/
|
|
567
|
+
renderMentionChip?: (
|
|
568
|
+
ctx: AgentWidgetContextMentionChipRenderContext
|
|
569
|
+
) => HTMLElement;
|
|
570
|
+
/**
|
|
571
|
+
* INLINE display only (`display: "inline"`). Render override for a single inline
|
|
572
|
+
* mention TOKEN — the atomic styled node that sits in the sentence, in both the
|
|
573
|
+
* contenteditable composer and the read-only sent bubble. Return your own
|
|
574
|
+
* element (e.g. a fully custom colored pill); the composer marks it
|
|
575
|
+
* `contenteditable="false"` and stamps `data-mention-id` so it stays atomic.
|
|
576
|
+
* Omit to use the built-in pill (icon + `@label`, tinted by
|
|
577
|
+
* `--persona-mention-token-accent` / `ref.color`, themeable per source via the
|
|
578
|
+
* `[data-mention-source]` attribute). Ignored in chip mode.
|
|
579
|
+
* @default built-in
|
|
580
|
+
*/
|
|
581
|
+
renderMentionToken?: (
|
|
582
|
+
ctx: AgentWidgetContextMentionTokenRenderContext
|
|
583
|
+
) => HTMLElement;
|
|
584
|
+
};
|
|
585
|
+
|
|
586
|
+
/** Render context for `renderMentionToken`. `readonly` is true in the sent bubble. */
|
|
587
|
+
export type AgentWidgetContextMentionTokenRenderContext = {
|
|
588
|
+
ref: AgentWidgetContextMentionRef;
|
|
589
|
+
readonly: boolean;
|
|
590
|
+
};
|
|
591
|
+
|
|
98
592
|
export type AgentWidgetRequestPayloadMessage = {
|
|
99
593
|
role: AgentWidgetMessageRole;
|
|
100
594
|
content: MessageContent;
|
|
@@ -4473,6 +4967,18 @@ export type AgentWidgetConfig = {
|
|
|
4473
4967
|
}) => string;
|
|
4474
4968
|
plugins?: AgentWidgetPlugin[];
|
|
4475
4969
|
contextProviders?: AgentWidgetContextProvider[];
|
|
4970
|
+
/**
|
|
4971
|
+
* Context mentions: let users explicitly pull external context into a turn by
|
|
4972
|
+
* typing `@` or clicking a visible composer button, both opening one shared
|
|
4973
|
+
* searchable menu of host-provided sources. Selecting a mention adds a
|
|
4974
|
+
* removable pill chip; resolved content reaches the model via `llmAppend`
|
|
4975
|
+
* (default) or the opt-in structured `context` channel.
|
|
4976
|
+
*
|
|
4977
|
+
* Disabled by default. When enabled, the mention runtime is lazy-loaded from a
|
|
4978
|
+
* sibling chunk on first use, so sites that leave it off pay no bundle cost.
|
|
4979
|
+
* See `docs/context-mentions-plan.md`.
|
|
4980
|
+
*/
|
|
4981
|
+
contextMentions?: AgentWidgetContextMentionConfig;
|
|
4476
4982
|
requestMiddleware?: AgentWidgetRequestMiddleware;
|
|
4477
4983
|
actionParsers?: AgentWidgetActionParser[];
|
|
4478
4984
|
actionHandlers?: AgentWidgetActionHandler[];
|
|
@@ -4925,6 +5431,23 @@ export type AgentWidgetConfig = {
|
|
|
4925
5431
|
loadingIndicator?: AgentWidgetLoadingIndicatorConfig;
|
|
4926
5432
|
};
|
|
4927
5433
|
|
|
5434
|
+
/**
|
|
5435
|
+
* Patch-aware deep-partial of {@link AgentWidgetConfig} accepted by `update()`.
|
|
5436
|
+
* Function types and arrays are preserved whole (not mapped over); plain-object
|
|
5437
|
+
* leaves recurse so a partial patch merges into the live config. See the merge
|
|
5438
|
+
* policy in utils/config-merge.ts (some plain-object fields still replace
|
|
5439
|
+
* wholesale at runtime; that is not encoded at the type level).
|
|
5440
|
+
*/
|
|
5441
|
+
export type AgentWidgetConfigPatch = ConfigPatch<AgentWidgetConfig>;
|
|
5442
|
+
|
|
5443
|
+
type ConfigPatch<T> = T extends (...args: never[]) => unknown
|
|
5444
|
+
? T
|
|
5445
|
+
: T extends readonly unknown[]
|
|
5446
|
+
? T
|
|
5447
|
+
: T extends object
|
|
5448
|
+
? { [K in keyof T]?: ConfigPatch<T[K]> }
|
|
5449
|
+
: T;
|
|
5450
|
+
|
|
4928
5451
|
export type AgentWidgetMessageRole = "user" | "assistant" | "system";
|
|
4929
5452
|
|
|
4930
5453
|
export type AgentWidgetReasoning = {
|
|
@@ -5024,6 +5547,25 @@ export type AgentWidgetMessage = {
|
|
|
5024
5547
|
* The `content` field contains the text-only representation for display.
|
|
5025
5548
|
*/
|
|
5026
5549
|
contentParts?: ContentPart[];
|
|
5550
|
+
/**
|
|
5551
|
+
* Context mentions attached to this (user) message, for transcript fidelity +
|
|
5552
|
+
* chip rendering in the sent bubble. Refs only — the resolved payloads are
|
|
5553
|
+
* merged into `llmContent`/`contentParts` (model-visible) at send time.
|
|
5554
|
+
*/
|
|
5555
|
+
contextMentions?: AgentWidgetContextMentionRef[];
|
|
5556
|
+
/**
|
|
5557
|
+
* Ordered prose + mention blocks for inline-mode rendering and composer history
|
|
5558
|
+
* recall (`contextMentions.display: "inline"`). Present only on inline-mode user
|
|
5559
|
+
* messages; when omitted the sent bubble falls back to the chip row derived from
|
|
5560
|
+
* `contextMentions`. Display/transcript concern only — not the model channel.
|
|
5561
|
+
*/
|
|
5562
|
+
contentSegments?: AgentWidgetContentSegment[];
|
|
5563
|
+
/**
|
|
5564
|
+
* Resolved opt-in structured mention context for this (user) message, merged
|
|
5565
|
+
* into the request `context` under `mentions.<sourceId>.<itemId>` by the
|
|
5566
|
+
* client. Internal: set at send time from each source's `resolve().context`.
|
|
5567
|
+
*/
|
|
5568
|
+
mentionContext?: Record<string, unknown>;
|
|
5027
5569
|
streaming?: boolean;
|
|
5028
5570
|
variant?: AgentWidgetMessageVariant;
|
|
5029
5571
|
sequence?: number;
|
|
@@ -402,7 +402,17 @@ describe("artifact pane expand toggle (full widget)", () => {
|
|
|
402
402
|
expect(mountEl.classList.contains("persona-artifact-expanded")).toBe(true);
|
|
403
403
|
|
|
404
404
|
// Turning the toggle back off hides the button and collapses the pane.
|
|
405
|
-
|
|
405
|
+
// update() patches recursively now, so omission preserves the prior
|
|
406
|
+
// showExpandToggle: the flag must be set false explicitly to disable it.
|
|
407
|
+
controller.update({
|
|
408
|
+
features: {
|
|
409
|
+
artifacts: {
|
|
410
|
+
enabled: true,
|
|
411
|
+
allowedTypes: ["markdown", "component"],
|
|
412
|
+
layout: { showExpandToggle: false },
|
|
413
|
+
},
|
|
414
|
+
},
|
|
415
|
+
});
|
|
406
416
|
expect(mountEl.classList.contains("persona-artifact-expanded")).toBe(false);
|
|
407
417
|
expect(expandBtn(mountEl).classList.contains("persona-hidden")).toBe(true);
|
|
408
418
|
controller.destroy();
|
|
@@ -186,3 +186,93 @@ describe("createAgentExperience attachment file drop", () => {
|
|
|
186
186
|
controller.destroy();
|
|
187
187
|
});
|
|
188
188
|
});
|
|
189
|
+
|
|
190
|
+
describe("drop overlay live config updates", () => {
|
|
191
|
+
beforeEach(() => {
|
|
192
|
+
window.scrollTo = vi.fn();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
afterEach(() => {
|
|
196
|
+
document.body.innerHTML = "";
|
|
197
|
+
vi.restoreAllMocks();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("rebuilds the overlay with new dropOverlay values on update() (regression: built once, live updates ignored)", () => {
|
|
201
|
+
const mount = createMount();
|
|
202
|
+
const controller = createAgentExperience(mount, {
|
|
203
|
+
apiUrl: "https://api.example.com/chat",
|
|
204
|
+
launcher: { enabled: false },
|
|
205
|
+
attachments: { enabled: true, dropOverlay: { label: "Drop it", background: "#111111" } },
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
const overlay = () => mount.querySelector<HTMLElement>(".persona-attachment-drop-overlay")!;
|
|
209
|
+
expect(overlay()).not.toBeNull();
|
|
210
|
+
expect(overlay().querySelector(".persona-drop-overlay-label")!.textContent).toBe("Drop it");
|
|
211
|
+
expect(overlay().style.getPropertyValue("--persona-drop-overlay-bg")).toBe("#111111");
|
|
212
|
+
|
|
213
|
+
controller.update({
|
|
214
|
+
attachments: { dropOverlay: { label: "New label", background: "#222222" } },
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
expect(overlay().querySelector(".persona-drop-overlay-label")!.textContent).toBe("New label");
|
|
218
|
+
expect(overlay().style.getPropertyValue("--persona-drop-overlay-bg")).toBe("#222222");
|
|
219
|
+
// Only one overlay after the rebuild.
|
|
220
|
+
expect(mount.querySelectorAll(".persona-attachment-drop-overlay").length).toBe(1);
|
|
221
|
+
|
|
222
|
+
controller.destroy();
|
|
223
|
+
});
|
|
224
|
+
|
|
225
|
+
it("clears overlay styling when dropOverlay is reset via explicit undefined", () => {
|
|
226
|
+
const mount = createMount();
|
|
227
|
+
const controller = createAgentExperience(mount, {
|
|
228
|
+
apiUrl: "https://api.example.com/chat",
|
|
229
|
+
launcher: { enabled: false },
|
|
230
|
+
attachments: { enabled: true, dropOverlay: { label: "Drop it", background: "#111111" } },
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
controller.update({ attachments: { dropOverlay: undefined } });
|
|
234
|
+
|
|
235
|
+
const overlay = mount.querySelector<HTMLElement>(".persona-attachment-drop-overlay")!;
|
|
236
|
+
expect(overlay).not.toBeNull();
|
|
237
|
+
expect(overlay.querySelector(".persona-drop-overlay-label")).toBeNull();
|
|
238
|
+
expect(overlay.style.getPropertyValue("--persona-drop-overlay-bg")).toBe("");
|
|
239
|
+
|
|
240
|
+
controller.destroy();
|
|
241
|
+
});
|
|
242
|
+
});
|
|
243
|
+
|
|
244
|
+
describe("attachment button live config updates", () => {
|
|
245
|
+
beforeEach(() => {
|
|
246
|
+
window.scrollTo = vi.fn();
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
afterEach(() => {
|
|
250
|
+
document.body.innerHTML = "";
|
|
251
|
+
vi.restoreAllMocks();
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
it("re-renders the button icon and tooltip on update() (regression: set once at creation)", () => {
|
|
255
|
+
const mount = createMount();
|
|
256
|
+
const controller = createAgentExperience(mount, {
|
|
257
|
+
apiUrl: "https://api.example.com/chat",
|
|
258
|
+
launcher: { enabled: false },
|
|
259
|
+
attachments: { enabled: true, buttonIconName: "paperclip", buttonTooltipText: "Attach file" },
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
const button = () => mount.querySelector<HTMLButtonElement>(".persona-attachment-button")!;
|
|
263
|
+
expect(button()).not.toBeNull();
|
|
264
|
+
const initialSvg = button().querySelector("svg")!.outerHTML;
|
|
265
|
+
|
|
266
|
+
controller.update({
|
|
267
|
+
attachments: { buttonIconName: "camera", buttonTooltipText: "Add a photo" },
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
expect(button().getAttribute("aria-label")).toBe("Add a photo");
|
|
271
|
+
const updatedSvg = button().querySelector("svg")!.outerHTML;
|
|
272
|
+
expect(updatedSvg).not.toBe(initialSvg);
|
|
273
|
+
const tooltip = button().parentElement!.querySelector(".persona-send-button-tooltip");
|
|
274
|
+
expect(tooltip?.textContent).toBe("Add a photo");
|
|
275
|
+
|
|
276
|
+
controller.destroy();
|
|
277
|
+
});
|
|
278
|
+
});
|