@artooi/ag-ui-web-component 0.34.0 → 0.35.1

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 (58) hide show
  1. package/CHANGELOG.md +517 -1
  2. package/README.md +243 -13
  3. package/dist/ag-ui-web-component.bundle.js +614 -96
  4. package/dist/ag-ui-web-component.bundle.js.map +4 -4
  5. package/dist/constants.d.ts +76 -3
  6. package/dist/constants.d.ts.map +1 -1
  7. package/dist/core/ag_ui_chat.d.ts +37 -2
  8. package/dist/core/ag_ui_chat.d.ts.map +1 -1
  9. package/dist/dom/animations.d.ts +14 -0
  10. package/dist/dom/animations.d.ts.map +1 -1
  11. package/dist/dom/highlight_overlay.d.ts +47 -0
  12. package/dist/dom/highlight_overlay.d.ts.map +1 -0
  13. package/dist/index.d.ts +2 -0
  14. package/dist/index.d.ts.map +1 -1
  15. package/dist/index.js +1886 -320
  16. package/dist/index.js.map +4 -4
  17. package/dist/tools/chat_surface_tools.d.ts +96 -0
  18. package/dist/tools/chat_surface_tools.d.ts.map +1 -0
  19. package/dist/tools/page_action_tools.d.ts +2 -0
  20. package/dist/tools/page_action_tools.d.ts.map +1 -1
  21. package/dist/ui/clamp_launcher.d.ts +10 -5
  22. package/dist/ui/clamp_launcher.d.ts.map +1 -1
  23. package/dist/ui/clamp_panel.d.ts +2 -2
  24. package/dist/ui/clamp_panel.d.ts.map +1 -1
  25. package/dist/ui/launcher_drag.d.ts +2 -2
  26. package/dist/ui/launcher_drag.d.ts.map +1 -1
  27. package/dist/ui/launcher_placement.d.ts +14 -1
  28. package/dist/ui/launcher_placement.d.ts.map +1 -1
  29. package/dist/ui/panel_drag.d.ts.map +1 -1
  30. package/dist/ui/place_widget.d.ts +9 -1
  31. package/dist/ui/place_widget.d.ts.map +1 -1
  32. package/dist/ui/run_notice.d.ts +14 -3
  33. package/dist/ui/run_notice.d.ts.map +1 -1
  34. package/dist/ui/styles.d.ts +1 -1
  35. package/dist/ui/styles.d.ts.map +1 -1
  36. package/dist/ui/thread_drawer.d.ts +21 -0
  37. package/dist/ui/thread_drawer.d.ts.map +1 -1
  38. package/dist/ui/ui_strings.d.ts +16 -0
  39. package/dist/ui/ui_strings.d.ts.map +1 -1
  40. package/package.json +1 -1
  41. package/src/constants.ts +82 -3
  42. package/src/core/ag_ui_chat.ts +965 -55
  43. package/src/dom/animations.ts +30 -0
  44. package/src/dom/highlight_overlay.ts +256 -0
  45. package/src/index.ts +12 -0
  46. package/src/tools/chat_surface_tools.ts +207 -0
  47. package/src/tools/page_action_tools.ts +2 -0
  48. package/src/ui/clamp_launcher.ts +25 -7
  49. package/src/ui/clamp_panel.ts +10 -4
  50. package/src/ui/launcher_drag.ts +11 -2
  51. package/src/ui/launcher_placement.ts +34 -8
  52. package/src/ui/panel_drag.ts +4 -0
  53. package/src/ui/place_widget.ts +11 -3
  54. package/src/ui/run_notice.ts +32 -3
  55. package/src/ui/styles.ts +563 -45
  56. package/src/ui/thread_drawer.ts +138 -8
  57. package/src/ui/ui_strings.ts +24 -0
  58. package/src/version.ts +1 -1
package/dist/index.js CHANGED
@@ -59,6 +59,8 @@ var ICON_SEND = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path
59
59
  var ICON_STOP = `<svg class="glyph glyph--solid" viewBox="0 0 24 24" aria-hidden="true"><rect x="7" y="7" width="10" height="10" rx="2.5"/></svg>`;
60
60
  var ICON_ATTACH = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M17 8.5V15a5 5 0 0 1-10 0V7a3 3 0 0 1 6 0v7.5a1 1 0 0 1-2 0V8.5"/></svg>`;
61
61
  var ICON_VOICE = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M12 4a3 3 0 0 1 3 3v5a3 3 0 0 1-6 0V7a3 3 0 0 1 3-3z"/><path d="M5 11v1a7 7 0 0 0 14 0v-1"/><path d="M12 19v3"/></svg>`;
62
+ var ICON_MOON = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M20 14.5A8.5 8.5 0 0 1 9.5 4a8.5 8.5 0 1 0 10.5 10.5z"/></svg>`;
63
+ var ICON_SUN = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><circle cx="12" cy="12" r="4"/><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"/></svg>`;
62
64
  var ICON_LAUNCHER = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M4 5.5A2.5 2.5 0 0 1 6.5 3h11A2.5 2.5 0 0 1 20 5.5v8a2.5 2.5 0 0 1-2.5 2.5H9l-5 4z"/></svg>`;
63
65
  var ICON_COPY = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><rect x="9" y="9" width="11" height="11" rx="2"/><path d="M5 15V6a2 2 0 0 1 2-2h8"/></svg>`;
64
66
  var ICON_RETRY = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M20 12a8 8 0 1 1-2.5-5.8"/><path d="M20 4v4h-4"/></svg>`;
@@ -71,6 +73,10 @@ var ICON_FILE_TEXT = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true">
71
73
  var CHART_ACTIVITY_TYPE = "chart";
72
74
  var EDGE_MARGIN = 24;
73
75
  var ANNOUNCE_CLEAR_MS = 150;
76
+ var HIGHLIGHT_OVERLAY_Z_INDEX = 2147483001;
77
+ var THREADS_DOCK_MIN_WIDTH = 900;
78
+ var PASTE_ATTACH_CHARS = 5e3;
79
+ var SCREEN_EDGE_MARGIN = 8;
74
80
 
75
81
  // src/core/ag_ui_chat.ts
76
82
  import { randomUUID as randomUUID5 } from "@ag-ui/client";
@@ -107,6 +113,95 @@ function parseSkills(value) {
107
113
  return value.filter(isSkill);
108
114
  }
109
115
 
116
+ // src/tools/chat_surface_tools.ts
117
+ var CHAT_CORNERS = ["top-left", "top-right", "bottom-left", "bottom-right"];
118
+ function isChatCorner(value) {
119
+ return CHAT_CORNERS.includes(value);
120
+ }
121
+ function createChatSurfaceTools(surface) {
122
+ return [
123
+ {
124
+ name: "read_chat_surface",
125
+ description: "Describe the chat panel you are speaking from: its placement, whether it is collapsed, whether it can be moved, and the box it occupies. Read this before moving or minimising yourself, since a full-screen panel has nowhere to move to. Read-only.",
126
+ parameters: {
127
+ type: "object",
128
+ properties: {},
129
+ required: [],
130
+ [X_SUMMARY_KEY]: "Read the chat's own position"
131
+ },
132
+ handler: () => surface.describeSurface()
133
+ },
134
+ {
135
+ name: "move_chat",
136
+ description: "Move your own panel to a corner, to uncover something the user needs to see. `corner` is top-left, top-right, bottom-left or bottom-right. Answers `moved: false` with a reason when the placement owns its position or the panel fills the screen; check `read_chat_surface` first, and prefer minimise_chat when there is nowhere to move to.",
137
+ parameters: {
138
+ type: "object",
139
+ properties: {
140
+ corner: {
141
+ type: "string",
142
+ enum: [...CHAT_CORNERS]
143
+ }
144
+ },
145
+ required: ["corner"],
146
+ [X_SUMMARY_KEY]: "Move the chat out of the way"
147
+ },
148
+ handler: (args) => {
149
+ const asked = String(args["corner"] ?? "");
150
+ const report = surface.describeSurface();
151
+ if (!isChatCorner(asked)) {
152
+ return {
153
+ moved: false,
154
+ reason: `"${asked}" is not a corner; use one of ${CHAT_CORNERS.join(", ")}`
155
+ };
156
+ }
157
+ if (!report.movable) {
158
+ return {
159
+ moved: false,
160
+ reason: report.fullBleed ? "the panel fills the screen, so there is nowhere to move it to" : report.draggable === false ? "this page has turned off moving the panel" : `the "${report.placement ?? ""}" placement owns the panel's position`,
161
+ suggestion: report.collapsible ? "minimise_chat" : null
162
+ };
163
+ }
164
+ return { moved: surface.moveTo(asked, { announce: true }), corner: asked };
165
+ }
166
+ },
167
+ {
168
+ name: "minimise_chat",
169
+ description: "Collapse your own panel to its launcher, so the user can see the whole page. The launcher stays visible and reopens it. Answers `minimised: false` when the placement has no collapsed state, which is the case for a full-page chat.",
170
+ parameters: {
171
+ type: "object",
172
+ properties: {},
173
+ required: [],
174
+ [X_SUMMARY_KEY]: "Minimise the chat"
175
+ },
176
+ handler: () => {
177
+ const report = surface.describeSurface();
178
+ if (!report.collapsible) {
179
+ return {
180
+ minimised: false,
181
+ reason: "this placement has no collapsed state, so there is no launcher to return to"
182
+ };
183
+ }
184
+ surface.setCollapsed(true, { announce: true });
185
+ return { minimised: true };
186
+ }
187
+ },
188
+ {
189
+ name: "restore_chat",
190
+ description: "Open your own panel again after minimising it.",
191
+ parameters: {
192
+ type: "object",
193
+ properties: {},
194
+ required: [],
195
+ [X_SUMMARY_KEY]: "Restore the chat"
196
+ },
197
+ handler: () => {
198
+ surface.setCollapsed(false);
199
+ return { restored: true };
200
+ }
201
+ }
202
+ ];
203
+ }
204
+
110
205
  // src/tools/client_tool_registry.ts
111
206
  var ClientToolRegistry = class {
112
207
  #tools = /* @__PURE__ */ new Map();
@@ -150,6 +245,133 @@ function isNavigates(parameters) {
150
245
  return parameters[X_NAVIGATES_KEY] === true;
151
246
  }
152
247
 
248
+ // src/dom/highlight_overlay.ts
249
+ var ACCENT = "#4f46e5";
250
+ function tokenOn(target, name, fallback) {
251
+ const value = window.getComputedStyle(target).getPropertyValue(name).trim();
252
+ return value === "" ? fallback : value;
253
+ }
254
+ var DEFAULT_PADDING_PX = 4;
255
+ function showHighlightOverlay(target, options = {}) {
256
+ const root = document.createElement("div");
257
+ root.setAttribute("data-ag-ui-highlight", "");
258
+ root.setAttribute("aria-hidden", "true");
259
+ root.style.cssText = [
260
+ "position: fixed",
261
+ "inset: 0",
262
+ "pointer-events: none",
263
+ // Above the widget's own default, because the overlay's whole job is to
264
+ // point at something on the page the widget is sitting over. A host whose
265
+ // chrome stacks higher still can say so.
266
+ `z-index: ${tokenOn(target, "--ag-ui-highlight-z-index", String(HIGHLIGHT_OVERLAY_Z_INDEX))}`
267
+ ].join(";");
268
+ const scrim = document.createElement("div");
269
+ const ring = document.createElement("div");
270
+ root.className = "ag-ui-highlight";
271
+ scrim.className = "ag-ui-highlight-scrim";
272
+ ring.className = "ag-ui-highlight-ring";
273
+ if (options.scrim === true) {
274
+ root.append(scrim);
275
+ }
276
+ root.append(ring);
277
+ const ringWidth = options.ringWidth ?? Number.parseFloat(tokenOn(target, "--ag-ui-highlight-ring-width", "3"));
278
+ const flowMs = options.flowMs ?? Number.parseFloat(tokenOn(target, "--ag-ui-highlight-flow-ms", "2400"));
279
+ const paint = () => {
280
+ const box = target.getBoundingClientRect();
281
+ const pad = options.padding ?? DEFAULT_PADDING_PX;
282
+ const radius = options.radius ?? Number.parseFloat(getComputedStyle(target).borderRadius);
283
+ const left = box.left - pad;
284
+ const top = box.top - pad;
285
+ const width = box.width + pad * 2;
286
+ const height = box.height + pad * 2;
287
+ if (options.scrim === true) {
288
+ scrim.style.cssText = [
289
+ "position: absolute",
290
+ "inset: 0",
291
+ `background: ${tokenOn(target, "--ag-ui-highlight-scrim", "rgba(15, 15, 25, 0.45)")}`,
292
+ `clip-path: path(evenodd, '${holePath(left, top, width, height, radius + pad)}')`
293
+ ].join(";");
294
+ }
295
+ ring.style.cssText = [
296
+ "position: absolute",
297
+ `left: ${left}px`,
298
+ `top: ${top}px`,
299
+ `width: ${width}px`,
300
+ `height: ${height}px`,
301
+ `border-radius: ${radius + pad}px`,
302
+ `border: ${ringWidth}px solid transparent`,
303
+ "box-sizing: border-box",
304
+ ringPaint(target, options)
305
+ ].join(";");
306
+ };
307
+ paint();
308
+ let flow = null;
309
+ if (options.gradient === true && !window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
310
+ flow = ring.animate([{ backgroundPosition: "100% 0" }, { backgroundPosition: "-100% 0" }], {
311
+ duration: flowMs,
312
+ iterations: Number.POSITIVE_INFINITY,
313
+ easing: "linear"
314
+ });
315
+ }
316
+ const listen = { capture: true, passive: true };
317
+ window.addEventListener("scroll", paint, listen);
318
+ window.addEventListener("resize", paint, listen);
319
+ document.body.appendChild(root);
320
+ return () => {
321
+ window.removeEventListener("scroll", paint, listen);
322
+ window.removeEventListener("resize", paint, listen);
323
+ flow?.cancel();
324
+ root.remove();
325
+ };
326
+ }
327
+ function safeColor(value) {
328
+ if (value === void 0) {
329
+ return null;
330
+ }
331
+ return CSS.supports("color", value) ? value : null;
332
+ }
333
+ function ringPaint(target, options) {
334
+ const color = safeColor(options.color) ?? tokenOn(target, "--ag-ui-accent", ACCENT);
335
+ if (options.gradient !== true) {
336
+ return `border-color: ${color}`;
337
+ }
338
+ const image = tokenOn(
339
+ target,
340
+ "--ag-ui-highlight-gradient",
341
+ `linear-gradient(115deg, transparent 20%, ${color} 50%, transparent 80%)`
342
+ );
343
+ const mask = "linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0)";
344
+ return [
345
+ `background-image: ${image}`,
346
+ "background-origin: border-box",
347
+ "background-size: 300% 100%",
348
+ `-webkit-mask: ${mask}`,
349
+ `mask: ${mask}`,
350
+ "-webkit-mask-composite: xor",
351
+ "mask-composite: exclude",
352
+ "background-position: 50% 0"
353
+ ].join(";");
354
+ }
355
+ function holePath(x2, y2, width, height, radius) {
356
+ const r = Math.max(0, Math.min(radius, width / 2, height / 2));
357
+ const right = x2 + width;
358
+ const bottom = y2 + height;
359
+ const outer = `M 0 0 H ${window.innerWidth} V ${window.innerHeight} H 0 Z`;
360
+ const hole = [
361
+ `M ${x2 + r} ${y2}`,
362
+ `H ${right - r}`,
363
+ `A ${r} ${r} 0 0 1 ${right} ${y2 + r}`,
364
+ `V ${bottom - r}`,
365
+ `A ${r} ${r} 0 0 1 ${right - r} ${bottom}`,
366
+ `H ${x2 + r}`,
367
+ `A ${r} ${r} 0 0 1 ${x2} ${bottom - r}`,
368
+ `V ${y2 + r}`,
369
+ `A ${r} ${r} 0 0 1 ${x2 + r} ${y2}`,
370
+ "Z"
371
+ ].join(" ");
372
+ return `${outer} ${hole}`;
373
+ }
374
+
153
375
  // src/dom/native_setter.ts
154
376
  function prototypeSetter(proto, prop) {
155
377
  const setter = Object.getOwnPropertyDescriptor(proto, prop).set;
@@ -173,7 +395,7 @@ function setNativeChecked(el2, checked) {
173
395
  }
174
396
 
175
397
  // src/dom/animations.ts
176
- var ACCENT = "#4f46e5";
398
+ var ACCENT2 = "#4f46e5";
177
399
  var ACCENT_HALO = "rgba(79, 70, 229, 0.4)";
178
400
  var ACCENT_PROPERTY = "--ag-ui-accent";
179
401
  function delay(ms) {
@@ -214,7 +436,7 @@ async function highlightThenClick(el2, options = {}) {
214
436
  const highlightMs = options.highlightMs ?? 280;
215
437
  const previousOutline = el2.style.outline;
216
438
  const previousOffset = el2.style.outlineOffset;
217
- el2.style.outline = `2px solid ${accentColor(el2, ACCENT)}`;
439
+ el2.style.outline = `2px solid ${accentColor(el2, ACCENT2)}`;
218
440
  el2.style.outlineOffset = "2px";
219
441
  await delay(highlightMs);
220
442
  el2.style.outline = previousOutline;
@@ -261,10 +483,21 @@ async function flashRing(el2, options, focusByDefault) {
261
483
  if (flashMs <= 0) {
262
484
  return;
263
485
  }
486
+ if (options.scrim === true || options.gradient === true) {
487
+ const dismiss = showHighlightOverlay(el2, {
488
+ scrim: options.scrim === true,
489
+ gradient: options.gradient === true,
490
+ ...options.color === void 0 ? {} : { color: options.color },
491
+ ...options.ringPadding === void 0 ? {} : { padding: options.ringPadding }
492
+ });
493
+ await delay(flashMs);
494
+ dismiss();
495
+ return;
496
+ }
264
497
  const previousOutline = el2.style.outline;
265
498
  const previousOffset = el2.style.outlineOffset;
266
499
  const previousTransition = el2.style.transition;
267
- const color = options.color ?? accentColor(el2, ACCENT);
500
+ const color = options.color ?? accentColor(el2, ACCENT2);
268
501
  el2.style.outline = `3px solid ${color}`;
269
502
  el2.style.outlineOffset = "2px";
270
503
  const fadeMs = prefersReducedMotion() ? 0 : Math.round(flashMs * FLASH_FADE_RATIO);
@@ -314,7 +547,7 @@ async function selectOption(el2, value, options = {}) {
314
547
  const highlightMs = options.highlightMs ?? 220;
315
548
  const previousOutline = el2.style.outline;
316
549
  const previousOffset = el2.style.outlineOffset;
317
- el2.style.outline = `2px solid ${accentColor(el2, ACCENT)}`;
550
+ el2.style.outline = `2px solid ${accentColor(el2, ACCENT2)}`;
318
551
  el2.style.outlineOffset = "2px";
319
552
  await motionDelay(highlightMs);
320
553
  setNativeValue(el2, option.value);
@@ -337,7 +570,9 @@ async function toggleControl(el2, checked, options = {}) {
337
570
  // src/tools/page_action_tools.ts
338
571
  var PAGE_ACTIONS = {
339
572
  SCROLL: "scroll",
340
- DRAG: "drag"
573
+ DRAG: "drag",
574
+ /** The widget's own position: see `createChatSurfaceTools`. */
575
+ CHAT: "chat"
341
576
  };
342
577
  function createPageActionTools(enabled, resolveTarget) {
343
578
  const tools = [];
@@ -547,6 +782,14 @@ function createRouteTools(getRouteMap, getNavigate) {
547
782
  var DEFAULT_UI_STRINGS = {
548
783
  title: "Assistant",
549
784
  chatHistory: "Chat history",
785
+ closeHistory: "Close history",
786
+ searchConversations: "Search conversations",
787
+ queued: "Waiting to send",
788
+ removeQueued: 'Do not send "{text}"',
789
+ noMatches: "No conversations match that.",
790
+ chatMoved: "Moved this panel out of the way",
791
+ chatMinimised: "Minimised this panel",
792
+ undo: "Undo",
550
793
  newChat: "New chat",
551
794
  collapse: "Collapse",
552
795
  expand: "Expand",
@@ -1750,10 +1993,18 @@ var CheckpointMenu = class {
1750
1993
  };
1751
1994
 
1752
1995
  // src/ui/clamp_launcher.ts
1753
- function clampLauncher(launcher, viewport) {
1996
+ function clampLauncher(launcher, viewport, margin = SCREEN_EDGE_MARGIN) {
1997
+ const room = Math.min(margin, Math.max(0, (viewport.width - launcher.width) / 2));
1998
+ const vertical = Math.min(margin, Math.max(0, (viewport.height - launcher.height) / 2));
1754
1999
  return {
1755
- left: Math.max(0, Math.min(launcher.left, viewport.width - launcher.width)),
1756
- top: Math.max(0, Math.min(launcher.top, viewport.height - launcher.height))
2000
+ left: Math.max(
2001
+ viewport.left + room,
2002
+ Math.min(launcher.left, viewport.left + viewport.width - launcher.width - room)
2003
+ ),
2004
+ top: Math.max(
2005
+ viewport.top + vertical,
2006
+ Math.min(launcher.top, viewport.top + viewport.height - launcher.height - vertical)
2007
+ )
1757
2008
  };
1758
2009
  }
1759
2010
 
@@ -1761,8 +2012,14 @@ function clampLauncher(launcher, viewport) {
1761
2012
  function clampPanel(host, viewport, margin = EDGE_MARGIN) {
1762
2013
  const width = host.right - host.left;
1763
2014
  const height = host.bottom - host.top;
1764
- const left = Math.max(margin, Math.min(host.left, viewport.width - margin - width));
1765
- const top = Math.max(margin, Math.min(host.top, viewport.height - margin - height));
2015
+ const left = Math.max(
2016
+ viewport.left + margin,
2017
+ Math.min(host.left, viewport.left + viewport.width - margin - width)
2018
+ );
2019
+ const top = Math.max(
2020
+ viewport.top + margin,
2021
+ Math.min(host.top, viewport.top + viewport.height - margin - height)
2022
+ );
1766
2023
  return { left, top, right: left + width, bottom: top + height };
1767
2024
  }
1768
2025
 
@@ -1967,6 +2224,7 @@ function enableLauncherDrag(launcher, options) {
1967
2224
  const onUp = (up) => {
1968
2225
  window.removeEventListener("pointermove", onMove);
1969
2226
  window.removeEventListener("pointerup", onUp);
2227
+ window.removeEventListener("pointercancel", onUp);
1970
2228
  if (!dragging) {
1971
2229
  return;
1972
2230
  }
@@ -1984,6 +2242,7 @@ function enableLauncherDrag(launcher, options) {
1984
2242
  };
1985
2243
  window.addEventListener("pointermove", onMove);
1986
2244
  window.addEventListener("pointerup", onUp);
2245
+ window.addEventListener("pointercancel", onUp);
1987
2246
  });
1988
2247
  let pending = null;
1989
2248
  const settle = () => {
@@ -2023,12 +2282,12 @@ function enableLauncherDrag(launcher, options) {
2023
2282
  }
2024
2283
 
2025
2284
  // src/ui/place_widget.ts
2026
- function placeWidget(host, launcher, corner, viewport) {
2285
+ function placeWidget(host, launcher, corner, screen) {
2027
2286
  return {
2028
2287
  hostInset: inset({
2029
2288
  top: corner.y === "top" ? host.top : null,
2030
- right: corner.x === "right" ? viewport.width - host.right : null,
2031
- bottom: corner.y === "bottom" ? viewport.height - host.bottom : null,
2289
+ right: corner.x === "right" ? screen.width - host.right : null,
2290
+ bottom: corner.y === "bottom" ? screen.height - host.bottom : null,
2032
2291
  left: corner.x === "left" ? host.left : null
2033
2292
  }),
2034
2293
  launcherInset: inset({
@@ -2045,11 +2304,11 @@ function inset(sides) {
2045
2304
  }
2046
2305
 
2047
2306
  // src/ui/launcher_placement.ts
2048
- function launcherPlacement(launcher, panel, viewport, margin = EDGE_MARGIN) {
2049
- const roomRunningRight = viewport.width - launcher.left;
2050
- const roomRunningLeft = launcher.left + launcher.width;
2051
- const roomRunningDown = viewport.height - launcher.top;
2052
- const roomRunningUp = launcher.top + launcher.height;
2307
+ function launcherPlacement(launcher, panel, viewport, screen, margin = SCREEN_EDGE_MARGIN) {
2308
+ const roomRunningRight = viewport.left + viewport.width - launcher.left;
2309
+ const roomRunningLeft = launcher.left + launcher.width - viewport.left;
2310
+ const roomRunningDown = viewport.top + viewport.height - launcher.top;
2311
+ const roomRunningUp = launcher.top + launcher.height - viewport.top;
2053
2312
  const corner = {
2054
2313
  x: roomRunningRight >= roomRunningLeft ? "left" : "right",
2055
2314
  y: roomRunningDown >= roomRunningUp ? "top" : "bottom"
@@ -2066,7 +2325,7 @@ function launcherPlacement(launcher, panel, viewport, margin = EDGE_MARGIN) {
2066
2325
  viewport,
2067
2326
  margin
2068
2327
  );
2069
- return { corner, ...placeWidget(host, launcher, corner, viewport) };
2328
+ return { corner, ...placeWidget(host, launcher, corner, screen) };
2070
2329
  }
2071
2330
 
2072
2331
  // src/ui/message_actions.ts
@@ -2470,6 +2729,7 @@ function enablePanelDrag(handle, options) {
2470
2729
  const onUp = (up) => {
2471
2730
  window.removeEventListener("pointermove", onMove);
2472
2731
  window.removeEventListener("pointerup", onUp);
2732
+ window.removeEventListener("pointercancel", onUp);
2473
2733
  if (!dragging) {
2474
2734
  return;
2475
2735
  }
@@ -2479,6 +2739,7 @@ function enablePanelDrag(handle, options) {
2479
2739
  event.preventDefault();
2480
2740
  window.addEventListener("pointermove", onMove);
2481
2741
  window.addEventListener("pointerup", onUp);
2742
+ window.addEventListener("pointercancel", onUp);
2482
2743
  });
2483
2744
  }
2484
2745
  function onControl(event, handle) {
@@ -4222,111 +4483,111 @@ function createDOMPurify() {
4222
4483
  }
4223
4484
  var purify = createDOMPurify();
4224
4485
 
4225
- // node_modules/.pnpm/marked@18.0.10/node_modules/marked/lib/marked.esm.js
4226
- function C() {
4486
+ // node_modules/.pnpm/marked@18.0.11/node_modules/marked/lib/marked.esm.js
4487
+ function A() {
4227
4488
  return { async: false, breaks: false, extensions: null, gfm: true, hooks: null, pedantic: false, renderer: null, silent: false, tokenizer: null, walkTokens: null };
4228
4489
  }
4229
- var R = C();
4490
+ var R = A();
4230
4491
  function j(l3) {
4231
4492
  R = l3;
4232
4493
  }
4233
4494
  var z = { exec: () => null };
4234
- function A(l3) {
4495
+ function I(l3) {
4235
4496
  let e = [];
4236
4497
  return (t) => {
4237
4498
  let n = Math.max(0, Math.min(3, t - 1)), s = e[n];
4238
4499
  return s || (s = l3(n), e[n] = s), s;
4239
4500
  };
4240
4501
  }
4241
- function d(l3, e = "") {
4502
+ function k(l3, e = "") {
4242
4503
  let t = typeof l3 == "string" ? l3 : l3.source, n = { replace: (s, r) => {
4243
4504
  let i = typeof r == "string" ? r : r.source;
4244
4505
  return i = i.replace(m.caret, "$1"), t = t.replace(s, i), n;
4245
4506
  }, getRegex: () => new RegExp(t, e) };
4246
4507
  return n;
4247
4508
  }
4248
- var Te = ((l3 = "") => {
4509
+ var Oe = ((l3 = "") => {
4249
4510
  try {
4250
4511
  return !!new RegExp("(?<=1)(?<!1)" + l3);
4251
4512
  } catch {
4252
4513
  return false;
4253
4514
  }
4254
4515
  })();
4255
- var m = { codeRemoveIndent: /^(?: {1,4}| {0,3}\t)/gm, outputLinkReplace: /\\([\[\]])/g, indentCodeCompensation: /^(\s+)(?:```)/, beginningSpace: /^\s+/, endingHash: /#$/, startingSpaceChar: /^ /, endingSpaceChar: / $/, nonSpaceChar: /[^ ]/, newLineCharGlobal: /\n/g, tabCharGlobal: /\t/g, multipleSpaceGlobal: /\s+/g, blankLine: /^[ \t]*$/, doubleBlankLine: /\n[ \t]*\n[ \t]*$/, blockquoteStart: /^ {0,3}>/, blockquoteSetextReplace: /\n {0,3}((?:=+|-+) *)(?=\n|$)/g, blockquoteSetextReplace2: /^ {0,3}>[ \t]?/gm, listReplaceNesting: /^ {1,4}(?=( {4})*[^ ])/g, listIsTask: /^\[[ xX]\] +\S/, listReplaceTask: /^\[[ xX]\] +/, listTaskCheckbox: /\[[ xX]\]/, anyLine: /\n.*\n/, hrefBrackets: /^<(.*)>$/, tableDelimiter: /[:|]/, tableAlignChars: /^\||\| *$/g, tableRowBlankLine: /\n[ \t]*$/, tableAlignRight: /^ *-+: *$/, tableAlignCenter: /^ *:-+: *$/, tableAlignLeft: /^ *:-+ *$/, startATag: /^<a /i, endATag: /^<\/a>/i, startPreScriptTag: /^<(pre|code|kbd|script)(\s|>)/i, endPreScriptTag: /^<\/(pre|code|kbd|script)(\s|>)/i, startAngleBracket: /^</, endAngleBracket: />$/, pedanticHrefTitle: /^([^'"]*[^\s])\s+(['"])(.*)\2/, unicodeAlphaNumeric: /[\p{L}\p{N}]/u, escapeTest: /[&<>"']/, escapeReplace: /[&<>"']/g, escapeTestNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/, escapeReplaceNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g, caret: /(^|[^\[])\^/g, percentDecode: /%25/g, findPipe: /\|/g, splitPipe: / \|/, slashPipe: /\\\|/g, carriageReturn: /\r\n|\r/g, spaceLine: /^ +$/gm, notSpaceStart: /^\S*/, endingNewline: /\n$/, listItemRegex: (l3) => new RegExp(`^( {0,3}${l3})((?:[ ][^\\n]*)?(?:\\n|$))`), nextBulletRegex: A((l3) => new RegExp(`^ {0,${l3}}(?:[*+-]|\\d{1,9}[.)])((?:[ ][^\\n]*)?(?:\\n|$))`)), hrRegex: A((l3) => new RegExp(`^ {0,${l3}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`)), fencesBeginRegex: A((l3) => new RegExp(`^ {0,${l3}}(?:\`\`\`|~~~)`)), headingBeginRegex: A((l3) => new RegExp(`^ {0,${l3}}#`)), htmlBeginRegex: A((l3) => new RegExp(`^ {0,${l3}}<(?:[a-z].*>|!--)`, "i")), blockquoteBeginRegex: A((l3) => new RegExp(`^ {0,${l3}}>`)) };
4256
- var Oe = /^(?:[ \t]*(?:\n|$))+/;
4516
+ var m = { codeRemoveIndent: /^(?: {1,4}| {0,3}\t)/gm, outputLinkReplace: /\\([\[\]])/g, indentCodeCompensation: /^(\s+)(?:```)/, beginningSpace: /^\s+/, endingHash: /#$/, startingSpaceChar: /^ /, endingSpaceChar: / $/, nonSpaceChar: /[^ ]/, newLineCharGlobal: /\n/g, tabCharGlobal: /\t/g, multipleSpaceGlobal: /\s+/g, blankLine: /^[ \t]*$/, doubleBlankLine: /\n[ \t]*\n[ \t]*$/, blockquoteStart: /^ {0,3}>/, blockquoteSetextReplace: /\n {0,3}((?:=+|-+) *)(?=\n|$)/g, blockquoteSetextReplace2: /^ {0,3}>[ \t]?/gm, listReplaceNesting: /^ {1,4}(?=( {4})*[^ ])/g, listIsTask: /^\[[ xX]\] +\S/, listReplaceTask: /^\[[ xX]\] +/, listTaskCheckbox: /\[[ xX]\]/, anyLine: /\n.*\n/, hrefBrackets: /^<(.*)>$/, tableDelimiter: /[:|]/, tableAlignChars: /^\||\| *$/g, tableRowBlankLine: /\n[ \t]*$/, tableAlignRight: /^ *-+: *$/, tableAlignCenter: /^ *:-+: *$/, tableAlignLeft: /^ *:-+ *$/, startATag: /^<a /i, endATag: /^<\/a>/i, startPreScriptTag: /^<(pre|code|kbd|script)(\s|>)/i, endPreScriptTag: /^<\/(pre|code|kbd|script)(\s|>)/i, startAngleBracket: /^</, endAngleBracket: />$/, pedanticHrefTitle: /^([^'"]*[^\s])\s+(['"])(.*)\2/, unicodeAlphaNumeric: /[\p{L}\p{N}]/u, escapeTest: /[&<>"']/, escapeReplace: /[&<>"']/g, escapeTestNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/, escapeReplaceNoEncode: /[<>"']|&(?!(#\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\w+);)/g, caret: /(^|[^\[])\^/g, percentDecode: /%25/g, findPipe: /\|/g, splitPipe: / \|/, slashPipe: /\\\|/g, carriageReturn: /\r\n|\r/g, spaceLine: /^ +$/gm, notSpaceStart: /^\S*/, endingNewline: /\n$/, listItemRegex: (l3) => new RegExp(`^( {0,3}${l3})((?:[ ][^\\n]*)?(?:\\n|$))`), nextBulletRegex: I((l3) => new RegExp(`^ {0,${l3}}(?:[*+-]|\\d{1,9}[.)])((?:[ ][^\\n]*)?(?:\\n|$))`)), hrRegex: I((l3) => new RegExp(`^ {0,${l3}}((?:- *){3,}|(?:_ *){3,}|(?:\\* *){3,})(?:\\n+|$)`)), fencesBeginRegex: I((l3) => new RegExp(`^ {0,${l3}}(?:\`\`\`|~~~)`)), headingBeginRegex: I((l3) => new RegExp(`^ {0,${l3}}#`)), htmlBeginRegex: I((l3) => new RegExp(`^ {0,${l3}}<(?:[a-z].*>|!--)`, "i")), blockquoteBeginRegex: I((l3) => new RegExp(`^ {0,${l3}}>`)) };
4517
+ var Te = /^(?:[ \t]*(?:\n|$))+/;
4257
4518
  var we = /^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/;
4258
4519
  var ye = /^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/;
4259
4520
  var q = /^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/;
4260
4521
  var Pe = /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/;
4261
4522
  var U = / {0,3}(?:[*+-]|\d{1,9}[.)])/;
4262
4523
  var oe = /^(?!bull |blockCode|fences|blockquote|heading|html|table)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html|table))+?)\n {0,3}(=+|-+) *(?:\n+|$)/;
4263
- var ae = d(oe).replace(/bull/g, U).replace(/blockCode/g, /(?: {4}| {0,3}\t)/).replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g, / {0,3}>/).replace(/heading/g, / {0,3}#{1,6}(?:\s|$)/).replace(/html/g, / {0,3}<[^\n>]+>\n/).replace(/\|table/g, "").getRegex();
4264
- var Se = d(oe).replace(/bull/g, U).replace(/blockCode/g, /(?: {4}| {0,3}\t)/).replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g, / {0,3}>/).replace(/heading/g, / {0,3}#{1,6}(?:\s|$)/).replace(/html/g, / {0,3}<[^\n>]+>\n/).replace(/table/g, / {0,3}\|?(?:[:\- ]*\|)+[\:\- ]*\n/).getRegex();
4524
+ var ae = k(oe).replace(/bull/g, U).replace(/blockCode/g, /(?: {4}| {0,3}\t)/).replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g, / {0,3}>/).replace(/heading/g, / {0,3}#{1,6}(?:\s|$)/).replace(/html/g, / {0,3}<[^\n>]+>\n/).replace(/\|table/g, "").getRegex();
4525
+ var Se = k(oe).replace(/bull/g, U).replace(/blockCode/g, /(?: {4}| {0,3}\t)/).replace(/fences/g, / {0,3}(?:`{3,}|~{3,})/).replace(/blockquote/g, / {0,3}>/).replace(/heading/g, / {0,3}#{1,6}(?:\s|$)/).replace(/html/g, / {0,3}<[^\n>]+>\n/).replace(/table/g, / {0,3}\|?(?:[:\- ]*\|)+[\:\- ]*\n/).getRegex();
4265
4526
  var K = /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table|[ \t]+\n)[^\n]+)*)/;
4266
4527
  var _e = /^[^\n]+/;
4267
4528
  var W = /(?!\s*\])(?:\\[\s\S]|[^\[\]\\])+/;
4268
- var $e = d(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label", W).replace("title", /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex();
4269
- var Le = d(/^(bull)([ \t][^\n]*?)?(?:\n|$)/).replace(/bull/g, U).getRegex();
4529
+ var $e = k(/^ {0,3}\[(label)\]: *(?:\n[ \t]*)?([^<\s][^\s]*|<.*?>)(?:(?: +(?:\n[ \t]*)?| *\n[ \t]*)(title))? *(?:\n+|$)/).replace("label", W).replace("title", /(?:"(?:\\"?|[^"\\])*"|'[^'\n]*(?:\n[^'\n]+)*\n?'|\([^()]*\))/).getRegex();
4530
+ var Le = k(/^(bull)([ \t][^\n]*?)?(?:\n|$)/).replace(/bull/g, U).getRegex();
4270
4531
  var Q = "address|article|aside|base|basefont|blockquote|body|caption|center|col|colgroup|dd|details|dialog|dir|div|dl|dt|fieldset|figcaption|figure|footer|form|frame|frameset|h[1-6]|head|header|hr|html|iframe|legend|li|link|main|menu|menuitem|meta|nav|noframes|ol|optgroup|option|p|param|search|section|summary|table|tbody|td|tfoot|th|thead|title|tr|track|ul";
4271
4532
  var X = /<!--(?:-?>|[\s\S]*?(?:-->|$))/;
4272
- var Me = d("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n*|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>[^\\n]*\\n*|$)|<![A-Z][\\s\\S]*?(?:>[^\\n]*\\n*|$)|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]>[^\\n]*\\n*|$)|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|</(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$))", "i").replace("comment", X).replace("tag", Q).replace("attribute", / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex();
4273
- var le = (l3) => d(K).replace("hr", q).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("|table", "").replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*(?:\\n|$))|~~~)[^\\n]*(?:\\n|$)").replace("list", l3).replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", Q).getRegex();
4533
+ var Ee = k("^ {0,3}(?:<(script|pre|style|textarea)[\\s>][\\s\\S]*?(?:</\\1>[^\\n]*\\n*|$)|comment[^\\n]*(\\n+|$)|<\\?[\\s\\S]*?(?:\\?>[^\\n]*\\n*|$)|<![A-Z][\\s\\S]*?(?:>[^\\n]*\\n*|$)|<!\\[CDATA\\[[\\s\\S]*?(?:\\]\\]>[^\\n]*\\n*|$)|</?(tag)(?: +|\\n|/?>)[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|<(?!script|pre|style|textarea)([a-z][\\w-]*)(?:attribute)*? */?>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$)|</(?!script|pre|style|textarea)[a-z][\\w-]*\\s*>(?=[ \\t]*(?:\\n|$))[\\s\\S]*?(?:(?:\\n[ ]*)+\\n|$))", "i").replace("comment", X).replace("tag", Q).replace("attribute", / +[a-zA-Z:_][\w.:-]*(?: *= *"[^"\n]*"| *= *'[^'\n]*'| *= *[^\s"'=<>`]+)?/).getRegex();
4534
+ var le = (l3) => k(K).replace("hr", q).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("|table", "").replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*(?:\\n|$))|~~~)[^\\n]*(?:\\n|$)").replace("list", l3).replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", Q).getRegex();
4274
4535
  var ze = le(/ {0,3}(?:[*+-]|1[.)])[ \t]+[^ \t\n]/);
4275
- var Ee = le(/ {0,3}(?:[*+-]|\d{1,9}[.)])(?:[ \t]|\n|$)/);
4276
- var Ce = d(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph", Ee).getRegex();
4277
- var J = { blockquote: Ce, code: we, def: $e, fences: ye, heading: Pe, hr: q, html: Me, lheading: ae, list: Le, newline: Oe, paragraph: ze, table: z, text: _e };
4278
- var se = d("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr", q).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("blockquote", " {0,3}>").replace("code", "(?: {4}| {0,3} )[^\\n]").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*(?:\\n|$))|~~~)[^\\n]*(?:\\n|$)").replace("list", " {0,3}(?:[*+-]|1[.)])[ \\t]").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", Q).getRegex();
4279
- var Ae = { ...J, lheading: Se, table: se, paragraph: d(K).replace("hr", q).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("table", se).replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*(?:\\n|$))|~~~)[^\\n]*(?:\\n|$)").replace("list", " {0,3}(?:[*+-]|1[.)])[ \\t]+[^ \\t\\n]").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", Q).getRegex() };
4280
- var Ie = { ...J, html: d(`^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)|<tag(?:"[^"]*"|'[^']*'|\\s[^'"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))`).replace("comment", X).replace(/tag/g, "(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(), def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/, heading: /^(#{1,6})(.*)(?:\n+|$)/, fences: z, lheading: /^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/, paragraph: d(K).replace("hr", q).replace("heading", ` *#{1,6} *[^
4536
+ var Me = le(/ {0,3}(?:[*+-]|\d{1,9}[.)])(?:[ \t]|\n|$)/);
4537
+ var Ae = k(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph", Me).getRegex();
4538
+ var J = { blockquote: Ae, code: we, def: $e, fences: ye, heading: Pe, hr: q, html: Ee, lheading: ae, list: Le, newline: Te, paragraph: ze, table: z, text: _e };
4539
+ var se = k("^ *([^\\n ].*)\\n {0,3}((?:\\| *)?:?-+:? *(?:\\| *:?-+:? *)*(?:\\| *)?)(?:\\n((?:(?! *\\n|hr|heading|blockquote|code|fences|list|html).*(?:\\n|$))*)\\n*|$)").replace("hr", q).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("blockquote", " {0,3}>").replace("code", "(?: {4}| {0,3} )[^\\n]").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*(?:\\n|$))|~~~)[^\\n]*(?:\\n|$)").replace("list", " {0,3}(?:[*+-]|1[.)])[ \\t]").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", Q).getRegex();
4540
+ var Ie = { ...J, lheading: Se, table: se, paragraph: k(K).replace("hr", q).replace("heading", " {0,3}#{1,6}(?:\\s|$)").replace("|lheading", "").replace("table", se).replace("blockquote", " {0,3}>").replace("fences", " {0,3}(?:`{3,}(?=[^`\\n]*(?:\\n|$))|~~~)[^\\n]*(?:\\n|$)").replace("list", " {0,3}(?:[*+-]|1[.)])[ \\t]+[^ \\t\\n]").replace("html", "</?(?:tag)(?: +|\\n|/?>)|<(?:script|pre|style|textarea|!--)").replace("tag", Q).getRegex() };
4541
+ var Ce = { ...J, html: k(`^ *(?:comment *(?:\\n|\\s*$)|<(tag)[\\s\\S]+?</\\1> *(?:\\n{2,}|\\s*$)|<tag(?:"[^"]*"|'[^']*'|\\s[^'"/>\\s]*)*?/?> *(?:\\n{2,}|\\s*$))`).replace("comment", X).replace(/tag/g, "(?!(?:a|em|strong|small|s|cite|q|dfn|abbr|data|time|code|var|samp|kbd|sub|sup|i|b|u|mark|ruby|rt|rp|bdi|bdo|span|br|wbr|ins|del|img)\\b)\\w+(?!:|[^\\w\\s@]*@)\\b").getRegex(), def: /^ *\[([^\]]+)\]: *<?([^\s>]+)>?(?: +(["(][^\n]+[")]))? *(?:\n+|$)/, heading: /^(#{1,6})(.*)(?:\n+|$)/, fences: z, lheading: /^(.+?)\n {0,3}(=+|-+) *(?:\n+|$)/, paragraph: k(K).replace("hr", q).replace("heading", ` *#{1,6} *[^
4281
4542
  ]`).replace("lheading", ae).replace("|table", "").replace("blockquote", " {0,3}>").replace("|fences", "").replace("|list", "").replace("|html", "").replace("|tag", "").getRegex() };
4282
4543
  var Be = /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/;
4283
4544
  var De = /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/;
4284
- var pe = /^( {2,}|\\)\n(?!\s*$)/;
4545
+ var ue = /^( {2,}|\\)\n(?!\s*$)/;
4285
4546
  var qe = /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*_]|\b_|$)|[^ ](?= {2,}\n)))/;
4286
4547
  var _ = /[\p{P}\p{S}]/u;
4287
- var I = /[\s\p{P}\p{S}]/u;
4548
+ var C = /[\s\p{P}\p{S}]/u;
4288
4549
  var v = /[^\s\p{P}\p{S}]/u;
4289
- var ve = d(/^((?![*_])punctSpace)/, "u").replace(/punctSpace/g, I).getRegex();
4550
+ var ve = k(/^((?![*_])punctSpace)/, "u").replace(/punctSpace/g, C).getRegex();
4290
4551
  var He = /[\p{Pi}\p{Ps}"']/u;
4291
- var ue = /(?!~)[\p{P}\p{S}]/u;
4552
+ var pe = /(?!~)[\p{P}\p{S}]/u;
4292
4553
  var Ze = /(?!~)[\s\p{P}\p{S}]/u;
4293
4554
  var Ge = /(?:[^\s\p{P}\p{S}]|~)/u;
4294
- var Qe = d(/link|precode-code|html/, "g").replace("link", /\[(?:[^\[\]`]|(?<a>`+)[^`]+\k<a>(?!`))*?\]\((?:\\[\s\S]|[^\\\(\)]|\((?:\\[\s\S]|[^\\\(\)])*\))*\)/).replace("precode-", Te ? "(?<!`)()" : "(^^|[^`])").replace("code", /(?<b>`+)[^`]+\k<b>(?!`)/).replace("html", /<(?! )[^<>]*?>/).getRegex();
4555
+ var Qe = k(/link|precode-code|html/, "g").replace("link", /\[(?:[^\[\]`]|(?<a>`+)[^`]+\k<a>(?!`))*?\]\((?:\\[\s\S]|[^\\\(\)]|\((?:\\[\s\S]|[^\\\(\)])*\))*\)/).replace("precode-", Oe ? "(?<!`)()" : "(^^|[^`])").replace("code", /(?<b>`+)[^`]+\k<b>(?!`)/).replace("html", /<(?! )[^<>]*?>/).getRegex();
4295
4556
  var ce = /^(?:\*+(?:((?!\*)punct)|([^\s*]))?)|^_+(?:((?!_)punct)|([^\s_]))?/;
4296
- var Ne = d(ce, "u").replace(/punct/g, _).getRegex();
4297
- var je = d(ce, "u").replace(/punct/g, ue).getRegex();
4557
+ var Ne = k(ce, "u").replace(/punct/g, _).getRegex();
4558
+ var je = k(ce, "u").replace(/punct/g, pe).getRegex();
4298
4559
  var Fe = /^(?:\*+(?:((?!\*)(?!openQuote)punct)|([^\s*]))?)|^_+(?:((?!_)(?!openQuote)punct)|([^\s_]))?/;
4299
- var Ue = d(Fe, "u").replace(/openQuote/g, He).replace(/punct/g, _).getRegex();
4560
+ var Ue = k(Fe, "u").replace(/openQuote/g, He).replace(/punct/g, _).getRegex();
4300
4561
  var he = "^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)punct(\\*+)(?=[\\s]|$)|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)|(?!\\*)punctSpace(\\*+)(?=notPunctSpace)|[\\s](\\*+)(?!\\*)(?=punct)|(?!\\*)punct(\\*+)(?!\\*)(?=punct)|notPunctSpace(\\*+)(?=notPunctSpace)";
4301
- var Ke = d(he, "gu").replace(/notPunctSpace/g, v).replace(/punctSpace/g, I).replace(/punct/g, _).getRegex();
4302
- var We = d(he, "gu").replace(/notPunctSpace/g, Ge).replace(/punctSpace/g, Ze).replace(/punct/g, ue).getRegex();
4562
+ var Ke = k(he, "gu").replace(/notPunctSpace/g, v).replace(/punctSpace/g, C).replace(/punct/g, _).getRegex();
4563
+ var We = k(he, "gu").replace(/notPunctSpace/g, Ge).replace(/punctSpace/g, Ze).replace(/punct/g, pe).getRegex();
4303
4564
  var Xe = "^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)punct(\\*+)(?=[\\s]|$)|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)|(?!\\*)[\\s](\\*+)(?=notPunctSpace)|[\\s](\\*+)(?!\\*)(?=punct)|(?!\\*)punct(\\*+)(?!\\*)(?=punct)|(?:(?!\\*)punct|notPunctSpace)(\\*+)(?!\\*)(?=notPunctSpace)";
4304
- var Je = d(Xe, "gu").replace(/notPunctSpace/g, v).replace(/punctSpace/g, I).replace(/punct/g, _).getRegex();
4305
- var Ve = d("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)punct(_+)(?=[\\s]|$)|notPunctSpace(_+)(?!_)(?=punctSpace|$)|(?!_)punctSpace(_+)(?=notPunctSpace)|[\\s](_+)(?!_)(?=punct)|(?!_)punct(_+)(?!_)(?=punct)", "gu").replace(/notPunctSpace/g, v).replace(/punctSpace/g, I).replace(/punct/g, _).getRegex();
4565
+ var Je = k(Xe, "gu").replace(/notPunctSpace/g, v).replace(/punctSpace/g, C).replace(/punct/g, _).getRegex();
4566
+ var Ve = k("^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)punct(_+)(?=[\\s]|$)|notPunctSpace(_+)(?!_)(?=punctSpace|$)|(?!_)punctSpace(_+)(?=notPunctSpace)|[\\s](_+)(?!_)(?=punct)|(?!_)punct(_+)(?!_)(?=punct)", "gu").replace(/notPunctSpace/g, v).replace(/punctSpace/g, C).replace(/punct/g, _).getRegex();
4306
4567
  var Ye = "^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)punct(_+)(?=[\\s]|$)|notPunctSpace(_+)(?!_)(?=punctSpace|$)|(?!_)[\\s](_+)(?=notPunctSpace)|[\\s](_+)(?!_)(?=punct)|(?!_)punct(_+)(?!_)(?=punct)|(?:(?!_)punct|notPunctSpace)(_+)(?!_)(?=notPunctSpace)";
4307
- var et = d(Ye, "gu").replace(/notPunctSpace/g, v).replace(/punctSpace/g, I).replace(/punct/g, _).getRegex();
4308
- var tt = d(/^~~?(?:((?!~)punct)|[^\s~])/, "u").replace(/punct/g, _).getRegex();
4568
+ var et = k(Ye, "gu").replace(/notPunctSpace/g, v).replace(/punctSpace/g, C).replace(/punct/g, _).getRegex();
4569
+ var tt = k(/^~~?(?:((?!~)punct)|[^\s~])/, "u").replace(/punct/g, _).getRegex();
4309
4570
  var nt = "^[^~]+(?=[^~])|(?!~)punct(~~?)(?=[\\s]|$)|notPunctSpace(~~?)(?!~)(?=punctSpace|$)|(?!~)punctSpace(~~?)(?=notPunctSpace)|[\\s](~~?)(?!~)(?=punct)|(?!~)punct(~~?)(?!~)(?=punct)|notPunctSpace(~~?)(?=notPunctSpace)";
4310
- var rt = d(nt, "gu").replace(/notPunctSpace/g, v).replace(/punctSpace/g, I).replace(/punct/g, _).getRegex();
4311
- var st = d(/\\(punct)/, "gu").replace(/punct/g, _).getRegex();
4312
- var it = d(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme", /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email", /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex();
4313
- var ot = d(X).replace("(?:-->|$)", "-->").getRegex();
4314
- var at = d("^comment|^</[a-zA-Z][\\w:-]*\\s*>|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^<![a-zA-Z]+\\s[\\s\\S]*?>|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>").replace("comment", ot).replace("attribute", /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex();
4571
+ var rt = k(nt, "gu").replace(/notPunctSpace/g, v).replace(/punctSpace/g, C).replace(/punct/g, _).getRegex();
4572
+ var st = k(/\\(punct)/, "gu").replace(/punct/g, _).getRegex();
4573
+ var it = k(/^<(scheme:[^\s\x00-\x1f<>]*|email)>/).replace("scheme", /[a-zA-Z][a-zA-Z0-9+.-]{1,31}/).replace("email", /[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+(@)[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)+(?![-_])/).getRegex();
4574
+ var ot = k(X).replace("(?:-->|$)", "-->").getRegex();
4575
+ var at = k("^comment|^</[a-zA-Z][\\w:-]*\\s*>|^<[a-zA-Z][\\w-]*(?:attribute)*?\\s*/?>|^<\\?[\\s\\S]*?\\?>|^<![a-zA-Z]+\\s[\\s\\S]*?>|^<!\\[CDATA\\[[\\s\\S]*?\\]\\]>").replace("comment", ot).replace("attribute", /\s+[a-zA-Z:_][\w.:-]*(?:\s*=\s*"[^"]*"|\s*=\s*'[^']*'|\s*=\s*[^\s"'=<>`]+)?/).getRegex();
4315
4576
  var G = /(?:\[(?:\\[\s\S]|[^\[\]\\])*\]|\\[\s\S]|`+(?!`)[^`]*?`+(?!`)|``+(?=\])|[^\[\]\\`])*?/;
4316
- var lt = d(/^!?\[(label)\]\(\s*(href)(?:(?:[ \t]+(?:\n[ \t]*)?|\n[ \t]*)(title))?\s*\)/).replace("label", G).replace("href", /<(?:\\.|[^\n<>\\])+>|[^ \t\n\x00-\x1f]+|(?=\))/).replace("title", /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex();
4317
- var de = d(/^!?\[(label)\]\[(ref)\]/).replace("label", G).replace("ref", W).getRegex();
4318
- var ke = d(/^!?\[(ref)\](?:\[\])?/).replace("ref", W).getRegex();
4319
- var pt = d("reflink|nolink(?!\\()", "g").replace("reflink", de).replace("nolink", ke).getRegex();
4577
+ var lt = k(/^!?\[(label)\]\(\s*(href)(?:(?:[ \t]+(?:\n[ \t]*)?|\n[ \t]*)(title))?\s*\)/).replace("label", G).replace("href", /<(?:\\.|[^\n<>\\])+>|[^ \t\n\x00-\x1f]+|(?=\))/).replace("title", /"(?:\\"?|[^"\\])*"|'(?:\\'?|[^'\\])*'|\((?:\\\)?|[^)\\])*\)/).getRegex();
4578
+ var ke = k(/^!?\[(label)\]\[(ref)\]/).replace("label", G).replace("ref", W).getRegex();
4579
+ var de = k(/^!?\[(ref)\](?:\[\])?/).replace("ref", W).getRegex();
4580
+ var ut = k("reflink|nolink(?!\\()", "g").replace("reflink", ke).replace("nolink", de).getRegex();
4320
4581
  var ie = /[hH][tT][tT][pP][sS]?|[fF][tT][pP]/;
4321
- var V = { _backpedal: z, anyPunctuation: st, autolink: it, blockSkip: Qe, br: pe, code: De, del: z, delLDelim: z, delRDelim: z, emStrongLDelim: Ne, emStrongRDelimAst: Ke, emStrongRDelimUnd: Ve, escape: Be, link: lt, nolink: ke, punctuation: ve, reflink: de, reflinkSearch: pt, tag: at, text: qe, url: z };
4322
- var ut = { ...V, emStrongLDelim: Ue, emStrongRDelimAst: Je, emStrongRDelimUnd: et, link: d(/^!?\[(label)\]\((.*?)\)/).replace("label", G).getRegex(), reflink: d(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label", G).getRegex() };
4323
- var F = { ...V, emStrongRDelimAst: We, emStrongLDelim: je, delLDelim: tt, delRDelim: rt, url: d(/^((?:protocol):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/).replace("protocol", ie).replace("email", /[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/).getRegex(), _backpedal: /(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/, del: /^(~~?)(?=[^\s~])((?:\\[\s\S]|[^\\])*?(?:\\[\s\S]|[^\s~\\]))\1(?=[^~]|$)/, text: d(/^(`+|~+|[^`~])(?:(?=[`~])|(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\<!\[`*~_]|\b_|protocol:\/\/|www\.|$)|[^ ](?= {2,}\n)|[^a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-](?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)))/).replace("protocol", ie).getRegex() };
4324
- var ct = { ...F, br: d(pe).replace("{2,}", "*").getRegex(), text: d(F.text).replace("\\b_", "\\b_| {2,}\\n").replace(/\{2,\}/g, "*").getRegex() };
4325
- var H = { normal: J, gfm: Ae, pedantic: Ie };
4326
- var B = { normal: V, gfm: F, breaks: ct, pedantic: ut };
4582
+ var V = { _backpedal: z, anyPunctuation: st, autolink: it, blockSkip: Qe, br: ue, code: De, del: z, delLDelim: z, delRDelim: z, emStrongLDelim: Ne, emStrongRDelimAst: Ke, emStrongRDelimUnd: Ve, escape: Be, link: lt, nolink: de, punctuation: ve, reflink: ke, reflinkSearch: ut, tag: at, text: qe, url: z };
4583
+ var pt = { ...V, emStrongLDelim: Ue, emStrongRDelimAst: Je, emStrongRDelimUnd: et, link: k(/^!?\[(label)\]\((.*?)\)/).replace("label", G).getRegex(), reflink: k(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label", G).getRegex() };
4584
+ var F = { ...V, emStrongRDelimAst: We, emStrongLDelim: je, delLDelim: tt, delRDelim: rt, url: k(/^((?:protocol):\/\/|www\.)(?:[a-zA-Z0-9\-]+\.?)+[^\s<]*|^email/).replace("protocol", ie).replace("email", /[A-Za-z0-9._+-]+(@)[a-zA-Z0-9-_]+(?:\.[a-zA-Z0-9-_]*[a-zA-Z0-9])+(?![-_])/).getRegex(), _backpedal: /(?:[^?!.,:;*_'"~()&]+|\([^)]*\)|&(?![a-zA-Z0-9]+;$)|[?!.,:;*_'"~)]+(?!$))+/, del: /^(~~?)(?=[^\s~])((?:\\[\s\S]|[^\\])*?(?:\\[\s\S]|[^\s~\\]))\1(?=[^~]|$)/, text: k(/^(`+|~+|[^`~])(?:(?=[`~])|(?= {2,}\n)|(?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)|[\s\S]*?(?:(?=[\\<!\[`*~_]|\b_|protocol:\/\/|www\.|$)|[^ ](?= {2,}\n)|[^a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-](?=[a-zA-Z0-9.!#$%&'*+\/=?_`{\|}~-]+@)))/).replace("protocol", ie).getRegex() };
4585
+ var ct = { ...F, br: k(ue).replace("{2,}", "*").getRegex(), text: k(F.text).replace("\\b_", "\\b_| {2,}\\n").replace(/\{2,\}/g, "*").getRegex() };
4586
+ var H = { normal: J, gfm: Ie, pedantic: Ce };
4587
+ var B = { normal: V, gfm: F, breaks: ct, pedantic: pt };
4327
4588
  var ht = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" };
4328
4589
  var ge = (l3) => ht[l3];
4329
- function O(l3, e) {
4590
+ function T(l3, e) {
4330
4591
  if (e) {
4331
4592
  if (m.escapeTest.test(l3)) return l3.replace(m.escapeReplace, ge);
4332
4593
  } else if (m.escapeTestNoEncode.test(l3)) return l3.replace(m.escapeReplaceNoEncode, ge);
@@ -4342,9 +4603,9 @@ function Y(l3) {
4342
4603
  }
4343
4604
  function ee(l3, e) {
4344
4605
  let t = l3.replace(m.findPipe, (r, i, o) => {
4345
- let p = false, a = i;
4346
- for (; --a >= 0 && o[a] === "\\"; ) p = !p;
4347
- return p ? "|" : " |";
4606
+ let u = false, a = i;
4607
+ for (; --a >= 0 && o[a] === "\\"; ) u = !u;
4608
+ return u ? "|" : " |";
4348
4609
  }), n = t.split(m.splitPipe), s = 0;
4349
4610
  if (n[0].trim() || n.shift(), n.length > 0 && !n.at(-1)?.trim() && n.pop(), e) if (n.length > e) n.splice(e);
4350
4611
  else for (; n.length < e; ) n.push("");
@@ -4387,12 +4648,21 @@ function me(l3, e = 0) {
4387
4648
  return n;
4388
4649
  }
4389
4650
  function xe(l3, e, t, n, s) {
4390
- let r = e.href, i = e.title || null, o = l3[1].replace(s.other.outputLinkReplace, "$1");
4651
+ let r = e.href, i = e.title || null, o = l3[1].replace(s.other.outputLinkReplace, "$1"), u = l3[0].charAt(0) === "!";
4391
4652
  n.state.inLink = true;
4392
- let p = { type: l3[0].charAt(0) === "!" ? "image" : "link", raw: t, href: r, title: i, text: o, tokens: n.inlineTokens(o) };
4393
- return n.state.inLink = false, p;
4653
+ let a = n.state.linkEmitted, p = n.state.inRawBlock;
4654
+ n.state.linkEmitted = false;
4655
+ let c = n.inlineTokens(o), h = n.state.linkEmitted;
4656
+ if (n.state.linkEmitted = a, n.state.inLink = false, !u) {
4657
+ if (h) {
4658
+ n.state.inRawBlock = p;
4659
+ return;
4660
+ }
4661
+ n.state.linkEmitted = true;
4662
+ }
4663
+ return { type: u ? "image" : "link", raw: t, href: r, title: i, text: o, tokens: c };
4394
4664
  }
4395
- function dt(l3, e, t) {
4665
+ function kt(l3, e, t) {
4396
4666
  let n = l3.match(t.other.indentCodeCompensation);
4397
4667
  if (n === null) return e;
4398
4668
  let s = n[1];
@@ -4426,7 +4696,7 @@ var y = class {
4426
4696
  fences(e) {
4427
4697
  let t = this.rules.block.fences.exec(e);
4428
4698
  if (t) {
4429
- let n = t[0], s = dt(n, t[3] || "", this.rules);
4699
+ let n = t[0], s = kt(n, t[3] || "", this.rules);
4430
4700
  return { type: "code", raw: n, lang: t[2] ? t[2].trim().replace(this.rules.inline.anyPunctuation, "$1") : t[2], text: s };
4431
4701
  }
4432
4702
  }
@@ -4454,33 +4724,33 @@ var y = class {
4454
4724
  `).split(`
4455
4725
  `), s = "", r = "", i = [];
4456
4726
  for (; n.length > 0; ) {
4457
- let o = false, p = [], a;
4458
- for (a = 0; a < n.length; a++) if (this.rules.other.blockquoteStart.test(n[a])) p.push(n[a]), o = true;
4459
- else if (!o) p.push(n[a]);
4727
+ let o = false, u = [], a;
4728
+ for (a = 0; a < n.length; a++) if (this.rules.other.blockquoteStart.test(n[a])) u.push(n[a]), o = true;
4729
+ else if (!o) u.push(n[a]);
4460
4730
  else break;
4461
4731
  n = n.slice(a);
4462
- let u = p.join(`
4463
- `), c = u.replace(this.rules.other.blockquoteSetextReplace, `
4732
+ let p = u.join(`
4733
+ `), c = p.replace(this.rules.other.blockquoteSetextReplace, `
4464
4734
  $1`).replace(this.rules.other.blockquoteSetextReplace2, "");
4465
4735
  s = s ? `${s}
4466
- ${u}` : u, r = r ? `${r}
4736
+ ${p}` : p, r = r ? `${r}
4467
4737
  ${c}` : c;
4468
4738
  let h = this.lexer.state.top;
4469
4739
  if (this.lexer.state.top = true, this.lexer.blockTokens(c, i, true), this.lexer.state.top = h, n.length === 0) break;
4470
- let k = i.at(-1);
4471
- if (k?.type === "code") break;
4472
- if (k?.type === "blockquote") {
4473
- let T = k, g = n.join(`
4474
- `), w = T.raw + `
4475
- ` + g.replace(this.rules.other.blockquoteSetextReplace2, ""), M = this.blockquote(w);
4476
- i[i.length - 1] = M, s = `${s}
4477
- ${g}`, r = r.substring(0, r.length - T.text.length) + M.text;
4740
+ let d = i.at(-1);
4741
+ if (d?.type === "code") break;
4742
+ if (d?.type === "blockquote") {
4743
+ let O = d, g = n.join(`
4744
+ `), w = O.raw + `
4745
+ ` + g.replace(this.rules.other.blockquoteSetextReplace2, ""), E = this.blockquote(w);
4746
+ i[i.length - 1] = E, s = `${s}
4747
+ ${g}`, r = r.substring(0, r.length - O.text.length) + E.text;
4478
4748
  break;
4479
- } else if (k?.type === "list") {
4480
- let T = k, g = T.raw + `
4749
+ } else if (d?.type === "list") {
4750
+ let O = d, g = O.raw + `
4481
4751
  ` + n.join(`
4482
4752
  `), w = this.list(g);
4483
- i[i.length - 1] = w, s = s.substring(0, s.length - k.raw.length) + w.raw, r = r.substring(0, r.length - T.raw.length) + w.raw, n = g.substring(i.at(-1).raw.length).split(`
4753
+ i[i.length - 1] = w, s = s.substring(0, s.length - d.raw.length) + w.raw, r = r.substring(0, r.length - O.raw.length) + w.raw, n = g.substring(i.at(-1).raw.length).split(`
4484
4754
  `);
4485
4755
  continue;
4486
4756
  }
@@ -4495,44 +4765,44 @@ ${g}`, r = r.substring(0, r.length - T.text.length) + M.text;
4495
4765
  n = s ? `\\d{1,9}\\${n.slice(-1)}` : `\\${n}`, this.options.pedantic && (n = s ? n : "[*+-]");
4496
4766
  let i = this.rules.other.listItemRegex(n), o = false;
4497
4767
  for (; e; ) {
4498
- let a = false, u = "", c = "";
4768
+ let a = false, p = "", c = "";
4499
4769
  if (!(t = i.exec(e)) || this.rules.block.hr.test(e)) break;
4500
- u = t[0], e = e.substring(u.length);
4770
+ p = t[0], e = e.substring(p.length);
4501
4771
  let h = me(t[2].split(`
4502
- `, 1)[0], t[1].length), k = e.split(`
4503
- `, 1)[0], T = !h.trim(), g = 0;
4504
- if (this.options.pedantic ? (g = 2, c = h.trimStart()) : T ? g = t[1].length + 1 : (g = h.search(this.rules.other.nonSpaceChar), g = g > 4 ? 1 : g, c = h.slice(g), g += t[1].length), T && this.rules.other.blankLine.test(k) && (u += k + `
4505
- `, e = e.substring(k.length + 1), a = true), !a) {
4506
- let w = this.rules.other.nextBulletRegex(g), M = this.rules.other.hrRegex(g), ne = this.rules.other.fencesBeginRegex(g), re = this.rules.other.headingBeginRegex(g), be = this.rules.other.htmlBeginRegex(g), Re = this.rules.other.blockquoteBeginRegex(g);
4772
+ `, 1)[0], t[1].length), d = e.split(`
4773
+ `, 1)[0], O = !h.trim(), g = 0;
4774
+ if (this.options.pedantic ? (g = 2, c = h.trimStart()) : O ? g = t[1].length + 1 : (g = h.search(this.rules.other.nonSpaceChar), g = g > 4 ? 1 : g, c = h.slice(g), g += t[1].length), O && this.rules.other.blankLine.test(d) && (p += d + `
4775
+ `, e = e.substring(d.length + 1), a = true), !a) {
4776
+ let w = this.rules.other.nextBulletRegex(g), E = this.rules.other.hrRegex(g), ne = this.rules.other.fencesBeginRegex(g), re = this.rules.other.headingBeginRegex(g), be = this.rules.other.htmlBeginRegex(g), Re = this.rules.other.blockquoteBeginRegex(g);
4507
4777
  for (; e; ) {
4508
4778
  let N = e.split(`
4509
4779
  `, 1)[0], D;
4510
- if (k = N, this.options.pedantic ? (k = k.replace(this.rules.other.listReplaceNesting, " "), D = k) : D = k.replace(this.rules.other.tabCharGlobal, " "), ne.test(k) || re.test(k) || be.test(k) || Re.test(k) || w.test(k) || M.test(k)) break;
4511
- if (D.search(this.rules.other.nonSpaceChar) >= g || !k.trim()) c += `
4780
+ if (d = N, this.options.pedantic ? (d = d.replace(this.rules.other.listReplaceNesting, " "), D = d) : D = d.replace(this.rules.other.tabCharGlobal, " "), ne.test(d) || re.test(d) || be.test(d) || Re.test(d) || w.test(d) || E.test(d)) break;
4781
+ if (D.search(this.rules.other.nonSpaceChar) >= g || !d.trim()) c += `
4512
4782
  ` + D.slice(g);
4513
4783
  else {
4514
- if (T || h.replace(this.rules.other.tabCharGlobal, " ").search(this.rules.other.nonSpaceChar) >= 4 || ne.test(h) || re.test(h) || M.test(h)) break;
4784
+ if (O || h.replace(this.rules.other.tabCharGlobal, " ").search(this.rules.other.nonSpaceChar) >= 4 || ne.test(h) || re.test(h) || E.test(h)) break;
4515
4785
  c += `
4516
- ` + k;
4786
+ ` + d;
4517
4787
  }
4518
- T = !k.trim(), u += N + `
4788
+ O = !d.trim(), p += N + `
4519
4789
  `, e = e.substring(N.length + 1), h = D.slice(g);
4520
4790
  }
4521
4791
  }
4522
- r.loose || (o ? r.loose = true : this.rules.other.doubleBlankLine.test(u) && (o = true)), r.items.push({ type: "list_item", raw: u, task: !!this.options.gfm && this.rules.other.listIsTask.test(c), loose: false, text: c, tokens: [] }), r.raw += u;
4792
+ r.loose || (o ? r.loose = true : this.rules.other.doubleBlankLine.test(p) && (o = true)), r.items.push({ type: "list_item", raw: p, task: !!this.options.gfm && this.rules.other.listIsTask.test(c), loose: false, text: c, tokens: [] }), r.raw += p;
4523
4793
  }
4524
- let p = r.items.at(-1);
4525
- if (p) p.raw = p.raw.trimEnd(), p.text = p.text.trimEnd();
4794
+ let u = r.items.at(-1);
4795
+ if (u) u.raw = u.raw.trimEnd(), u.text = u.text.trimEnd();
4526
4796
  else return;
4527
4797
  r.raw = r.raw.trimEnd();
4528
4798
  for (let a of r.items) if (this.lexer.state.top = false, a.tokens = this.lexer.blockTokens(a.text, []), !r.loose) {
4529
- let u = a.tokens.filter((h) => h.type === "space"), c = u.length > 0 && u.some((h) => this.rules.other.anyLine.test(h.raw));
4799
+ let p = a.tokens.filter((h) => h.type === "space"), c = p.length > 0 && p.some((h) => this.rules.other.anyLine.test(h.raw));
4530
4800
  r.loose = c;
4531
4801
  }
4532
4802
  for (let a of r.items) {
4533
- let u = a.tokens[0];
4534
- if (a.task && (u?.type === "text" || u?.type === "paragraph")) {
4535
- a.text = a.text.replace(this.rules.other.listReplaceTask, ""), u.raw = u.raw.replace(this.rules.other.listReplaceTask, ""), u.text = u.text.replace(this.rules.other.listReplaceTask, "");
4803
+ let p = a.tokens[0];
4804
+ if (a.task && (p?.type === "text" || p?.type === "paragraph")) {
4805
+ a.text = a.text.replace(this.rules.other.listReplaceTask, ""), p.raw = p.raw.replace(this.rules.other.listReplaceTask, ""), p.text = p.text.replace(this.rules.other.listReplaceTask, "");
4536
4806
  for (let h = this.lexer.inlineQueue.length - 1; h >= 0; h--) if (this.rules.other.listIsTask.test(this.lexer.inlineQueue[h].src)) {
4537
4807
  this.lexer.inlineQueue[h].src = this.lexer.inlineQueue[h].src.replace(this.rules.other.listReplaceTask, "");
4538
4808
  break;
@@ -4546,7 +4816,7 @@ ${g}`, r = r.substring(0, r.length - T.text.length) + M.text;
4546
4816
  }
4547
4817
  if (r.loose) for (let a of r.items) {
4548
4818
  a.loose = true;
4549
- for (let u of a.tokens) u.type === "text" && (u.type = "paragraph");
4819
+ for (let p of a.tokens) p.type === "text" && (p.type = "paragraph");
4550
4820
  }
4551
4821
  return r;
4552
4822
  }
@@ -4575,7 +4845,7 @@ ${g}`, r = r.substring(0, r.length - T.text.length) + M.text;
4575
4845
  if (n.length === s.length) {
4576
4846
  for (let o of s) this.rules.other.tableAlignRight.test(o) ? i.align.push("right") : this.rules.other.tableAlignCenter.test(o) ? i.align.push("center") : this.rules.other.tableAlignLeft.test(o) ? i.align.push("left") : i.align.push(null);
4577
4847
  for (let o = 0; o < n.length; o++) i.header.push({ text: n[o], tokens: this.lexer.inline(n[o]), header: true, align: i.align[o] });
4578
- for (let o of r) i.rows.push(ee(o, i.header.length).map((p, a) => ({ text: p, tokens: this.lexer.inline(p), header: false, align: i.align[a] })));
4848
+ for (let o of r) i.rows.push(ee(o, i.header.length).map((u, a) => ({ text: u, tokens: this.lexer.inline(u), header: false, align: i.align[a] })));
4579
4849
  return i;
4580
4850
  }
4581
4851
  }
@@ -4619,8 +4889,8 @@ ${g}`, r = r.substring(0, r.length - T.text.length) + M.text;
4619
4889
  let i = fe(t[2], "()");
4620
4890
  if (i === -2) return;
4621
4891
  if (i > -1) {
4622
- let p = (t[0].indexOf("!") === 0 ? 5 : 4) + t[1].length + i;
4623
- t[2] = t[2].substring(0, i), t[0] = t[0].substring(0, p).trim(), t[3] = "";
4892
+ let u = (t[0].indexOf("!") === 0 ? 5 : 4) + t[1].length + i;
4893
+ t[2] = t[2].substring(0, i), t[0] = t[0].substring(0, u).trim(), t[3] = "";
4624
4894
  }
4625
4895
  }
4626
4896
  let s = t[2], r = "";
@@ -4646,25 +4916,25 @@ ${g}`, r = r.substring(0, r.length - T.text.length) + M.text;
4646
4916
  let s = this.rules.inline.emStrongLDelim.exec(e);
4647
4917
  if (!s || !s[1] && !s[2] && !s[3] && !s[4] || s[4] && n.match(this.rules.other.unicodeAlphaNumeric)) return;
4648
4918
  if (!(s[1] || s[3] || "") || !n || this.rules.inline.punctuation.exec(n)) {
4649
- let i = [...s[0]].length - 1, o, p, a = i, u = 0, c = s[0][0], h = n === c, k = c === "*" ? this.rules.inline.emStrongRDelimAst : this.rules.inline.emStrongRDelimUnd;
4650
- for (k.lastIndex = 0, t = t.slice(-1 * e.length + i); (s = k.exec(t)) !== null; ) {
4919
+ let i = [...s[0]].length - 1, o, u, a = i, p = 0, c = s[0][0], h = n === c, d = c === "*" ? this.rules.inline.emStrongRDelimAst : this.rules.inline.emStrongRDelimUnd;
4920
+ for (d.lastIndex = 0, t = t.slice(-1 * e.length + i); (s = d.exec(t)) !== null; ) {
4651
4921
  if (o = s[1] || s[2] || s[3] || s[4] || s[5] || s[6], !o) continue;
4652
- if (p = [...o].length, s[3] || s[4]) {
4653
- a += p;
4922
+ if (u = [...o].length, s[3] || s[4]) {
4923
+ a += u;
4654
4924
  continue;
4655
4925
  } else if (s[5] || s[6]) {
4656
- if (i % 3 && !((i + p) % 3)) {
4657
- u += p;
4926
+ if (i % 3 && !((i + u) % 3)) {
4927
+ p += u;
4658
4928
  continue;
4659
4929
  }
4660
4930
  if (h) break;
4661
4931
  }
4662
- if (a -= p, a > 0) continue;
4663
- p = Math.min(p, p + a + u);
4664
- let T = [...s[0]][0].length, g = e.slice(0, i + s.index + T + p);
4665
- if (Math.min(i, p) % 2) {
4666
- let M = g.slice(1, -1);
4667
- return { type: "em", raw: g, text: M, tokens: this.lexer.inlineTokens(M) };
4932
+ if (a -= u, a > 0) continue;
4933
+ u = Math.min(u, u + a + p);
4934
+ let O = [...s[0]][0].length, g = e.slice(0, i + s.index + O + u);
4935
+ if (Math.min(i, u) % 2) {
4936
+ let E = g.slice(1, -1);
4937
+ return { type: "em", raw: g, text: E, tokens: this.lexer.inlineTokens(E) };
4668
4938
  }
4669
4939
  let w = g.slice(2, -2);
4670
4940
  return { type: "strong", raw: g, text: w, tokens: this.lexer.inlineTokens(w) };
@@ -4686,17 +4956,17 @@ ${g}`, r = r.substring(0, r.length - T.text.length) + M.text;
4686
4956
  let s = this.rules.inline.delLDelim.exec(e);
4687
4957
  if (!s) return;
4688
4958
  if (!(s[1] || "") || !n || this.rules.inline.punctuation.exec(n)) {
4689
- let i = [...s[0]].length - 1, o, p, a = i, u = this.rules.inline.delRDelim;
4690
- for (u.lastIndex = 0, t = t.slice(-1 * e.length + i); (s = u.exec(t)) !== null; ) {
4691
- if (o = s[1] || s[2] || s[3] || s[4] || s[5] || s[6], !o || (p = [...o].length, p !== i)) continue;
4959
+ let i = [...s[0]].length - 1, o, u, a = i, p = this.rules.inline.delRDelim;
4960
+ for (p.lastIndex = 0, t = t.slice(-1 * e.length + i); (s = p.exec(t)) !== null; ) {
4961
+ if (o = s[1] || s[2] || s[3] || s[4] || s[5] || s[6], !o || (u = [...o].length, u !== i)) continue;
4692
4962
  if (s[3] || s[4]) {
4693
- a += p;
4963
+ a += u;
4694
4964
  continue;
4695
4965
  }
4696
- if (a -= p, a > 0) continue;
4697
- p = Math.min(p, p + a);
4698
- let c = [...s[0]][0].length, h = e.slice(0, i + s.index + c + p), k = h.slice(i, -i);
4699
- return { type: "del", raw: h, text: k, tokens: this.lexer.inlineTokens(k) };
4966
+ if (a -= u, a > 0) continue;
4967
+ u = Math.min(u, u + a);
4968
+ let c = [...s[0]][0].length, h = e.slice(0, i + s.index + c + u), d = h.slice(i, -i);
4969
+ return { type: "del", raw: h, text: d, tokens: this.lexer.inlineTokens(d) };
4700
4970
  }
4701
4971
  }
4702
4972
  }
@@ -4737,7 +5007,7 @@ var x = class l {
4737
5007
  inlineQueue;
4738
5008
  tokenizer;
4739
5009
  constructor(e) {
4740
- this.tokens = [], this.tokens.links = /* @__PURE__ */ Object.create(null), this.options = e || R, this.options.tokenizer = this.options.tokenizer || new y(), this.tokenizer = this.options.tokenizer, this.tokenizer.options = this.options, this.tokenizer.lexer = this, this.inlineQueue = [], this.state = { inLink: false, inRawBlock: false, top: true };
5010
+ this.tokens = [], this.tokens.links = /* @__PURE__ */ Object.create(null), this.options = e || R, this.options.tokenizer = this.options.tokenizer || new y(), this.tokenizer = this.options.tokenizer, this.tokenizer.options = this.options, this.tokenizer.lexer = this, this.inlineQueue = [], this.state = { inLink: false, inRawBlock: false, linkEmitted: false, top: true };
4741
5011
  let t = { other: m, block: H.normal, inline: B.normal };
4742
5012
  this.options.pedantic ? (t.block = H.pedantic, t.inline = B.pedantic) : this.options.gfm && (t.block = H.gfm, this.options.breaks ? t.inline = B.breaks : t.inline = B.gfm), this.tokenizer.rules = t;
4743
5013
  }
@@ -4829,9 +5099,9 @@ var x = class l {
4829
5099
  }
4830
5100
  let i = e;
4831
5101
  if (this.options.extensions?.startBlock) {
4832
- let o = 1 / 0, p = e.slice(1), a;
4833
- this.options.extensions.startBlock.forEach((u) => {
4834
- a = u.call({ lexer: this }, p), typeof a == "number" && a >= 0 && (o = Math.min(o, a));
5102
+ let o = 1 / 0, u = e.slice(1), a;
5103
+ this.options.extensions.startBlock.forEach((p) => {
5104
+ a = p.call({ lexer: this }, u), typeof a == "number" && a >= 0 && (o = Math.min(o, a));
4835
5105
  }), o < 1 / 0 && o >= 0 && (i = e.substring(0, o + 1));
4836
5106
  }
4837
5107
  if (this.state.top && (r = this.tokenizer.paragraph(i))) {
@@ -4861,16 +5131,34 @@ var x = class l {
4861
5131
  inline(e, t = []) {
4862
5132
  return this.inlineQueue.push({ src: e, tokens: t }), t;
4863
5133
  }
5134
+ linkInText(e) {
5135
+ if (!e.includes("[")) return false;
5136
+ let t = this.tokenizer.rules.inline.link;
5137
+ for (let n of e.matchAll(this.tokenizer.rules.inline.blockSkip)) if (t.test(n[0]) && e.charAt(n.index - 1) !== "!") return true;
5138
+ for (let n of e.matchAll(this.tokenizer.rules.inline.reflinkSearch)) {
5139
+ let s = n[0], r = s.lastIndexOf("[");
5140
+ if (!(s.charAt(0) === "!" || !Object.hasOwn(this.tokens.links, s.slice(r + 1, -1))) && !(r > 1 && this.linkInText(s.slice(1, r - 1)))) return true;
5141
+ }
5142
+ return false;
5143
+ }
4864
5144
  inlineTokens(e, t = []) {
4865
5145
  this.tokenizer.lexer = this;
4866
5146
  let n = e;
4867
- if (this.tokens.links) {
4868
- let o = Object.keys(this.tokens.links);
4869
- o.length > 0 && (n = n.replace(this.tokenizer.rules.inline.reflinkSearch, (p) => o.includes(p.slice(p.lastIndexOf("[") + 1, -1)) ? "[" + "a".repeat(p.length - 2) + "]" : p));
5147
+ if (this.tokens.links && e.includes("[")) {
5148
+ let o = this.tokenizer.rules.inline.reflinkSearch, u = (a) => {
5149
+ let p = a.lastIndexOf("[");
5150
+ if (!Object.hasOwn(this.tokens.links, a.slice(p + 1, -1))) return a;
5151
+ if (p > 1 && a.charAt(0) !== "!") {
5152
+ let c = a.slice(1, p - 1);
5153
+ if (this.linkInText(c)) return "[" + c.replace(o, u) + "][" + "a".repeat(a.length - p - 2) + "]";
5154
+ }
5155
+ return "[" + "a".repeat(a.length - 2) + "]";
5156
+ };
5157
+ n = n.replace(o, u);
4870
5158
  }
4871
- n = n.replace(this.tokenizer.rules.inline.anyPunctuation, (o) => "+".repeat(o.length)), n = n.replace(this.tokenizer.rules.inline.blockSkip, (o, p, a) => {
4872
- let u = a ? a.length : 0;
4873
- return o.slice(0, u) + "[" + "a".repeat(o.length - u - 2) + "]";
5159
+ n = n.replace(this.tokenizer.rules.inline.anyPunctuation, (o) => "+".repeat(o.length)), n = n.replace(this.tokenizer.rules.inline.blockSkip, (o, u, a) => {
5160
+ let p = a ? a.length : 0;
5161
+ return o.slice(0, p) + "[" + "a".repeat(o.length - p - 2) + "]";
4874
5162
  }), n = this.options.hooks?.emStrongMask?.call({ lexer: this }, n) ?? n;
4875
5163
  let s = false, r = "", i = 1 / 0;
4876
5164
  for (; e; ) {
@@ -4924,14 +5212,14 @@ var x = class l {
4924
5212
  e = e.substring(o.raw.length), t.push(o);
4925
5213
  continue;
4926
5214
  }
4927
- let p = e;
5215
+ let u = e;
4928
5216
  if (this.options.extensions?.startInline) {
4929
- let a = 1 / 0, u = e.slice(1), c;
5217
+ let a = 1 / 0, p = e.slice(1), c;
4930
5218
  this.options.extensions.startInline.forEach((h) => {
4931
- c = h.call({ lexer: this }, u), typeof c == "number" && c >= 0 && (a = Math.min(a, c));
4932
- }), a < 1 / 0 && a >= 0 && (p = e.substring(0, a + 1));
5219
+ c = h.call({ lexer: this }, p), typeof c == "number" && c >= 0 && (a = Math.min(a, c));
5220
+ }), a < 1 / 0 && a >= 0 && (u = e.substring(0, a + 1));
4933
5221
  }
4934
- if (o = this.tokenizer.inlineText(p)) {
5222
+ if (o = this.tokenizer.inlineText(u)) {
4935
5223
  e = e.substring(o.raw.length), o.raw.slice(-1) !== "_" && (r = o.raw.slice(-1)), s = true;
4936
5224
  let a = t.at(-1);
4937
5225
  a?.type === "text" ? (a.raw += o.raw, a.text += o.text) : t.push(o);
@@ -4962,8 +5250,8 @@ var P = class {
4962
5250
  code({ text: e, lang: t, escaped: n }) {
4963
5251
  let s = (t || "").match(m.notSpaceStart)?.[0], r = e.replace(m.endingNewline, "") + `
4964
5252
  `;
4965
- return s ? '<pre><code class="language-' + O(s) + '">' + (n ? r : O(r, true)) + `</code></pre>
4966
- ` : "<pre><code>" + (n ? r : O(r, true)) + `</code></pre>
5253
+ return s ? '<pre><code class="language-' + T(s) + '">' + (n ? r : T(r, true)) + `</code></pre>
5254
+ ` : "<pre><code>" + (n ? r : T(r, true)) + `</code></pre>
4967
5255
  `;
4968
5256
  }
4969
5257
  blockquote({ tokens: e }) {
@@ -4988,8 +5276,8 @@ ${this.parser.parse(e)}</blockquote>
4988
5276
  list(e) {
4989
5277
  let t = e.ordered, n = e.start, s = "";
4990
5278
  for (let o = 0; o < e.items.length; o++) {
4991
- let p = e.items[o];
4992
- s += this.listitem(p);
5279
+ let u = e.items[o];
5280
+ s += this.listitem(u);
4993
5281
  }
4994
5282
  let r = t ? "ol" : "ul", i = t && n !== 1 ? ' start="' + n + '"' : "";
4995
5283
  return "<" + r + i + `>
@@ -5041,7 +5329,7 @@ ${e}</tr>
5041
5329
  return `<em>${this.parser.parseInline(e)}</em>`;
5042
5330
  }
5043
5331
  codespan({ text: e }) {
5044
- return `<code>${O(e, true)}</code>`;
5332
+ return `<code>${T(e, true)}</code>`;
5045
5333
  }
5046
5334
  br(e) {
5047
5335
  return "<br>";
@@ -5054,18 +5342,18 @@ ${e}</tr>
5054
5342
  if (r === null) return s;
5055
5343
  e = r;
5056
5344
  let i = '<a href="' + e + '"';
5057
- return t && (i += ' title="' + O(t) + '"'), i += ">" + s + "</a>", i;
5345
+ return t && (i += ' title="' + T(t) + '"'), i += ">" + s + "</a>", i;
5058
5346
  }
5059
5347
  image({ href: e, title: t, text: n, tokens: s }) {
5060
5348
  s && (n = this.parser.parseInline(s, this.parser.textRenderer));
5061
5349
  let r = Y(e);
5062
- if (r === null) return O(n);
5350
+ if (r === null) return T(n);
5063
5351
  e = r;
5064
- let i = `<img src="${e}" alt="${O(n)}"`;
5065
- return t && (i += ` title="${O(t)}"`), i += ">", i;
5352
+ let i = `<img src="${e}" alt="${T(n)}"`;
5353
+ return t && (i += ` title="${T(t)}"`), i += ">", i;
5066
5354
  }
5067
5355
  text(e) {
5068
- return "tokens" in e && e.tokens ? this.parser.parseInline(e.tokens) : "escaped" in e && e.escaped ? e.text : O(e.text);
5356
+ return "tokens" in e && e.tokens ? this.parser.parseInline(e.tokens) : "escaped" in e && e.escaped ? e.text : T(e.text);
5069
5357
  }
5070
5358
  };
5071
5359
  var L = class {
@@ -5280,7 +5568,7 @@ var S = class {
5280
5568
  }
5281
5569
  };
5282
5570
  var Z = class {
5283
- defaults = C();
5571
+ defaults = A();
5284
5572
  options = this.setOptions;
5285
5573
  parse = this.parseMarkdown(true);
5286
5574
  parseInline = this.parseMarkdown(false);
@@ -5326,8 +5614,8 @@ var Z = class {
5326
5614
  if ("renderer" in r) {
5327
5615
  let i = t.renderers[r.name];
5328
5616
  i ? t.renderers[r.name] = function(...o) {
5329
- let p = r.renderer.apply(this, o);
5330
- return p === false && (p = i.apply(this, o)), p;
5617
+ let u = r.renderer.apply(this, o);
5618
+ return u === false && (u = i.apply(this, o)), u;
5331
5619
  } : t.renderers[r.name] = r.renderer;
5332
5620
  }
5333
5621
  if ("tokenizer" in r) {
@@ -5341,10 +5629,10 @@ var Z = class {
5341
5629
  for (let i in n.renderer) {
5342
5630
  if (!(i in r)) throw new Error(`renderer '${i}' does not exist`);
5343
5631
  if (["options", "parser"].includes(i)) continue;
5344
- let o = i, p = n.renderer[o], a = r[o];
5345
- r[o] = (...u) => {
5346
- let c = p.apply(r, u);
5347
- return c === false && (c = a.apply(r, u)), c || "";
5632
+ let o = i, u = n.renderer[o], a = r[o];
5633
+ r[o] = (...p) => {
5634
+ let c = u.apply(r, p);
5635
+ return c === false && (c = a.apply(r, p)), c || "";
5348
5636
  };
5349
5637
  }
5350
5638
  s.renderer = r;
@@ -5354,10 +5642,10 @@ var Z = class {
5354
5642
  for (let i in n.tokenizer) {
5355
5643
  if (!(i in r)) throw new Error(`tokenizer '${i}' does not exist`);
5356
5644
  if (["options", "rules", "lexer"].includes(i)) continue;
5357
- let o = i, p = n.tokenizer[o], a = r[o];
5358
- r[o] = (...u) => {
5359
- let c = p.apply(r, u);
5360
- return c === false && (c = a.apply(r, u)), c;
5645
+ let o = i, u = n.tokenizer[o], a = r[o];
5646
+ r[o] = (...p) => {
5647
+ let c = u.apply(r, p);
5648
+ return c === false && (c = a.apply(r, p)), c;
5361
5649
  };
5362
5650
  }
5363
5651
  s.tokenizer = r;
@@ -5367,21 +5655,21 @@ var Z = class {
5367
5655
  for (let i in n.hooks) {
5368
5656
  if (!(i in r)) throw new Error(`hook '${i}' does not exist`);
5369
5657
  if (["options", "block"].includes(i)) continue;
5370
- let o = i, p = n.hooks[o], a = r[o];
5371
- S.passThroughHooks.has(i) ? r[o] = (u) => {
5658
+ let o = i, u = n.hooks[o], a = r[o];
5659
+ S.passThroughHooks.has(i) ? r[o] = (p) => {
5372
5660
  if (this.defaults.async && S.passThroughHooksRespectAsync.has(i)) return (async () => {
5373
- let h = await p.call(r, u);
5661
+ let h = await u.call(r, p);
5374
5662
  return a.call(r, h);
5375
5663
  })();
5376
- let c = p.call(r, u);
5664
+ let c = u.call(r, p);
5377
5665
  return a.call(r, c);
5378
- } : r[o] = (...u) => {
5666
+ } : r[o] = (...p) => {
5379
5667
  if (this.defaults.async) return (async () => {
5380
- let h = await p.apply(r, u);
5381
- return h === false && (h = await a.apply(r, u)), h;
5668
+ let h = await u.apply(r, p);
5669
+ return h === false && (h = await a.apply(r, p)), h;
5382
5670
  })();
5383
- let c = p.apply(r, u);
5384
- return c === false && (c = a.apply(r, u)), c;
5671
+ let c = u.apply(r, p);
5672
+ return c === false && (c = a.apply(r, p)), c;
5385
5673
  };
5386
5674
  }
5387
5675
  s.hooks = r;
@@ -5389,8 +5677,8 @@ var Z = class {
5389
5677
  if (n.walkTokens) {
5390
5678
  let r = this.defaults.walkTokens, i = n.walkTokens;
5391
5679
  s.walkTokens = function(o) {
5392
- let p = [];
5393
- return p.push(i.call(this, o)), r && (p = p.concat(r.call(this, o))), p;
5680
+ let u = [];
5681
+ return u.push(i.call(this, o)), r && (u = u.concat(r.call(this, o))), u;
5394
5682
  };
5395
5683
  }
5396
5684
  this.defaults = { ...this.defaults, ...s };
@@ -5412,10 +5700,10 @@ var Z = class {
5412
5700
  if (typeof n > "u" || n === null) return o(new Error("marked(): input parameter is undefined or null"));
5413
5701
  if (typeof n != "string") return o(new Error("marked(): input parameter is of type " + Object.prototype.toString.call(n) + ", string expected"));
5414
5702
  if (i.hooks && (i.hooks.options = i, i.hooks.block = e), i.async) return (async () => {
5415
- let p = i.hooks ? await i.hooks.preprocess(n) : n, u = await (i.hooks ? await i.hooks.provideLexer(e) : e ? x.lex : x.lexInline)(p, i), c = i.hooks ? await i.hooks.processAllTokens(u) : u;
5703
+ let u = i.hooks ? await i.hooks.preprocess(n) : n, p = await (i.hooks ? await i.hooks.provideLexer(e) : e ? x.lex : x.lexInline)(u, i), c = i.hooks ? await i.hooks.processAllTokens(p) : p;
5416
5704
  i.walkTokens && await Promise.all(this.walkTokens(c, i.walkTokens));
5417
- let k = await (i.hooks ? await i.hooks.provideParser(e) : e ? b.parse : b.parseInline)(c, i);
5418
- return i.hooks ? await i.hooks.postprocess(k) : k;
5705
+ let d = await (i.hooks ? await i.hooks.provideParser(e) : e ? b.parse : b.parseInline)(c, i);
5706
+ return i.hooks ? await i.hooks.postprocess(d) : d;
5419
5707
  })().catch(o);
5420
5708
  try {
5421
5709
  i.hooks && (n = i.hooks.preprocess(n));
@@ -5423,8 +5711,8 @@ var Z = class {
5423
5711
  i.hooks && (a = i.hooks.processAllTokens(a)), i.walkTokens && this.walkTokens(a, i.walkTokens);
5424
5712
  let c = (i.hooks ? i.hooks.provideParser(e) : e ? b.parse : b.parseInline)(a, i);
5425
5713
  return i.hooks && (c = i.hooks.postprocess(c)), c;
5426
- } catch (p) {
5427
- return o(p);
5714
+ } catch (u) {
5715
+ return o(u);
5428
5716
  }
5429
5717
  };
5430
5718
  }
@@ -5432,7 +5720,7 @@ var Z = class {
5432
5720
  return (n) => {
5433
5721
  if (n.message += `
5434
5722
  Please report this to https://github.com/markedjs/marked.`, e) {
5435
- let s = "<p>An error occurred:</p><pre>" + O(n.message + "", true) + "</pre>";
5723
+ let s = "<p>An error occurred:</p><pre>" + T(n.message + "", true) + "</pre>";
5436
5724
  return t ? Promise.resolve(s) : s;
5437
5725
  }
5438
5726
  if (t) return Promise.reject(n);
@@ -5440,23 +5728,23 @@ Please report this to https://github.com/markedjs/marked.`, e) {
5440
5728
  };
5441
5729
  }
5442
5730
  };
5443
- var E = new Z();
5731
+ var M = new Z();
5444
5732
  function f(l3, e) {
5445
- return E.parse(l3, e);
5733
+ return M.parse(l3, e);
5446
5734
  }
5447
5735
  f.options = f.setOptions = function(l3) {
5448
- return E.setOptions(l3), f.defaults = E.defaults, j(f.defaults), f;
5736
+ return M.setOptions(l3), f.defaults = M.defaults, j(f.defaults), f;
5449
5737
  };
5450
- f.getDefaults = C;
5738
+ f.getDefaults = A;
5451
5739
  f.defaults = R;
5452
- function kt(...l3) {
5453
- return E.use(...l3), f.defaults = E.defaults, j(f.defaults), f;
5740
+ function dt(...l3) {
5741
+ return M.use(...l3), f.defaults = M.defaults, j(f.defaults), f;
5454
5742
  }
5455
- f.use = kt;
5743
+ f.use = dt;
5456
5744
  f.walkTokens = function(l3, e) {
5457
- return E.walkTokens(l3, e);
5745
+ return M.walkTokens(l3, e);
5458
5746
  };
5459
- f.parseInline = E.parseInline;
5747
+ f.parseInline = M.parseInline;
5460
5748
  f.Parser = b;
5461
5749
  f.parser = b.parse;
5462
5750
  f.Renderer = P;
@@ -5471,7 +5759,7 @@ var rn = f.setOptions;
5471
5759
  var sn = f.walkTokens;
5472
5760
  var on = f.parseInline;
5473
5761
  var ln = b.parse;
5474
- var pn = x.lex;
5762
+ var un = x.lex;
5475
5763
 
5476
5764
  // src/ui/render_markdown.ts
5477
5765
  var parser = new Z({ gfm: true, breaks: true });
@@ -5682,7 +5970,7 @@ function wrapWords(root) {
5682
5970
  }
5683
5971
 
5684
5972
  // src/ui/run_notice.ts
5685
- function renderRunNotice(icon, text4, kind) {
5973
+ function renderRunNotice(icon, text4, kind, undo) {
5686
5974
  const notice = document.createElement("div");
5687
5975
  notice.className = `run-notice run-notice--${kind}`;
5688
5976
  notice.setAttribute("part", `run-notice run-notice-${kind}`);
@@ -5697,6 +5985,18 @@ function renderRunNotice(icon, text4, kind) {
5697
5985
  label.setAttribute("part", "run-notice-text");
5698
5986
  label.textContent = text4;
5699
5987
  notice.append(glyph, label);
5988
+ if (undo !== void 0) {
5989
+ const button2 = document.createElement("button");
5990
+ button2.type = "button";
5991
+ button2.className = "run-notice-undo";
5992
+ button2.setAttribute("part", "run-notice-undo");
5993
+ button2.textContent = undo.label;
5994
+ button2.addEventListener("click", () => {
5995
+ button2.disabled = true;
5996
+ undo.onActivate();
5997
+ });
5998
+ notice.append(button2);
5999
+ }
5700
6000
  return notice;
5701
6001
  }
5702
6002
 
@@ -5954,6 +6254,11 @@ var STYLES = `
5954
6254
  /* Message action row: the control box and the mark inside it. The box has a
5955
6255
  floor of 24px so it stays a reliable target at every density. */
5956
6256
  --_action-size: var(--ag-ui-action-size, 28px);
6257
+ /* The header's own controls. Sized rather than left to their contents: a row
6258
+ of buttons each as wide as the glyph inside it comes out uneven, and uneven
6259
+ buttons two pixels apart read as one smudge rather than five controls. */
6260
+ --_header-btn-size: var(--ag-ui-header-btn-size, 30px);
6261
+ --_header-gap: var(--ag-ui-header-gap, 4px);
5957
6262
  --_action-icon-size: var(--ag-ui-action-icon-size, 15px);
5958
6263
  --_tooltip-bg: var(--ag-ui-tooltip-bg, #1f2430);
5959
6264
  --_tooltip-fg: var(--ag-ui-tooltip-fg, #f5f6fa);
@@ -6048,6 +6353,68 @@ var STYLES = `
6048
6353
  --_msg-pad: var(--ag-ui-msg-pad, 8px 12px);
6049
6354
  --_msg-radius: var(--ag-ui-msg-radius, 14px);
6050
6355
 
6356
+ /* Edges of the viewport the host has already spent, and that a fixed
6357
+ placement must therefore stay out of: a sticky nav bar, a docked toolbar,
6358
+ a device's safe area. Four longhands rather than one shorthand because a
6359
+ custom property is a token stream and CSS cannot index one -- the height
6360
+ arithmetic below needs the vertical pair on its own.
6361
+
6362
+ Every fixed placement derives from these, and the heights subtract them in
6363
+ one place. A host that reserved its chrome by restating --ag-ui-inset per
6364
+ placement had to keep --ag-ui-height in step by hand, and forgetting it
6365
+ overflowed the panel past the bottom of the screen with nothing to say so.
6366
+
6367
+ These take env(safe-area-inset-*) verbatim, which is what a full-bleed
6368
+ placement wants on a device with a notch. */
6369
+ --_viewport-inset-top: var(--ag-ui-viewport-inset-top, 0px);
6370
+ --_viewport-inset-right: var(--ag-ui-viewport-inset-right, 0px);
6371
+ --_viewport-inset-bottom: var(--ag-ui-viewport-inset-bottom, 0px);
6372
+ --_viewport-inset-left: var(--ag-ui-viewport-inset-left, 0px);
6373
+ /* What is left of the viewport once the host's chrome is taken out, on both
6374
+ axes, so a placement never composes this itself and no host can subtract
6375
+ an edge from the position and forget it in the height.
6376
+
6377
+ Settable in their own right, and that is not only convention: an on-screen
6378
+ keyboard changes no viewport-percentage length -- not vh, not dvh, not svh
6379
+ -- so a full-bleed panel on a phone has to be told the height rather than
6380
+ deriving it. The value to publish there is the visual viewport's. */
6381
+ --_viewport-height: var(--ag-ui-viewport-height, var(--_visual-viewport-height));
6382
+ /* The measured height of the part of the screen the user can actually see,
6383
+ written by the element from the visual viewport and falling back to the
6384
+ layout viewport where nothing has measured yet.
6385
+
6386
+ This is the on-screen keyboard, and it needs measuring because no CSS
6387
+ length describes it: an on-screen keyboard has no effect on any
6388
+ viewport-percentage unit, so 100vh, 100dvh and 100svh are all the same
6389
+ number with the keyboard up as without it. A full-bleed panel sized from
6390
+ any of them puts its composer behind the keyboard the user is typing into.
6391
+
6392
+ Separate from the token above so a host that states the usable height
6393
+ outright still wins: the element writes this one inline, and an inline
6394
+ value would otherwise outrank the host's own rule. */
6395
+ --_visual-viewport-height: var(
6396
+ --ag-ui-visual-viewport-height,
6397
+ calc(100vh - var(--_viewport-inset-top) - var(--_viewport-inset-bottom))
6398
+ );
6399
+ /* How much of the layout viewport is hidden below the visible one, measured
6400
+ and written by the element alongside the height above.
6401
+
6402
+ A shorter panel is not enough on its own for anything anchored to the
6403
+ bottom. A floating widget is positioned against the layout viewport, so
6404
+ with a keyboard up its bottom edge -- and the launcher that lives at that
6405
+ corner -- sits behind the keyboard however tall the panel is. This is what
6406
+ lifts it clear. */
6407
+ /* Two tokens, for the same reason the height above has two: the element
6408
+ writes the measurement inline, and a host that states its own value needs
6409
+ a knob that outranks that write rather than one the next write replaces.
6410
+ A host wanting no keyboard lift at all sets --ag-ui-keyboard-inset: 0px. */
6411
+ --_keyboard-inset: var(--ag-ui-keyboard-inset, var(--_visual-viewport-inset-bottom));
6412
+ --_visual-viewport-inset-bottom: var(--ag-ui-visual-viewport-inset-bottom, 0px);
6413
+ --_viewport-width: var(
6414
+ --ag-ui-viewport-width,
6415
+ calc(100vw - var(--_viewport-inset-left) - var(--_viewport-inset-right))
6416
+ );
6417
+
6051
6418
  /* Layout \u2014 override from outside to dock the widget anywhere.
6052
6419
  Set --ag-ui-position: static (and place this element in your own
6053
6420
  grid/flex layout) to embed it in the page flow instead of floating. */
@@ -6055,14 +6422,49 @@ var STYLES = `
6055
6422
  --_z-index: var(--ag-ui-z-index, 2147483000);
6056
6423
  --_width: var(--ag-ui-width, 380px);
6057
6424
  --_height: var(--ag-ui-height, 560px);
6058
- --_inset: var(--ag-ui-inset, auto 24px 24px auto);
6059
- --_max-width: var(--ag-ui-max-width, calc(100vw - 48px));
6060
- --_max-height: var(--ag-ui-max-height, calc(100vh - 48px));
6425
+ /* The gutter a resting floating panel keeps between itself and the edge of
6426
+ the box the host left free. One number, because the inset below spends it
6427
+ and the cap beneath has to know it was spent. */
6428
+ --_edge-gutter: var(--ag-ui-edge-gutter, 24px);
6429
+ --_inset: var(
6430
+ --ag-ui-inset,
6431
+ auto calc(var(--_edge-gutter) + var(--_viewport-inset-right))
6432
+ calc(var(--_edge-gutter) + var(--_viewport-inset-bottom) + var(--_keyboard-inset)) auto
6433
+ );
6434
+ /* The cap is what the host left free, less the *one* gutter the inset above
6435
+ spends on the anchored edge -- not two, and not none.
6436
+
6437
+ Two was the first answer and it was felt on one axis only. The arithmetic
6438
+ says why: the default panel is 560 tall against a cap of the viewport
6439
+ minus 48, which on an 800px screen with a header reserved is 72px of
6440
+ headroom, so the height reaches its cap almost immediately; the default
6441
+ width is 380 against a cap near 1230, which is 850px nothing ever reaches.
6442
+ Once the size is capped a grip cannot grow the panel, so a pull on the
6443
+ anchored edge is written as position instead and the panel travels, and a
6444
+ pull on the free edge stops a whole gutter short of an edge a drag can
6445
+ reach.
6446
+
6447
+ None was the correction, and it overshot: the panel is anchored bottom-
6448
+ right with a gutter already spent there, so a cap of the full usable
6449
+ height puts the far edge exactly one gutter outside it. On an 800px screen
6450
+ with nothing reserved that is a top of -24 -- the header, and every
6451
+ control in it, off the top of the window. Reserve a 120px header and it
6452
+ sits inside that instead, which is the one thing the reservation exists to
6453
+ prevent.
6454
+
6455
+ One is the fixed point of both complaints. A resting panel grown to the
6456
+ cap runs from the usable near edge to its gutter on the far one, so the
6457
+ near edge is reachable by a resize exactly as it is by a drag, and neither
6458
+ can put any part of the panel outside the box. */
6459
+ --_max-width: var(--ag-ui-max-width, calc(var(--_viewport-width) - var(--_edge-gutter)));
6460
+ --_max-height: var(--ag-ui-max-height, calc(var(--_viewport-height) - var(--_edge-gutter)));
6061
6461
  /* Reading-column width for placement="page" (full-bleed, centred content). */
6062
6462
  --_content-max-width: var(--ag-ui-content-max-width, 820px);
6063
6463
  /* Slim rail the sidebar placement collapses to. Only that placement reads
6064
6464
  it, but it is declared here so every alias has a default in one place. */
6065
6465
  --_rail-width: var(--ag-ui-rail-width, 52px);
6466
+ /* Width of the docked conversation list on a full-page chat. */
6467
+ --_threads-rail-width: var(--ag-ui-threads-rail-width, 280px);
6066
6468
 
6067
6469
  position: var(--_position);
6068
6470
  inset: var(--_inset);
@@ -6133,23 +6535,30 @@ var STYLES = `
6133
6535
 
6134
6536
  /* \u2500\u2500 Placement presets \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500 */
6135
6537
  :host([placement="bottom-left"]) {
6136
- --_inset: var(--ag-ui-inset, auto auto 24px 24px);
6538
+ --_inset: var(
6539
+ --ag-ui-inset,
6540
+ auto auto calc(var(--_edge-gutter) + var(--_viewport-inset-bottom) + var(--_keyboard-inset))
6541
+ calc(var(--_edge-gutter) + var(--_viewport-inset-left))
6542
+ );
6137
6543
  }
6138
6544
 
6139
6545
  :host([placement="side"]) {
6140
- --_inset: var(--ag-ui-inset, 0 0 0 auto);
6546
+ --_inset: var(--ag-ui-inset, var(--_viewport-inset-top) var(--_viewport-inset-right) var(--_viewport-inset-bottom) auto);
6141
6547
  --_width: var(--ag-ui-width, 420px);
6142
- --_height: var(--ag-ui-height, 100vh);
6143
- --_max-height: var(--ag-ui-max-height, 100vh);
6548
+ --_height: var(--ag-ui-height, var(--_viewport-height));
6549
+ --_max-height: var(--ag-ui-max-height, var(--_viewport-height));
6144
6550
  --_radius: var(--ag-ui-radius, 0);
6145
6551
  }
6146
6552
 
6147
6553
  :host([placement="full"]) {
6148
- --_inset: var(--ag-ui-inset, 0);
6149
- --_width: var(--ag-ui-width, 100vw);
6150
- --_height: var(--ag-ui-height, 100vh);
6151
- --_max-width: var(--ag-ui-max-width, 100vw);
6152
- --_max-height: var(--ag-ui-max-height, 100vh);
6554
+ --_inset: var(
6555
+ --ag-ui-inset,
6556
+ var(--_viewport-inset-top) var(--_viewport-inset-right) var(--_viewport-inset-bottom) var(--_viewport-inset-left)
6557
+ );
6558
+ --_width: var(--ag-ui-width, var(--_viewport-width));
6559
+ --_height: var(--ag-ui-height, var(--_viewport-height));
6560
+ --_max-width: var(--ag-ui-max-width, var(--_viewport-width));
6561
+ --_max-height: var(--ag-ui-max-height, var(--_viewport-height));
6153
6562
  --_radius: var(--ag-ui-radius, 0);
6154
6563
  }
6155
6564
 
@@ -6159,11 +6568,14 @@ var STYLES = `
6159
6568
  composer rather than a per-row wrapper, so user pills still right-align and
6160
6569
  the assistant well spans the column. */
6161
6570
  :host([placement="page"]) {
6162
- --_inset: var(--ag-ui-inset, 0);
6163
- --_width: var(--ag-ui-width, 100vw);
6164
- --_height: var(--ag-ui-height, 100vh);
6165
- --_max-width: var(--ag-ui-max-width, 100vw);
6166
- --_max-height: var(--ag-ui-max-height, 100vh);
6571
+ --_inset: var(
6572
+ --ag-ui-inset,
6573
+ var(--_viewport-inset-top) var(--_viewport-inset-right) var(--_viewport-inset-bottom) var(--_viewport-inset-left)
6574
+ );
6575
+ --_width: var(--ag-ui-width, var(--_viewport-width));
6576
+ --_height: var(--ag-ui-height, var(--_viewport-height));
6577
+ --_max-width: var(--ag-ui-max-width, var(--_viewport-width));
6578
+ --_max-height: var(--ag-ui-max-height, var(--_viewport-height));
6167
6579
  --_radius: var(--ag-ui-radius, 0);
6168
6580
  }
6169
6581
 
@@ -6194,28 +6606,100 @@ var STYLES = `
6194
6606
  max-width: 100%;
6195
6607
  }
6196
6608
 
6609
+ /* Small viewports: one shape, reached from whichever placement the host chose.
6610
+
6611
+ A phone is not an eighth placement, it is an override that collapses the
6612
+ others onto one of them. The host picked a placement for the desktop it was
6613
+ designing; a 380x560 panel with a 24px margin is not a smaller version of
6614
+ that decision, it is most of the screen with a frame drawn round it.
6615
+
6616
+ The breakpoint is a literal because a custom property cannot be read in a
6617
+ media query -- which is exactly why there is an opt-out. Everything the block
6618
+ sets is a token a host can re-state, but the *trigger* is unreachable, so a
6619
+ host whose layout wants a different threshold, or none, sets
6620
+ data-small-viewport="off" and keeps its desktop shape at every width. 600px is above every common phone in portrait and below every
6621
+ tablet in landscape, and it is a width rather than a pointer test on purpose:
6622
+ a touch laptop is coarse-pointered and wide, a narrow desktop window is
6623
+ fine-pointered and small, and conflating the two gets both wrong. What the
6624
+ pointer decides is which controls make sense, further down.
6625
+
6626
+ Two placements are left alone. "page" is already this shape. "embedded" sits
6627
+ in a box the host sized and placed, and taking that over would break the app
6628
+ shell it was embedded into -- the host is the only party that knows whether
6629
+ its column should become the whole screen. */
6630
+ @media (max-width: 600px) {
6631
+ :host([placement="floating"]:not([data-small-viewport="off"])),
6632
+ :host([placement="bottom-left"]:not([data-small-viewport="off"])),
6633
+ :host([placement="sidebar"]:not([data-small-viewport="off"])),
6634
+ :host([placement="side"]:not([data-small-viewport="off"])),
6635
+ :host(:not([placement]):not([data-small-viewport="off"])),
6636
+ :host([placement=""]:not([data-small-viewport="off"])) {
6637
+ --_inset: var(
6638
+ --ag-ui-inset,
6639
+ var(--_viewport-inset-top) var(--_viewport-inset-right)
6640
+ calc(var(--_viewport-inset-bottom) + var(--_keyboard-inset))
6641
+ var(--_viewport-inset-left)
6642
+ );
6643
+ --_width: var(--ag-ui-width, var(--_viewport-width));
6644
+ --_height: var(--ag-ui-height, var(--_viewport-height));
6645
+ --_max-width: var(--ag-ui-max-width, var(--_viewport-width));
6646
+ --_max-height: var(--ag-ui-max-height, var(--_viewport-height));
6647
+ --_radius: var(--ag-ui-radius, 0);
6648
+ --_shadow: var(--ag-ui-shadow, none);
6649
+ }
6650
+
6651
+ /* Nothing to resize once the panel is the screen, and the grips would sit
6652
+ under the thumbs holding the phone. */
6653
+ :host([placement="floating"]:not([data-small-viewport="off"])) .resize-handle,
6654
+ :host([placement="bottom-left"]:not([data-small-viewport="off"])) .resize-handle,
6655
+ :host([placement="sidebar"]:not([data-small-viewport="off"])) .resize-handle,
6656
+ :host([placement="side"]:not([data-small-viewport="off"])) .resize-handle,
6657
+ :host(:not([placement]):not([data-small-viewport="off"])) .resize-handle,
6658
+ :host([placement=""]:not([data-small-viewport="off"])) .resize-handle {
6659
+ display: none;
6660
+ }
6661
+ }
6662
+
6197
6663
  /* Sidebar: a full-height docked panel that slides open/closed and
6198
6664
  collapses to a slim icon rail (not the floating launcher). Docked right by
6199
6665
  default; data-side="left" docks it left. Overlay by default \u2014 set
6200
6666
  --ag-ui-position: static (and place this element in your own layout) for a
6201
6667
  host-managed push instead. */
6202
6668
  :host([placement="sidebar"]) {
6203
- --_inset: var(--ag-ui-inset, 0 0 0 auto);
6669
+ --_inset: var(--ag-ui-inset, var(--_viewport-inset-top) var(--_viewport-inset-right) var(--_viewport-inset-bottom) auto);
6204
6670
  --_width: var(--ag-ui-width, 420px);
6205
- --_height: var(--ag-ui-height, 100vh);
6206
- --_max-height: var(--ag-ui-max-height, 100vh);
6671
+ --_height: var(--ag-ui-height, var(--_viewport-height));
6672
+ --_max-height: var(--ag-ui-max-height, var(--_viewport-height));
6207
6673
  --_radius: var(--ag-ui-radius, 0);
6208
6674
  transition: width var(--_motion) var(--_ease);
6209
6675
  }
6210
6676
 
6211
6677
  :host([placement="sidebar"][data-side="left"]) {
6212
- --_inset: var(--ag-ui-inset, 0 auto 0 0);
6678
+ --_inset: var(--ag-ui-inset, var(--_viewport-inset-top) auto var(--_viewport-inset-bottom) var(--_viewport-inset-left));
6213
6679
  }
6214
6680
 
6215
6681
  /* The docked panel is pinned to the edge it docks against rather than filling
6216
6682
  the host as a flex child. Collapsing shrinks the host to the rail width, and
6217
6683
  a flex child would be squashed to 52px on the way out instead of sliding out
6218
6684
  at full width. */
6685
+ /* The panel is taken out of flow so the collapse can slide it out at full
6686
+ width; see the note above. That makes the host its containing block, and the
6687
+ host is only one by accident: it is position: fixed by default. A host that
6688
+ takes the documented route to a pushed layout instead of an overlay sets
6689
+ --ag-ui-position: static, and a static element establishes nothing -- so the
6690
+ panel resolved against the initial containing block, landed at the document
6691
+ origin, and scrolled away with the page. Docked left it pinned to the
6692
+ document's left edge rather than the host's.
6693
+
6694
+ It looked correct wherever it was first tried, because a full-height column
6695
+ at the top of an unscrolled document is exactly where those two answers
6696
+ coincide. Containment makes the host a containing block whatever its
6697
+ position, so the panel stays in the box the host was given. It is a no-op in
6698
+ the default case, which is already positioned. */
6699
+ :host([placement="sidebar"]) {
6700
+ contain: layout;
6701
+ }
6702
+
6219
6703
  :host([placement="sidebar"]) .chat {
6220
6704
  position: absolute;
6221
6705
  inset: 0 0 0 auto;
@@ -6231,11 +6715,15 @@ var STYLES = `
6231
6715
  /* Collapsed sidebar: shrink the host to the rail width and slide the panel out
6232
6716
  through the edge it docks against. Higher specificity than the generic
6233
6717
  collapse rules, so it wins regardless of source order. */
6718
+ /* The rail is the full height of what the host left, not of the screen. It
6719
+ said 100vh and pinned its own bottom, which put it under any chrome the host
6720
+ had reserved -- and the icon lives at the top of the rail, so the one control
6721
+ that reopens the panel was the first thing to disappear behind a sticky
6722
+ header. */
6234
6723
  :host([placement="sidebar"][collapsed]) {
6235
6724
  width: var(--_rail-width);
6236
- height: 100vh;
6237
- max-height: 100vh;
6238
- bottom: 0;
6725
+ height: var(--_viewport-height);
6726
+ max-height: var(--_viewport-height);
6239
6727
  pointer-events: auto;
6240
6728
  }
6241
6729
 
@@ -6351,17 +6839,83 @@ var STYLES = `
6351
6839
 
6352
6840
  /* The sidebar collapses to an edge rail instead: full height, square, flush
6353
6841
  against the dock. It slides in with the panel rather than popping. */
6842
+ /* The edge rail. It reads as the docked edge of a panel rather than a coloured
6843
+ stripe: the surface the panel is made of, a border on the side it docks
6844
+ against, and the accent kept for the icon -- a full-height slab of accent is
6845
+ the loudest thing on the page and says the least about itself.
6846
+
6847
+ Content sits at the top rather than centred, because a control floating in
6848
+ the middle of a screen-high column has nothing to belong to. */
6354
6849
  :host([placement="sidebar"][collapsed]) .launcher {
6355
6850
  inset: 0;
6356
6851
  width: auto;
6357
6852
  height: auto;
6358
- align-items: flex-start;
6359
- padding-top: 16px;
6360
- border: 1px solid var(--_border);
6853
+ flex-direction: column;
6854
+ align-items: center;
6855
+ justify-content: flex-start;
6856
+ gap: 12px;
6857
+ padding-top: 14px;
6858
+ border: 0;
6859
+ border-inline-start: 1px solid var(--_border);
6361
6860
  border-radius: 0;
6861
+ background: var(--_bg);
6862
+ color: var(--_fg);
6863
+ box-shadow: none;
6864
+ transform: none;
6865
+ }
6866
+
6867
+ :host([placement="sidebar"][data-side="left"][collapsed]) .launcher {
6868
+ border-inline-start: 0;
6869
+ border-inline-end: 1px solid var(--_border);
6870
+ }
6871
+
6872
+ /* The icon keeps the accent, so there is one obvious thing to press.
6873
+
6874
+ The glyph is sized here rather than left to fill the holder. A glyph in an
6875
+ icon holder takes the holder's whole box by default, which is right where the
6876
+ holder is only a box and wrong the moment it becomes a filled circle: the
6877
+ mark then runs edge to edge and reads as a square crammed into a disc. The
6878
+ proportion is the floating launcher's own -- a 26px glyph in a 56px bubble --
6879
+ so the two collapsed states look like the same widget. */
6880
+ :host([placement="sidebar"][collapsed]) .launcher .icon-holder {
6881
+ display: inline-flex;
6882
+ align-items: center;
6883
+ justify-content: center;
6884
+ width: 34px;
6885
+ height: 34px;
6886
+ border-radius: 50%;
6362
6887
  background: var(--_header-bg);
6363
6888
  color: var(--_header-fg);
6364
- box-shadow: none;
6889
+ }
6890
+
6891
+ :host([placement="sidebar"][collapsed]) .launcher .icon-holder .glyph,
6892
+ :host([placement="sidebar"][collapsed]) .launcher .icon-holder .icon-img {
6893
+ width: 16px;
6894
+ height: 16px;
6895
+ }
6896
+
6897
+ /* Written down the rail, which is the only direction it fits. Reading upward
6898
+ is the convention for a right-hand edge and matches how a docked panel's
6899
+ label is set everywhere it appears. */
6900
+ .rail-label {
6901
+ display: none;
6902
+ }
6903
+
6904
+ :host([placement="sidebar"][collapsed]) .rail-label {
6905
+ display: block;
6906
+ writing-mode: vertical-rl;
6907
+ transform: rotate(180deg);
6908
+ max-height: calc(100% - 96px);
6909
+ overflow: hidden;
6910
+ font-size: 0.9em;
6911
+ font-weight: 600;
6912
+ letter-spacing: 0.02em;
6913
+ white-space: nowrap;
6914
+ text-overflow: ellipsis;
6915
+ }
6916
+
6917
+ :host([placement="sidebar"][data-side="left"][collapsed]) .rail-label {
6918
+ writing-mode: vertical-rl;
6365
6919
  transform: none;
6366
6920
  }
6367
6921
 
@@ -6475,17 +7029,24 @@ var STYLES = `
6475
7029
 
6476
7030
  .header-controls {
6477
7031
  display: flex;
6478
- gap: 2px;
7032
+ gap: var(--_header-gap);
6479
7033
  flex: none;
6480
7034
  }
6481
7035
 
6482
7036
  .header-btn {
7037
+ display: inline-flex;
7038
+ flex: 0 0 auto;
7039
+ align-items: center;
7040
+ justify-content: center;
7041
+ box-sizing: border-box;
7042
+ width: var(--_header-btn-size);
7043
+ height: var(--_header-btn-size);
6483
7044
  border: none;
6484
7045
  background: transparent;
6485
7046
  color: inherit;
6486
7047
  font: inherit;
6487
7048
  line-height: 1;
6488
- padding: 4px 7px;
7049
+ padding: 0;
6489
7050
  border-radius: 6px;
6490
7051
  cursor: pointer;
6491
7052
  opacity: 0.85;
@@ -6507,9 +7068,9 @@ var STYLES = `
6507
7068
  Nothing paints there once the panel is gone, so the box only has to stop
6508
7069
  swallowing clicks: pointer events go to none and the launcher takes them.
6509
7070
 
6510
- Two placements collapse differently: "sidebar" slides to its rail (below),
6511
- while "embedded" and "page" keep the header bar, having no corner for a
6512
- floating circle that would escape the host's layout. */
7071
+ Two placements do not use it: "sidebar" slides to its rail (below), and
7072
+ "embedded" keeps its header bar, having no corner for a floating circle that
7073
+ would escape the host's layout. "page" has no collapsed state at all. */
6513
7074
  :host([collapsed]) {
6514
7075
  pointer-events: none;
6515
7076
  /* A collapsed host has to be allowed to shrink, and in the layout hosts
@@ -6571,37 +7132,107 @@ var STYLES = `
6571
7132
  transform-origin: bottom right;
6572
7133
  }
6573
7134
 
6574
- /* The two in-flow placements keep the original collapse: hide the body, let
6575
- the host shrink to the header bar. */
6576
- :host([collapsed]:is([placement="embedded"], [placement="page"])) {
7135
+ /* The in-flow placement keeps the original collapse: hide the body, let the
7136
+ host shrink to the header bar. */
7137
+ :host([collapsed][placement="embedded"]) {
6577
7138
  height: auto;
6578
7139
  max-height: none;
6579
7140
  pointer-events: auto;
6580
7141
  }
6581
7142
 
6582
- :host([collapsed]:is([placement="embedded"], [placement="page"])) .chat {
7143
+ :host([collapsed][placement="embedded"]) .chat {
6583
7144
  opacity: 1;
6584
7145
  transform: none;
6585
7146
  visibility: visible;
6586
7147
  }
6587
7148
 
6588
- /* These two keep the header bar, so the launcher must stay out of the way: an
7149
+ /* It keeps the header bar, so the launcher must stay out of the way: an
6589
7150
  embedded host is position: static, which would let an absolutely-positioned
6590
7151
  circle escape the layout and land against whatever the page positions. */
6591
- :host([collapsed]:is([placement="embedded"], [placement="page"])) .launcher {
7152
+ :host([collapsed][placement="embedded"]) .launcher {
6592
7153
  visibility: hidden;
6593
7154
  opacity: 0;
6594
7155
  }
6595
7156
 
6596
- :host([collapsed]:is([placement="embedded"], [placement="page"])) .messages-wrap,
6597
- :host([collapsed]:is([placement="embedded"], [placement="page"])) .messages,
6598
- :host([collapsed]:is([placement="embedded"], [placement="page"])) .input-row,
6599
- :host([collapsed]:is([placement="embedded"], [placement="page"])) .skill-chips,
6600
- :host([collapsed]:is([placement="embedded"], [placement="page"])) .skill-palette,
6601
- :host([collapsed]:is([placement="embedded"], [placement="page"])) .skill-hint {
7157
+ :host([collapsed][placement="embedded"]) .messages-wrap,
7158
+ :host([collapsed][placement="embedded"]) .messages,
7159
+ :host([collapsed][placement="embedded"]) .input-row,
7160
+ :host([collapsed][placement="embedded"]) .skill-chips,
7161
+ :host([collapsed][placement="embedded"]) .skill-palette,
7162
+ :host([collapsed][placement="embedded"]) .skill-hint {
7163
+ display: none;
7164
+ }
7165
+
7166
+ /* A docked conversation list: beside the transcript rather than over it.
7167
+
7168
+ A full-page chat is the one surface with width to spare, and covering the
7169
+ conversation to show the list of conversations is the wrong trade there --
7170
+ it hides the thing you are trying to get back to. Everywhere else the panel
7171
+ is a few hundred pixels wide and a docked list would leave a column of
7172
+ transcript narrower than the messages in it, so this is the only placement
7173
+ that gets it.
7174
+
7175
+ The transcript is moved by padding on the shell rather than by making the
7176
+ list a flex sibling: the drawer is the last child of the panel, and no
7177
+ selector reaches backwards from it to the rows that have to shift. That is
7178
+ also why the state is stamped on the host. */
7179
+ :host([data-threads-docked]) .drawer {
7180
+ /* Not a scrim: the page behind it is still the user's to work in. */
7181
+ pointer-events: none;
7182
+ }
7183
+
7184
+ :host([data-threads-docked]) .drawer-backdrop {
7185
+ display: none;
7186
+ }
7187
+
7188
+ :host([data-threads-docked]) .drawer-panel {
7189
+ width: var(--_threads-rail-width);
7190
+ pointer-events: auto;
7191
+ border-inline-end: 1px solid var(--_border);
7192
+ box-shadow: none;
7193
+ }
7194
+
7195
+ :host([data-threads-docked]) .messages,
7196
+ :host([data-threads-docked]) .input-row,
7197
+ :host([data-threads-docked]) .skill-chips,
7198
+ :host([data-threads-docked]) .attachment-tray {
7199
+ padding-inline-start: calc(
7200
+ var(--_threads-rail-width) + max(var(--_pad), (100% - var(--_content-max-width)) / 2)
7201
+ );
7202
+ }
7203
+
7204
+ :host([data-threads-docked]) .header {
7205
+ padding-inline-start: calc(var(--_threads-rail-width) + var(--_pad));
7206
+ }
7207
+
7208
+ /* The page placement has no collapsed state, so it offers no control for one.
7209
+ A dedicated route has no "away" to go to: shrinking it left a strip of
7210
+ application chrome fixed over a route that no longer had an owner, and this
7211
+ is the one placement where the launcher that would bring it back is hidden.
7212
+
7213
+ Hiding the control is only half of it. The state has another way in -- the
7214
+ attribute can be written straight onto the element, and a value stored under
7215
+ a different placement is restored on connect -- and the JS guards that catch
7216
+ those cannot see an attribute set directly. So the placement neutralises the
7217
+ state here as well: with the collapse rules above no longer naming "page", an
7218
+ unguarded collapsed attribute would otherwise fall through to the generic
7219
+ rule that scales the panel away and drops pointer events, leaving nothing on
7220
+ screen and no launcher to press. */
7221
+ :host([placement="page"]) .header-btn--collapse {
6602
7222
  display: none;
6603
7223
  }
6604
7224
 
7225
+ :host([placement="page"][collapsed]) {
7226
+ pointer-events: auto;
7227
+ align-self: auto;
7228
+ }
7229
+
7230
+ :host([placement="page"][collapsed]) .chat {
7231
+ opacity: 1;
7232
+ transform: none;
7233
+ visibility: visible;
7234
+ }
7235
+
6605
7236
  /* Jump-to-latest: shown only once the reader has scrolled away *and* missed
6606
7237
  something. Anchored to the panel rather than the list so it does not scroll
6607
7238
  with the content it is offering to scroll to. */
@@ -6689,6 +7320,28 @@ var STYLES = `
6689
7320
 
6690
7321
  The 1px box with clip-path, rather than width/height 0, is the shape that
6691
7322
  survives: a zero-sized element is dropped from the tree by some engines. */
7323
+ /* A used-value reader for the four viewport-inset tokens, and the only
7324
+ reliable one. getComputedStyle().getPropertyValue() on an unregistered
7325
+ custom property hands back the substituted token stream, not a length: it
7326
+ returns "4rem" verbatim, and "calc(56px + env(safe-area-inset-top))" as
7327
+ "calc(56px + 0px)" -- which parses as NaN and takes the whole inset with it.
7328
+ Padding is a real property, so the same tokens come back resolved to px.
7329
+
7330
+ visibility rather than display: none, because a box that generates no
7331
+ layout has no used values to read. Zero-sized, absolutely positioned and
7332
+ inert, so it costs nothing but the read. */
7333
+ .viewport-probe {
7334
+ position: absolute;
7335
+ top: 0;
7336
+ left: 0;
7337
+ width: 0;
7338
+ height: 0;
7339
+ visibility: hidden;
7340
+ pointer-events: none;
7341
+ padding: var(--_viewport-inset-top) var(--_viewport-inset-right)
7342
+ var(--_viewport-inset-bottom) var(--_viewport-inset-left);
7343
+ }
7344
+
6692
7345
  .sr-only {
6693
7346
  position: absolute;
6694
7347
  width: 1px;
@@ -6704,6 +7357,8 @@ var STYLES = `
6704
7357
  .messages {
6705
7358
  flex: 1;
6706
7359
  overflow-y: auto;
7360
+ /* Scrolling past the end of this must not scroll the page behind it. */
7361
+ overscroll-behavior: contain;
6707
7362
  /* The browser's own scroll anchoring competes with the scroller for the same
6708
7363
  job and wins unpredictably -- it can hold the view still exactly when we
6709
7364
  want to follow. Turned off so following is decided in one place. Safari
@@ -6856,6 +7511,8 @@ var STYLES = `
6856
7511
  .message--assistant pre {
6857
7512
  padding: 8px 10px;
6858
7513
  overflow: auto;
7514
+ /* Scrolling past the end of this must not scroll the page behind it. */
7515
+ overscroll-behavior: contain;
6859
7516
  background: var(--_bg);
6860
7517
  border: 1px solid var(--_border);
6861
7518
  border-radius: 6px;
@@ -7034,6 +7691,8 @@ var STYLES = `
7034
7691
  border-left: 2px solid var(--_border);
7035
7692
  max-height: 220px;
7036
7693
  overflow: auto;
7694
+ /* Scrolling past the end of this must not scroll the page behind it. */
7695
+ overscroll-behavior: contain;
7037
7696
  white-space: pre-wrap;
7038
7697
  word-break: break-word;
7039
7698
  font-family: inherit;
@@ -7224,6 +7883,8 @@ var STYLES = `
7224
7883
  padding: 6px 8px;
7225
7884
  max-height: 160px;
7226
7885
  overflow: auto;
7886
+ /* Scrolling past the end of this must not scroll the page behind it. */
7887
+ overscroll-behavior: contain;
7227
7888
  background: var(--_bg);
7228
7889
  border: 1px solid var(--_border);
7229
7890
  border-radius: 6px;
@@ -7718,6 +8379,8 @@ var STYLES = `
7718
8379
  resize: none;
7719
8380
  max-height: var(--_composer-max-height);
7720
8381
  overflow-y: auto;
8382
+ /* Scrolling past the end of this must not scroll the page behind it. */
8383
+ overscroll-behavior: contain;
7721
8384
  padding: 6px 4px 2px;
7722
8385
  background: transparent;
7723
8386
  border: none;
@@ -8002,6 +8665,30 @@ var STYLES = `
8002
8665
  opacity: 0.75;
8003
8666
  }
8004
8667
 
8668
+ /* The one control a notice may carry. Quiet, because it reports something
8669
+ already done rather than asking for a decision. */
8670
+ .run-notice-undo {
8671
+ flex: 0 0 auto;
8672
+ margin-inline-start: auto;
8673
+ padding: 2px 8px;
8674
+ border: 1px solid var(--_border);
8675
+ border-radius: var(--_radius);
8676
+ background: var(--_bg);
8677
+ color: var(--_accent);
8678
+ font: inherit;
8679
+ font-size: 0.9em;
8680
+ cursor: pointer;
8681
+ }
8682
+
8683
+ .run-notice-undo:hover:not(:disabled) {
8684
+ background: var(--_hover);
8685
+ }
8686
+
8687
+ .run-notice-undo:disabled {
8688
+ opacity: 0.5;
8689
+ cursor: default;
8690
+ }
8691
+
8005
8692
  .run-notice-text {
8006
8693
  min-width: 0;
8007
8694
  overflow-wrap: anywhere;
@@ -8146,6 +8833,8 @@ var STYLES = `
8146
8833
  padding: 8px 10px;
8147
8834
  max-height: 140px;
8148
8835
  overflow: auto;
8836
+ /* Scrolling past the end of this must not scroll the page behind it. */
8837
+ overscroll-behavior: contain;
8149
8838
  font-size: 12px;
8150
8839
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
8151
8840
  background: var(--_assistant-bg);
@@ -8334,6 +9023,43 @@ var STYLES = `
8334
9023
  }
8335
9024
  }
8336
9025
 
9026
+ /* Touch. Separate from the width breakpoint above on purpose: width decides
9027
+ the layout, the pointer decides which controls make sense, and a touch
9028
+ laptop is coarse-pointered and wide while a narrow desktop window is
9029
+ fine-pointered and small. */
9030
+ @media (pointer: coarse) {
9031
+ /* A 6px edge strip is not a control, it is a trap that eats a scroll. The
9032
+ corners are 14px, still under the 24px floor a target is meant to clear,
9033
+ and the panel has other ways to be resized on a device that has a pointer
9034
+ precise enough to grab one. */
9035
+ .resize-handle {
9036
+ display: none;
9037
+ }
9038
+
9039
+ /* Platform guidance is 44pt on iOS and 48dp on Android. These sit at 28-30px,
9040
+ which clears the WCAG minimum and misses both. */
9041
+ :host {
9042
+ --_action-size: var(--ag-ui-action-size, 44px);
9043
+ --_tool-btn-size: var(--ag-ui-tool-btn-size, 44px);
9044
+ --_send-size: var(--ag-ui-send-size, 44px);
9045
+ /* Missed the first time these were raised, which left the header -- the
9046
+ widget's primary controls, and the only way to reach history, a new chat
9047
+ or the collapse -- at half the size of everything beside it. */
9048
+ --_header-btn-size: var(--ag-ui-header-btn-size, 44px);
9049
+ --_header-gap: var(--ag-ui-header-gap, 6px);
9050
+ }
9051
+
9052
+ /* iOS Safari zooms the page when a control under 16px takes focus, which
9053
+ drags the whole fixed panel with it and leaves the user pinching back out
9054
+ of a chat they only wanted to type into. The composer inherits the widget
9055
+ font, which is 14px by default and 13px at compact density, so it has to
9056
+ say 16 outright -- and only ever upward, so a host that has deliberately
9057
+ set something larger keeps it. */
9058
+ .input {
9059
+ font-size: max(16px, var(--_font-size));
9060
+ }
9061
+ }
9062
+
8337
9063
  .message-action[aria-pressed="true"] {
8338
9064
  opacity: 1;
8339
9065
  color: var(--_accent);
@@ -8522,6 +9248,8 @@ var STYLES = `
8522
9248
  flex-direction: column;
8523
9249
  max-height: 220px;
8524
9250
  overflow: auto;
9251
+ /* Scrolling past the end of this must not scroll the page behind it. */
9252
+ overscroll-behavior: contain;
8525
9253
  background: var(--_bg);
8526
9254
  border: 1px solid var(--_border);
8527
9255
  border-radius: 8px;
@@ -8610,6 +9338,8 @@ var STYLES = `
8610
9338
  box-shadow: 0 6px 24px rgb(0 0 0 / 12%);
8611
9339
  max-height: 60%;
8612
9340
  overflow-y: auto;
9341
+ /* Scrolling past the end of this must not scroll the page behind it. */
9342
+ overscroll-behavior: contain;
8613
9343
  transform-origin: top center;
8614
9344
  transition:
8615
9345
  opacity var(--_motion) var(--_ease),
@@ -8785,10 +9515,20 @@ var STYLES = `
8785
9515
  }
8786
9516
 
8787
9517
  .drawer-title {
9518
+ /* Takes the slack so the two controls group at the trailing edge. With
9519
+ space-between and three items the middle one floats, which reads as the
9520
+ title and the close button being a pair with New chat wedged between
9521
+ them. Truncates rather than pushing them off the row. */
9522
+ flex: 1 1 auto;
9523
+ min-width: 0;
9524
+ overflow: hidden;
9525
+ text-overflow: ellipsis;
9526
+ white-space: nowrap;
8788
9527
  font-weight: 600;
8789
9528
  }
8790
9529
 
8791
9530
  .drawer-new {
9531
+ flex: 0 0 auto;
8792
9532
  border: 1px solid var(--_border);
8793
9533
  border-radius: var(--_radius);
8794
9534
  background: var(--_bg);
@@ -8799,10 +9539,63 @@ var STYLES = `
8799
9539
  cursor: pointer;
8800
9540
  }
8801
9541
 
9542
+ /* Narrows the list. Sits under the header rather than in it: the header's two
9543
+ controls act on the conversation, and a field that filters what is below it
9544
+ belongs with what it filters. */
9545
+ /* What is waiting for the run to finish. Above the composer, beside the
9546
+ attachment tray, because both are things already handed over and not yet
9547
+ sent. Each chip is a button: pressing it takes that message back. */
9548
+ .queued {
9549
+ display: flex;
9550
+ flex-wrap: wrap;
9551
+ gap: var(--_space);
9552
+ padding: 0 var(--_pad) var(--_space);
9553
+ }
9554
+
9555
+ .queued-chip {
9556
+ max-width: 100%;
9557
+ overflow: hidden;
9558
+ padding: 4px 10px;
9559
+ border: 1px dashed var(--_border);
9560
+ border-radius: var(--_msg-radius);
9561
+ background: var(--_bg);
9562
+ color: var(--_muted);
9563
+ font: inherit;
9564
+ font-size: 0.9em;
9565
+ text-overflow: ellipsis;
9566
+ white-space: nowrap;
9567
+ cursor: pointer;
9568
+ }
9569
+
9570
+ .queued-chip:hover {
9571
+ border-style: solid;
9572
+ color: var(--_fg);
9573
+ }
9574
+
9575
+ .drawer-filter {
9576
+ flex: 0 0 auto;
9577
+ box-sizing: border-box;
9578
+ width: calc(100% - var(--_pad) * 2);
9579
+ margin: var(--_space) var(--_pad) 0;
9580
+ padding: 6px 10px;
9581
+ border: 1px solid var(--_border);
9582
+ border-radius: var(--_radius);
9583
+ background: var(--_input-bg);
9584
+ color: var(--_fg);
9585
+ font: inherit;
9586
+ font-size: 0.9em;
9587
+ }
9588
+
9589
+ .drawer-filter::placeholder {
9590
+ color: var(--_muted);
9591
+ }
9592
+
8802
9593
  .drawer-list {
8803
9594
  flex: 1;
8804
9595
  min-height: 0;
8805
9596
  overflow-y: auto;
9597
+ /* Scrolling past the end of this must not scroll the page behind it. */
9598
+ overscroll-behavior: contain;
8806
9599
  }
8807
9600
 
8808
9601
  .drawer-empty {
@@ -8923,19 +9716,44 @@ var STYLES = `
8923
9716
  background: none;
8924
9717
  }
8925
9718
 
8926
- :host([placement="embedded"]) .drawer-panel {
8927
- width: 100%;
8928
- border-right: none;
8929
- box-shadow: none;
8930
- }
8931
- `;
8932
-
8933
- // src/ui/subagent_panel.ts
8934
- var SubAgentPanel = class {
8935
- /** The panel's root; append this into the delegating card's slot. */
8936
- element;
8937
- /**
8938
- * The child agent's name, as the last update that carried one gave it.
9719
+ /* The way back out of the list. It sits beside New chat, which is the control
9720
+ it must not be mistaken for: one returns you to the conversation you were
9721
+ reading, the other replaces it. */
9722
+ .drawer-close {
9723
+ flex: 0 0 auto;
9724
+ display: inline-flex;
9725
+ align-items: center;
9726
+ justify-content: center;
9727
+ width: 26px;
9728
+ height: 26px;
9729
+ padding: 0;
9730
+ border: 0;
9731
+ border-radius: 6px;
9732
+ background: none;
9733
+ color: inherit;
9734
+ font: inherit;
9735
+ font-size: 18px;
9736
+ line-height: 1;
9737
+ cursor: pointer;
9738
+ }
9739
+
9740
+ .drawer-close:hover {
9741
+ background: var(--_hover);
9742
+ }
9743
+
9744
+ :host([placement="embedded"]) .drawer-panel {
9745
+ width: 100%;
9746
+ border-right: none;
9747
+ box-shadow: none;
9748
+ }
9749
+ `;
9750
+
9751
+ // src/ui/subagent_panel.ts
9752
+ var SubAgentPanel = class {
9753
+ /** The panel's root; append this into the delegating card's slot. */
9754
+ element;
9755
+ /**
9756
+ * The child agent's name, as the last update that carried one gave it.
8939
9757
  *
8940
9758
  * Read back rather than only written to the DOM, because the two carriers
8941
9759
  * split the name away from the close: `SUBAGENT_STARTED` names the agent and
@@ -9190,6 +10008,7 @@ var ThoughtsBlock = class {
9190
10008
  };
9191
10009
 
9192
10010
  // src/ui/thread_drawer.ts
10011
+ var FILTER_FROM = 8;
9193
10012
  var ThreadDrawer = class {
9194
10013
  /** The drawer root (backdrop + panel). Append to the chat shell; hidden until opened. */
9195
10014
  element;
@@ -9202,6 +10021,23 @@ var ThreadDrawer = class {
9202
10021
  #strings;
9203
10022
  #threads = [];
9204
10023
  #activeId = "";
10024
+ /** Dismisses the list without starting a new conversation. */
10025
+ #closeButton;
10026
+ /** Narrows the list. Only rendered once there is enough of a list to narrow. */
10027
+ #filter;
10028
+ /** The current filter text, lowercased once rather than per row. */
10029
+ #query = "";
10030
+ /**
10031
+ * Whether the list covers the conversation, or sits beside it.
10032
+ *
10033
+ * Everything a dialog implies follows from this and nothing else does: the
10034
+ * modal role, the focus trap, and taking focus on open. A list docked beside
10035
+ * a full-page conversation is a region of that page -- telling assistive
10036
+ * technology to ignore the rest of the document while it is showing would be
10037
+ * a lie, and stealing focus into it would interrupt someone mid-sentence to
10038
+ * announce a list they can already see.
10039
+ */
10040
+ #modal = true;
9205
10041
  /** The element focused before the drawer opened, restored on close. */
9206
10042
  #lastFocused = null;
9207
10043
  constructor(callbacks, strings = DEFAULT_UI_STRINGS) {
@@ -9238,11 +10074,29 @@ var ThreadDrawer = class {
9238
10074
  this.close();
9239
10075
  this.#callbacks.onNew();
9240
10076
  });
9241
- header.append(this.#heading, this.#newButton);
10077
+ this.#closeButton = document.createElement("button");
10078
+ this.#closeButton.type = "button";
10079
+ this.#closeButton.className = "drawer-close";
10080
+ this.#closeButton.setAttribute("part", "drawer-close");
10081
+ this.#closeButton.title = strings.closeHistory;
10082
+ this.#closeButton.setAttribute("aria-label", strings.closeHistory);
10083
+ this.#closeButton.append(document.createTextNode("\xD7"));
10084
+ this.#closeButton.addEventListener("click", () => this.close());
10085
+ header.append(this.#heading, this.#newButton, this.#closeButton);
10086
+ this.#filter = document.createElement("input");
10087
+ this.#filter.type = "search";
10088
+ this.#filter.className = "drawer-filter";
10089
+ this.#filter.setAttribute("part", "drawer-filter");
10090
+ this.#filter.placeholder = strings.searchConversations;
10091
+ this.#filter.setAttribute("aria-label", strings.searchConversations);
10092
+ this.#filter.addEventListener("input", () => {
10093
+ this.#query = this.#filter.value.trim().toLowerCase();
10094
+ this.#renderList();
10095
+ });
9242
10096
  this.#list = document.createElement("div");
9243
10097
  this.#list.className = "drawer-list";
9244
10098
  this.#list.setAttribute("part", "drawer-list");
9245
- this.#panel.append(header, this.#list);
10099
+ this.#panel.append(header, this.#filter, this.#list);
9246
10100
  this.element.append(backdrop, this.#panel);
9247
10101
  }
9248
10102
  /** Re-localize the drawer's chrome and rows (the host calls this on connect). */
@@ -9266,8 +10120,26 @@ var ThreadDrawer = class {
9266
10120
  this.#panel.setAttribute("aria-label", strings.chatHistory);
9267
10121
  this.#heading.textContent = strings.chats;
9268
10122
  this.#newButton.textContent = strings.newChat;
10123
+ this.#closeButton.title = strings.closeHistory;
10124
+ this.#closeButton.setAttribute("aria-label", strings.closeHistory);
10125
+ this.#filter.placeholder = strings.searchConversations;
10126
+ this.#filter.setAttribute("aria-label", strings.searchConversations);
9269
10127
  this.#renderList();
9270
10128
  }
10129
+ /**
10130
+ * Whether the list is a dialog over the conversation or a rail beside it.
10131
+ * The host decides, because only the placement knows whether there is room.
10132
+ */
10133
+ setModal(modal) {
10134
+ this.#modal = modal;
10135
+ if (modal) {
10136
+ this.#panel.setAttribute("role", "dialog");
10137
+ this.#panel.setAttribute("aria-modal", "true");
10138
+ } else {
10139
+ this.#panel.setAttribute("role", "region");
10140
+ this.#panel.removeAttribute("aria-modal");
10141
+ }
10142
+ }
9271
10143
  isOpen() {
9272
10144
  return !this.element.hidden;
9273
10145
  }
@@ -9277,15 +10149,21 @@ var ThreadDrawer = class {
9277
10149
  }
9278
10150
  this.#lastFocused = this.#activeElement();
9279
10151
  this.element.hidden = false;
9280
- this.#newButton.focus();
10152
+ if (this.#modal) {
10153
+ this.#newButton.focus();
10154
+ }
10155
+ this.#callbacks.onVisibility?.(true);
9281
10156
  }
9282
10157
  close() {
9283
10158
  if (!this.isOpen()) {
9284
10159
  return;
9285
10160
  }
9286
10161
  this.element.hidden = true;
9287
- this.#lastFocused?.focus();
10162
+ if (this.#modal) {
10163
+ this.#lastFocused?.focus();
10164
+ }
9288
10165
  this.#lastFocused = null;
10166
+ this.#callbacks.onVisibility?.(false);
9289
10167
  }
9290
10168
  toggle() {
9291
10169
  if (this.isOpen()) {
@@ -9305,7 +10183,7 @@ var ThreadDrawer = class {
9305
10183
  this.close();
9306
10184
  return;
9307
10185
  }
9308
- if (event.key !== "Tab") {
10186
+ if (event.key !== "Tab" || !this.#modal) {
9309
10187
  return;
9310
10188
  }
9311
10189
  const focusables = Array.from(
@@ -9330,18 +10208,42 @@ var ThreadDrawer = class {
9330
10208
  }
9331
10209
  #renderList() {
9332
10210
  this.#list.replaceChildren();
9333
- if (this.#threads.length === 0) {
10211
+ const hideFilter = this.#threads.length < FILTER_FROM;
10212
+ this.#filter.hidden = hideFilter;
10213
+ if (hideFilter && this.#query !== "") {
10214
+ this.#filter.value = "";
10215
+ this.#query = "";
10216
+ }
10217
+ const shown = this.#matching();
10218
+ if (shown.length === 0) {
9334
10219
  const empty = document.createElement("div");
9335
10220
  empty.className = "drawer-empty";
9336
10221
  empty.setAttribute("part", "drawer-empty");
9337
- empty.textContent = this.#strings.noConversations;
10222
+ empty.textContent = this.#threads.length === 0 ? this.#strings.noConversations : this.#strings.noMatches;
9338
10223
  this.#list.appendChild(empty);
9339
10224
  return;
9340
10225
  }
9341
- for (const meta of this.#threads) {
10226
+ for (const meta of shown) {
9342
10227
  this.#list.appendChild(this.#renderRow(meta));
9343
10228
  }
9344
10229
  }
10230
+ /**
10231
+ * The rows the filter leaves.
10232
+ *
10233
+ * Title and preview both, because the title is often the model's summary of
10234
+ * a conversation and the thing being looked for is as likely to be a phrase
10235
+ * from inside it. Client-side over what the drawer already holds: the server
10236
+ * index is fetched whole, so a query would be a round trip to filter a list
10237
+ * already in memory.
10238
+ */
10239
+ #matching() {
10240
+ if (this.#query === "") {
10241
+ return this.#threads;
10242
+ }
10243
+ return this.#threads.filter(
10244
+ (meta) => meta.title.toLowerCase().includes(this.#query) || meta.preview.toLowerCase().includes(this.#query)
10245
+ );
10246
+ }
9345
10247
  #renderRow(meta) {
9346
10248
  const row = document.createElement("div");
9347
10249
  row.className = "drawer-row";
@@ -11113,7 +12015,7 @@ var AgUiChat = class extends HTMLElement {
11113
12015
  * Resolve a `scroll_to` / `drag_and_drop` target string to a host-page
11114
12016
  * element (or `null`). Defaults to a CSS-selector lookup; override to map
11115
12017
  * page-map element ids. The page-action tools are opt-in via the
11116
- * `data-page-actions` attribute (`"scroll"` / `"drag"`).
12018
+ * `data-page-actions` attribute (`"scroll"` / `"drag"` / `"chat"`).
11117
12019
  */
11118
12020
  resolvePageTarget = (target) => document.querySelector(target);
11119
12021
  /**
@@ -11229,6 +12131,12 @@ var AgUiChat = class extends HTMLElement {
11229
12131
  #root;
11230
12132
  /** Screen-reader-only status region -- see {@link AgUiChat.#announce}. */
11231
12133
  #announcer = document.createElement("div");
12134
+ /**
12135
+ * A zero-sized box carrying the host's viewport insets as padding, so they
12136
+ * can be read back as used pixel lengths. See the `.viewport-probe` rule for
12137
+ * why a custom property cannot be read directly.
12138
+ */
12139
+ #viewportProbe = document.createElement("div");
11232
12140
  /** Return-to-foot affordance, shown only once something has been missed. */
11233
12141
  #jumpButton = document.createElement("button");
11234
12142
  /**
@@ -11290,6 +12198,8 @@ var AgUiChat = class extends HTMLElement {
11290
12198
  #launcher;
11291
12199
  /** The launcher's unread badge; hidden at zero, and when the host opts out. */
11292
12200
  #badge;
12201
+ /** The rail's vertical caption. Rendered only by the sidebar's edge rail. */
12202
+ #railLabel = document.createElement("span");
11293
12203
  // Answers that finished while the widget was collapsed. Expanding clears it.
11294
12204
  #unread = 0;
11295
12205
  /** Empty-state region at the top of the message list; hidden once anything renders. */
@@ -11302,6 +12212,14 @@ var AgUiChat = class extends HTMLElement {
11302
12212
  * position -- see #applyLauncherPlacement for what that costs the host.
11303
12213
  */
11304
12214
  #launcherPos = null;
12215
+ /** What the user has sent this session, newest first, for arrow-key recall. */
12216
+ #sentDrafts = [];
12217
+ /** Typed while a run was in flight, oldest first; sent when it settles. */
12218
+ #queued = [];
12219
+ /** The row those show up in, above the composer. */
12220
+ #queuedRow = document.createElement("div");
12221
+ /** How far back the composer has been walked; null while the user is typing. */
12222
+ #recallIndex = null;
11305
12223
  /**
11306
12224
  * Where the user dragged the *panel*, in viewport coordinates, or null while
11307
12225
  * its position is still derived from the launcher's.
@@ -11332,8 +12250,53 @@ var AgUiChat = class extends HTMLElement {
11332
12250
  * a field so `removeEventListener` on disconnect gets the same reference.
11333
12251
  */
11334
12252
  #onViewportResize = () => {
12253
+ this.#publishVisualViewport();
12254
+ if (this.#dragging()) {
12255
+ return;
12256
+ }
11335
12257
  this.#restoreLauncherPosition();
12258
+ if (this.#drawer.isOpen()) {
12259
+ this.#drawer.setModal(!this.#threadsDock());
12260
+ this.#syncThreadsState();
12261
+ }
11336
12262
  };
12263
+ /**
12264
+ * Hold a resized box inside the part of the screen the host left free.
12265
+ *
12266
+ * Each edge on its own, unlike the drag's clamp: a drag moves a box of fixed
12267
+ * size, so pushing it back in is right, while a resize is anchored on the
12268
+ * opposite edge and pushing it back would move the edge the user is not
12269
+ * touching. Bounding each edge instead leaves the grip stopped at the limit
12270
+ * -- the gesture keeps going and the panel simply stops growing, which is
12271
+ * what dragging already does.
12272
+ *
12273
+ * The minimum size is the grip's own concern and is applied before this, so
12274
+ * a panel that cannot fit the space is left at its minimum and overflowing
12275
+ * rather than collapsed to nothing.
12276
+ */
12277
+ #withinViewport(box) {
12278
+ const viewport = this.#viewport();
12279
+ const left = viewport.left + SCREEN_EDGE_MARGIN;
12280
+ const top = viewport.top + SCREEN_EDGE_MARGIN;
12281
+ const right = viewport.left + viewport.width - SCREEN_EDGE_MARGIN;
12282
+ const bottom = viewport.top + viewport.height - SCREEN_EDGE_MARGIN;
12283
+ return {
12284
+ left: Math.min(Math.max(box.left, left), box.right),
12285
+ top: Math.min(Math.max(box.top, top), box.bottom),
12286
+ right: Math.max(Math.min(box.right, right), box.left),
12287
+ bottom: Math.max(Math.min(box.bottom, bottom), box.top)
12288
+ };
12289
+ }
12290
+ /**
12291
+ * Whether a pointer or key gesture is currently placing the widget.
12292
+ *
12293
+ * Read from the stamp the drag helpers already set, rather than tracked
12294
+ * separately: one source of truth, and it clears on `pointercancel` as well
12295
+ * as `pointerup`, which is the end a touch gesture usually gets.
12296
+ */
12297
+ #dragging() {
12298
+ return this.#launcher.hasAttribute("data-dragging") || this.#root.querySelector(".header[data-dragging]") !== null;
12299
+ }
11337
12300
  /** Mic button mount point (input row); the control mounts on connect when enabled. */
11338
12301
  #voiceSlot;
11339
12302
  /** Voice-input control; created on connect when transcription is available. */
@@ -11455,6 +12418,9 @@ var AgUiChat = class extends HTMLElement {
11455
12418
  },
11456
12419
  onDelete: (threadId) => {
11457
12420
  this.#deleteThread(threadId);
12421
+ },
12422
+ onVisibility: () => {
12423
+ this.#syncThreadsState();
11458
12424
  }
11459
12425
  });
11460
12426
  this.#checkpoints = new CheckpointMenu((runId, verb) => {
@@ -11543,11 +12509,15 @@ var AgUiChat = class extends HTMLElement {
11543
12509
  if (name === "placement") {
11544
12510
  this.#releaseOwnedAxes();
11545
12511
  this.#releaseLauncherPosition();
12512
+ if (!this.#collapsible() && this.collapsed) {
12513
+ this.setCollapsed(false);
12514
+ }
11546
12515
  requestAnimationFrame(() => this.#syncResizeAnchor());
11547
12516
  return;
11548
12517
  }
11549
12518
  if (name === "title-text") {
11550
12519
  this.#title.textContent = value ?? this.#strings.title;
12520
+ this.#railLabel.textContent = this.#title.textContent;
11551
12521
  return;
11552
12522
  }
11553
12523
  if (name === "user-key") {
@@ -11656,7 +12626,10 @@ var AgUiChat = class extends HTMLElement {
11656
12626
  const enabled = new Set(
11657
12627
  attr.split(",").map((token) => token.trim()).filter((token) => token !== "")
11658
12628
  );
11659
- return createPageActionTools(enabled, (target) => this.resolvePageTarget(target));
12629
+ return [
12630
+ ...createPageActionTools(enabled, (target) => this.resolvePageTarget(target)),
12631
+ ...enabled.has(PAGE_ACTIONS.CHAT) ? createChatSurfaceTools(this) : []
12632
+ ];
11660
12633
  }
11661
12634
  /** All built-in (route + page + page-action + ask_user) frontend tools. */
11662
12635
  #builtinTools() {
@@ -11896,7 +12869,7 @@ var AgUiChat = class extends HTMLElement {
11896
12869
  });
11897
12870
  this.#strings = mergeUiStrings({ ...this.#readStringOverrides(), ...this.strings });
11898
12871
  if (this.getAttribute("data-theme-toggle") !== null) {
11899
- const saved = this.#readScopedItem(THEME_KEY);
12872
+ const saved = this.#readPreference(THEME_KEY);
11900
12873
  if (saved !== null) {
11901
12874
  this.setAttribute("theme", saved);
11902
12875
  }
@@ -11904,7 +12877,7 @@ var AgUiChat = class extends HTMLElement {
11904
12877
  this.#render();
11905
12878
  this.#drawer.setStrings(this.#strings);
11906
12879
  this.#checkpoints.setStrings(this.#strings);
11907
- if (this.#readScopedItem(COLLAPSED_KEY) === "1") {
12880
+ if (this.#collapsible() && this.#startsCollapsed()) {
11908
12881
  this.setAttribute("collapsed", "");
11909
12882
  }
11910
12883
  this.#syncLauncher();
@@ -11915,6 +12888,9 @@ var AgUiChat = class extends HTMLElement {
11915
12888
  this.conversationStore = this.#builtinStore;
11916
12889
  }
11917
12890
  window.addEventListener("resize", this.#onViewportResize);
12891
+ window.visualViewport?.addEventListener("resize", this.#onViewportResize);
12892
+ window.visualViewport?.addEventListener("scroll", this.#onViewportResize);
12893
+ this.#publishVisualViewport();
11918
12894
  this.#wireThreadStore();
11919
12895
  this.#wireAttachments();
11920
12896
  this.#wireVoice();
@@ -11978,6 +12954,8 @@ var AgUiChat = class extends HTMLElement {
11978
12954
  disconnectedCallback() {
11979
12955
  this.#connected = false;
11980
12956
  window.removeEventListener("resize", this.#onViewportResize);
12957
+ window.visualViewport?.removeEventListener("resize", this.#onViewportResize);
12958
+ window.visualViewport?.removeEventListener("scroll", this.#onViewportResize);
11981
12959
  if (this.#claimedNs !== null) {
11982
12960
  CLAIMED_NAMESPACES.delete(this.#claimedNs);
11983
12961
  this.#claimedNs = null;
@@ -12050,7 +13028,7 @@ var AgUiChat = class extends HTMLElement {
12050
13028
  this.#fileInput.accept = accept;
12051
13029
  this.#attachButton.hidden = false;
12052
13030
  this.#enableDragAndDrop();
12053
- this.#enablePaste();
13031
+ this.#enablePaste(tray);
12054
13032
  }
12055
13033
  /** The built-in multipart upload handler for `data-attachments-url`, or `null`. */
12056
13034
  #defaultUploadHandler(url) {
@@ -12285,6 +13263,62 @@ ${quoted}`;
12285
13263
  }
12286
13264
  });
12287
13265
  }
13266
+ /**
13267
+ * Turn a very long text paste into an attachment instead of a wall of text.
13268
+ *
13269
+ * A composer capped at `40vh` is not where forty thousand characters go: the
13270
+ * user cannot read what they pasted, cannot edit around it, and sends one
13271
+ * enormous turn. As a file it stays whole, the model still receives it, and
13272
+ * the box is left for the question about it.
13273
+ *
13274
+ * Only where the host has configured uploads -- and structurally so, rather
13275
+ * than by a check here: the paste listener is wired inside the attachment
13276
+ * setup, so with no tray there is no listener at all and an ordinary paste is
13277
+ * untouched. Quietly dropping a paste for being long would be far worse than
13278
+ * an awkward composer. The tray is passed rather than read off the field for
13279
+ * the same reason its `onChange` hook is: it can only be called from one that
13280
+ * exists, so taking it as an argument removes a null check no caller can
13281
+ * reach.
13282
+ *
13283
+ * Nothing is lost by removing the chip: the text is still on the clipboard,
13284
+ * so pasting again brings it back. That is why this needs no undo of its own.
13285
+ */
13286
+ #pasteLongTextAsFile(event, clipboard, tray) {
13287
+ const threshold = this.#pasteAttachThreshold();
13288
+ const text4 = clipboard.getData("text/plain");
13289
+ if (threshold === null || text4.length < threshold) {
13290
+ return;
13291
+ }
13292
+ event.preventDefault();
13293
+ const stamp = (/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-");
13294
+ tray.add(new File([text4], `pasted-${stamp}.txt`, { type: "text/plain" }));
13295
+ }
13296
+ /**
13297
+ * How long a pasted string has to be before it becomes a file, or `null` to
13298
+ * leave every paste in the composer.
13299
+ *
13300
+ * One attribute with three answers rather than three attributes: absent is
13301
+ * the default, `off` refuses, and a number states the threshold. A value that
13302
+ * is neither says so, because a typo silently meaning "off" is the failure
13303
+ * this whole release keeps finding.
13304
+ */
13305
+ #pasteAttachThreshold() {
13306
+ const raw = this.getAttribute("data-paste-attach");
13307
+ if (raw === null) {
13308
+ return PASTE_ATTACH_CHARS;
13309
+ }
13310
+ if (raw === "off") {
13311
+ return null;
13312
+ }
13313
+ const stated = Number.parseInt(raw, 10);
13314
+ if (Number.isNaN(stated) || stated <= 0) {
13315
+ console.warn(
13316
+ `<ag-ui-chat>: data-paste-attach="${raw}" is neither "off" nor a positive number of characters, so the default of ${PASTE_ATTACH_CHARS} is used.`
13317
+ );
13318
+ return PASTE_ATTACH_CHARS;
13319
+ }
13320
+ return stated;
13321
+ }
12288
13322
  /**
12289
13323
  * Accept files pasted into the composer.
12290
13324
  *
@@ -12298,7 +13332,7 @@ ${quoted}`;
12298
13332
  * clipboard, and swallowing the words someone meant to paste in order to
12299
13333
  * attach a picture they did not is the worse of the two failures.
12300
13334
  */
12301
- #enablePaste() {
13335
+ #enablePaste(tray) {
12302
13336
  this.#chat.addEventListener("paste", (event) => {
12303
13337
  const clipboard = event.clipboardData ?? null;
12304
13338
  if (clipboard === null) {
@@ -12306,6 +13340,7 @@ ${quoted}`;
12306
13340
  }
12307
13341
  const files = Array.from(clipboard.files);
12308
13342
  if (files.length === 0) {
13343
+ this.#pasteLongTextAsFile(event, clipboard, tray);
12309
13344
  return;
12310
13345
  }
12311
13346
  if (clipboard.getData("text/plain") === "") {
@@ -12466,7 +13501,13 @@ ${quoted}`;
12466
13501
  * per-tab, and emit a {@link TOGGLE_EVENT} so a host can mirror the state in
12467
13502
  * its own chrome.
12468
13503
  */
12469
- setCollapsed(collapsed) {
13504
+ setCollapsed(collapsed, options = {}) {
13505
+ if (collapsed && !this.#collapsible()) {
13506
+ return;
13507
+ }
13508
+ if (options.announce === true && collapsed && !this.collapsed) {
13509
+ this.#announceSurfaceChange(this.#strings.chatMinimised, null);
13510
+ }
12470
13511
  if (!collapsed) {
12471
13512
  this.#restoreLauncherPosition();
12472
13513
  }
@@ -12494,6 +13535,167 @@ ${quoted}`;
12494
13535
  get unread() {
12495
13536
  return this.#unread;
12496
13537
  }
13538
+ /**
13539
+ * Whether to mount collapsed.
13540
+ *
13541
+ * A stored choice always wins -- in either direction, so a user who opened
13542
+ * the panel finds it open. With nothing stored, the corner placements start
13543
+ * collapsed: they are the two that have a launcher, and a launcher is the
13544
+ * resting state of every corner chat in the field. Mounting open put a
13545
+ * 380x560 panel over the host page's own bottom-right corner on a visitor's
13546
+ * first load, uninvited.
13547
+ *
13548
+ * The placements that place themselves are unchanged. A host that docks a
13549
+ * sidebar has already decided the widget belongs on screen, and one that
13550
+ * embeds it in its own layout has given it a box to fill.
13551
+ *
13552
+ * `data-start-open` restores the previous behaviour for a host that wants
13553
+ * the panel up immediately.
13554
+ */
13555
+ #startsCollapsed() {
13556
+ const stored = this.#readScopedItem(COLLAPSED_KEY);
13557
+ if (stored !== null) {
13558
+ return stored === "1";
13559
+ }
13560
+ return DRAGGABLE_PLACEMENTS.has(this.getAttribute("placement")) && !this.hasAttribute("data-start-open");
13561
+ }
13562
+ /**
13563
+ * Whether this placement has a collapsed state at all.
13564
+ *
13565
+ * `page` does not. It is a dedicated route rather than a panel sitting on
13566
+ * someone else's page, so there is no "away" for it to go to: collapsing it
13567
+ * left a strip of application chrome fixed over a route that no longer had an
13568
+ * owner. It is also the placement that hides the launcher, so the usual way
13569
+ * back does not exist here.
13570
+ *
13571
+ * The header hides its collapse control under this placement, but a control
13572
+ * removed from the UI is not a state removed from the model -- the property,
13573
+ * the attribute and a value restored from storage all still reach it. This is
13574
+ * what the reachable paths are gated on; the stylesheet covers the one path
13575
+ * that never passes through here, an attribute written straight onto the
13576
+ * element.
13577
+ */
13578
+ #collapsible() {
13579
+ return this.getAttribute("placement") !== "page";
13580
+ }
13581
+ /**
13582
+ * Describe the panel to whoever is asking -- in practice the agent, through
13583
+ * the opt-in chat-surface tools.
13584
+ *
13585
+ * `movable` folds two separate reasons into the one answer a caller needs:
13586
+ * a placement that places itself owns its position, and a panel that fills
13587
+ * the screen has nowhere to go. Reporting them apart would make every caller
13588
+ * re-derive the same conjunction.
13589
+ */
13590
+ describeSurface() {
13591
+ const box = this.getBoundingClientRect();
13592
+ const viewport = this.#viewport();
13593
+ const fullBleed = box.width >= viewport.width - 1 && box.height >= viewport.height - 1;
13594
+ return {
13595
+ placement: this.getAttribute("placement"),
13596
+ collapsed: this.collapsed,
13597
+ collapsible: this.#collapsible(),
13598
+ movable: this.#launcherDraggable() && !fullBleed,
13599
+ draggable: this.#launcherDraggable(),
13600
+ fullBleed,
13601
+ box: {
13602
+ left: Math.round(box.left),
13603
+ top: Math.round(box.top),
13604
+ width: Math.round(box.width),
13605
+ height: Math.round(box.height)
13606
+ },
13607
+ viewport: {
13608
+ left: Math.round(viewport.left),
13609
+ top: Math.round(viewport.top),
13610
+ width: Math.round(viewport.width),
13611
+ height: Math.round(viewport.height)
13612
+ }
13613
+ };
13614
+ }
13615
+ /**
13616
+ * Send the panel to a corner, and report whether it went.
13617
+ *
13618
+ * A third claimant on the axes a placement and a user drag already share, and
13619
+ * it takes them the same way the drag does rather than inventing a second
13620
+ * mechanism: the same commit path, so the launcher travels with the panel,
13621
+ * the corner it opens from is re-picked, and switching placement hands
13622
+ * everything back. What it must not do is claim a move it did not make --
13623
+ * hence the boolean, and hence {@link describeSurface} existing so a caller
13624
+ * can ask first.
13625
+ */
13626
+ moveTo(corner, options = {}) {
13627
+ if (!this.#launcherDraggable()) {
13628
+ return false;
13629
+ }
13630
+ const restore = options.announce === true ? this.#captureGeometry() : null;
13631
+ const viewport = this.#viewport();
13632
+ const box = this.getBoundingClientRect();
13633
+ if (box.width >= viewport.width - 1 && box.height >= viewport.height - 1) {
13634
+ return false;
13635
+ }
13636
+ const [edgeY, edgeX] = corner.split("-");
13637
+ const nearX = viewport.left + EDGE_MARGIN;
13638
+ const nearY = viewport.top + EDGE_MARGIN;
13639
+ const left = edgeX === "left" ? nearX : Math.max(nearX, viewport.left + viewport.width - EDGE_MARGIN - box.width);
13640
+ const top = edgeY === "top" ? nearY : Math.max(nearY, viewport.top + viewport.height - EDGE_MARGIN - box.height);
13641
+ const host = { left, top, right: left + box.width, bottom: top + box.height };
13642
+ const launcherWidth = this.#launcher.offsetWidth;
13643
+ const launcherHeight = this.#launcher.offsetHeight;
13644
+ this.#placePanelAndLauncher(host, {
13645
+ left: edgeX === "left" ? host.left : host.right - launcherWidth,
13646
+ top: edgeY === "top" ? host.top : host.bottom - launcherHeight,
13647
+ width: launcherWidth,
13648
+ height: launcherHeight
13649
+ });
13650
+ this.#storeLauncherPosition();
13651
+ if (restore !== null) {
13652
+ this.#announceSurfaceChange(this.#strings.chatMoved, restore);
13653
+ }
13654
+ return true;
13655
+ }
13656
+ /**
13657
+ * Snapshot the panel's stated position, and return a function that puts it
13658
+ * back.
13659
+ *
13660
+ * Both insets and the expand corner, because they are one decision: the
13661
+ * corner is what the panel grows from, so restoring a position without it
13662
+ * puts the box back and animates it out of the wrong side. Absent values are
13663
+ * captured as absent and removed on the way back, rather than written as
13664
+ * empty strings that would outrank the placement.
13665
+ */
13666
+ #captureGeometry() {
13667
+ const inset2 = this.style.getPropertyValue("--ag-ui-inset");
13668
+ const launcherInset = this.style.getPropertyValue("--ag-ui-launcher-inset");
13669
+ const corner = this.getAttribute("data-expand-corner");
13670
+ const launcherPos = this.#launcherPos;
13671
+ const panelPos = this.#panelPos;
13672
+ const expandCorner = this.#expandCorner;
13673
+ return () => {
13674
+ const put = (name, value) => {
13675
+ if (value === "") {
13676
+ this.style.removeProperty(name);
13677
+ } else {
13678
+ this.style.setProperty(name, value);
13679
+ }
13680
+ };
13681
+ put("--ag-ui-inset", inset2);
13682
+ put("--ag-ui-launcher-inset", launcherInset);
13683
+ if (corner === null) {
13684
+ this.removeAttribute("data-expand-corner");
13685
+ } else {
13686
+ this.setAttribute("data-expand-corner", corner);
13687
+ }
13688
+ this.#launcherPos = launcherPos;
13689
+ this.#panelPos = panelPos;
13690
+ this.#expandCorner = expandCorner;
13691
+ if (launcherPos === null) {
13692
+ this.#clearPreference(LAUNCHER_KEY);
13693
+ } else {
13694
+ this.#storeLauncherPosition();
13695
+ }
13696
+ this.#syncResizeAnchor();
13697
+ };
13698
+ }
12497
13699
  /** Flip the collapsed state. Bound to the built-in header toggle. */
12498
13700
  toggleCollapsed() {
12499
13701
  this.setCollapsed(!this.collapsed);
@@ -12507,7 +13709,7 @@ ${quoted}`;
12507
13709
  toggleTheme() {
12508
13710
  const next = this.getAttribute("theme") === "dark" ? "light" : "dark";
12509
13711
  this.setAttribute("theme", next);
12510
- writeStoredItem(this.#storageKey(THEME_KEY), next);
13712
+ this.#writePreference(THEME_KEY, next);
12511
13713
  this.#syncThemeGlyph();
12512
13714
  }
12513
13715
  /**
@@ -12635,9 +13837,81 @@ ${quoted}`;
12635
13837
  #launcherDraggable() {
12636
13838
  return this.getAttribute("data-launcher-drag") !== "false" && DRAGGABLE_PLACEMENTS.has(this.getAttribute("placement"));
12637
13839
  }
12638
- /** The viewport the launcher and the panel both have to fit inside. */
13840
+ /**
13841
+ * The viewport the launcher and the panel both have to fit inside.
13842
+ *
13843
+ * The *visual* viewport, not the layout one, because they come apart exactly
13844
+ * when this matters. An on-screen keyboard shrinks the visual viewport and
13845
+ * leaves the layout viewport alone, so clamping against `innerHeight` parks
13846
+ * the launcher behind the keyboard and decides which corner to open into
13847
+ * using space that is not on the screen. Pinch-zoom does the same on both
13848
+ * axes.
13849
+ *
13850
+ * Falls back where the API is absent, which keeps this working in the
13851
+ * happy-dom project as well as in an old browser.
13852
+ */
12639
13853
  #viewport() {
12640
- return { width: window.innerWidth, height: window.innerHeight };
13854
+ const visual = window.visualViewport;
13855
+ const width = visual?.width ?? window.innerWidth;
13856
+ const height = visual?.height ?? window.innerHeight;
13857
+ const style = getComputedStyle(this.#viewportProbe);
13858
+ const edge = (name) => {
13859
+ const value = Number.parseFloat(style.getPropertyValue(name));
13860
+ return Number.isFinite(value) ? value : 0;
13861
+ };
13862
+ const left = edge("padding-left");
13863
+ const top = edge("padding-top");
13864
+ return {
13865
+ left,
13866
+ top,
13867
+ width: Math.max(0, width - left - edge("padding-right")),
13868
+ height: Math.max(0, height - top - edge("padding-bottom"))
13869
+ };
13870
+ }
13871
+ /**
13872
+ * The whole viewport, before anything the host reserved is taken out of it.
13873
+ *
13874
+ * Distinct from {@link #viewport} on purpose, and the two must not be
13875
+ * swapped. The usable box decides where the widget may rest; this is what a
13876
+ * CSS `inset` on a fixed element is measured from, because that is what the
13877
+ * browser measures it from.
13878
+ */
13879
+ #screen() {
13880
+ const root = document.documentElement;
13881
+ return {
13882
+ width: root.clientWidth || window.innerWidth,
13883
+ height: root.clientHeight || window.innerHeight
13884
+ };
13885
+ }
13886
+ /**
13887
+ * Publish the measured viewport height so the stylesheet can size a
13888
+ * full-bleed panel to what the user can see.
13889
+ *
13890
+ * No CSS length carries this. An on-screen keyboard has no effect on any
13891
+ * viewport-percentage unit, so a panel sized from `100dvh` puts its composer
13892
+ * behind the keyboard being typed into. Written inline, and read through a
13893
+ * token the host's own `--ag-ui-viewport-height` still outranks.
13894
+ *
13895
+ * Removed rather than frozen when the two viewports agree, so a desktop that
13896
+ * never diverges carries no inline override at all and the declared fallback
13897
+ * stays in charge.
13898
+ */
13899
+ #publishVisualViewport() {
13900
+ const visual = window.visualViewport;
13901
+ if (visual === null || visual === void 0) {
13902
+ return;
13903
+ }
13904
+ if (Math.abs(visual.height - window.innerHeight) < 1) {
13905
+ this.style.removeProperty("--ag-ui-visual-viewport-height");
13906
+ this.style.removeProperty("--ag-ui-visual-viewport-inset-bottom");
13907
+ return;
13908
+ }
13909
+ this.style.setProperty("--ag-ui-visual-viewport-height", `${Math.round(visual.height)}px`);
13910
+ const hidden = window.innerHeight - visual.height - visual.offsetTop;
13911
+ this.style.setProperty(
13912
+ "--ag-ui-visual-viewport-inset-bottom",
13913
+ `${Math.max(0, Math.round(hidden))}px`
13914
+ );
12641
13915
  }
12642
13916
  /**
12643
13917
  * The launcher's box in viewport coordinates, with its transform divided out.
@@ -12686,7 +13960,8 @@ ${quoted}`;
12686
13960
  const placement = launcherPlacement(
12687
13961
  this.#launcherBox(),
12688
13962
  { width: panel.width, height: panel.height },
12689
- this.#viewport()
13963
+ this.#viewport(),
13964
+ this.#screen()
12690
13965
  );
12691
13966
  this.style.setProperty("--ag-ui-inset", placement.hostInset);
12692
13967
  this.style.setProperty("--ag-ui-launcher-inset", placement.launcherInset);
@@ -12717,28 +13992,39 @@ ${quoted}`;
12717
13992
  * measured from it, and rewriting one of them from a new corner while the
12718
13993
  * other still names the old one would move the launcher for no reason.
12719
13994
  */
12720
- #movePanel(box) {
13995
+ #movePanel(box, from) {
12721
13996
  if (!this.#launcherDraggable()) {
12722
- return box;
13997
+ return { held: box, launcher: null };
12723
13998
  }
12724
13999
  if (this.#launcherPos === null) {
12725
14000
  const resting = this.#launcherBox();
12726
14001
  this.#launcherPos = { left: resting.left, top: resting.top };
12727
14002
  }
12728
- const held = clampPanel(box, this.#viewport());
14003
+ const start = this.#launcherPos;
14004
+ const held = clampPanel(box, this.#viewport(), SCREEN_EDGE_MARGIN);
12729
14005
  const corner = this.#expandCorner ?? this.#anchor;
12730
- const viewport = this.#viewport();
14006
+ const screen = this.#screen();
12731
14007
  this.style.setProperty(
12732
14008
  "--ag-ui-inset",
12733
14009
  [
12734
14010
  corner.y === "top" ? `${Math.round(held.top)}px` : "auto",
12735
- corner.x === "right" ? `${Math.round(viewport.width - held.right)}px` : "auto",
12736
- corner.y === "bottom" ? `${Math.round(viewport.height - held.bottom)}px` : "auto",
14011
+ corner.x === "right" ? `${Math.round(screen.width - held.right)}px` : "auto",
14012
+ corner.y === "bottom" ? `${Math.round(screen.height - held.bottom)}px` : "auto",
12737
14013
  corner.x === "left" ? `${Math.round(held.left)}px` : "auto"
12738
14014
  ].join(" ")
12739
14015
  );
12740
14016
  this.#panelPos = { left: held.left, top: held.top };
12741
- return held;
14017
+ const carried = {
14018
+ ...this.#launcherBox(),
14019
+ left: start.left + (held.left - from.left),
14020
+ top: start.top + (held.top - from.top)
14021
+ };
14022
+ const launcher = { ...carried, ...clampLauncher(carried, this.#viewport()) };
14023
+ this.style.setProperty(
14024
+ "--ag-ui-launcher-inset",
14025
+ placeWidget(held, launcher, corner, this.#screen()).launcherInset
14026
+ );
14027
+ return { held, launcher };
12742
14028
  }
12743
14029
  /**
12744
14030
  * Finish a header drag: settle where both halves ended up, and remember it.
@@ -12754,20 +14040,11 @@ ${quoted}`;
12754
14040
  * both insets are rewritten from positions that are already decided.
12755
14041
  */
12756
14042
  #commitPanel(box, from) {
12757
- const held = this.#movePanel(box);
12758
- const start = this.#launcherPos;
12759
- if (!this.#launcherDraggable() || start === null) {
14043
+ const { held, launcher } = this.#movePanel(box, from);
14044
+ if (launcher === null) {
12760
14045
  return;
12761
14046
  }
12762
- const carried = {
12763
- ...this.#launcherBox(),
12764
- left: start.left + (held.left - from.left),
12765
- top: start.top + (held.top - from.top)
12766
- };
12767
- this.#placePanelAndLauncher(held, {
12768
- ...carried,
12769
- ...clampLauncher(carried, this.#viewport())
12770
- });
14047
+ this.#placePanelAndLauncher(held, launcher);
12771
14048
  this.#storeLauncherPosition();
12772
14049
  }
12773
14050
  /**
@@ -12776,9 +14053,10 @@ ${quoted}`;
12776
14053
  */
12777
14054
  #placePanelAndLauncher(host, launcher) {
12778
14055
  const viewport = this.#viewport();
14056
+ const screen = this.#screen();
12779
14057
  const size = { width: host.right - host.left, height: host.bottom - host.top };
12780
- const { corner } = launcherPlacement(launcher, size, viewport);
12781
- const insets = placeWidget(host, launcher, corner, viewport);
14058
+ const { corner } = launcherPlacement(launcher, size, viewport, screen);
14059
+ const insets = placeWidget(host, launcher, corner, screen);
12782
14060
  this.style.setProperty("--ag-ui-inset", insets.hostInset);
12783
14061
  this.style.setProperty("--ag-ui-launcher-inset", insets.launcherInset);
12784
14062
  this.#launcherPos = { left: launcher.left, top: launcher.top };
@@ -12802,7 +14080,8 @@ ${quoted}`;
12802
14080
  const rect = this.getBoundingClientRect();
12803
14081
  const held = clampPanel(
12804
14082
  { left: at2.left, top: at2.top, right: at2.left + rect.width, bottom: at2.top + rect.height },
12805
- this.#viewport()
14083
+ this.#viewport(),
14084
+ SCREEN_EDGE_MARGIN
12806
14085
  );
12807
14086
  const launcher = this.#launcherBox();
12808
14087
  const carried = {
@@ -12830,8 +14109,8 @@ ${quoted}`;
12830
14109
  return;
12831
14110
  }
12832
14111
  const panel = this.#panelPos;
12833
- writeStoredItem(
12834
- this.#storageKey(LAUNCHER_KEY),
14112
+ this.#writePreference(
14113
+ LAUNCHER_KEY,
12835
14114
  JSON.stringify(panel === null ? position : { ...position, panel })
12836
14115
  );
12837
14116
  }
@@ -12860,7 +14139,7 @@ ${quoted}`;
12860
14139
  }
12861
14140
  /** The persisted position for this instance, or null. */
12862
14141
  #readLauncherPosition() {
12863
- const raw = this.#readScopedItem(LAUNCHER_KEY);
14142
+ const raw = this.#readPreference(LAUNCHER_KEY);
12864
14143
  if (raw === null) {
12865
14144
  return null;
12866
14145
  }
@@ -12910,19 +14189,20 @@ ${quoted}`;
12910
14189
  * drags the edge it was holding.
12911
14190
  */
12912
14191
  #applyResize(grip, box) {
14192
+ box = this.#withinViewport(box);
12913
14193
  this.#applySize({ width: box.right - box.left, height: box.bottom - box.top });
12914
14194
  if (grip.x !== this.#anchor.x && grip.y !== this.#anchor.y) {
12915
- return;
14195
+ return box;
12916
14196
  }
12917
- const viewport = this.#viewport();
14197
+ const screen = this.#screen();
12918
14198
  const anchor = this.#anchor;
12919
14199
  const side = (value) => `${Math.round(value)}px`;
12920
14200
  this.style.setProperty(
12921
14201
  "--ag-ui-inset",
12922
14202
  [
12923
14203
  anchor.y === "top" ? side(box.top) : "auto",
12924
- anchor.x === "right" ? side(viewport.width - box.right) : "auto",
12925
- anchor.y === "bottom" ? side(viewport.height - box.bottom) : "auto",
14204
+ anchor.x === "right" ? side(screen.width - box.right) : "auto",
14205
+ anchor.y === "bottom" ? side(screen.height - box.bottom) : "auto",
12926
14206
  anchor.x === "left" ? side(box.left) : "auto"
12927
14207
  ].join(" ")
12928
14208
  );
@@ -12936,11 +14216,12 @@ ${quoted}`;
12936
14216
  if (this.#panelPos !== null) {
12937
14217
  this.#panelPos = { left: box.left, top: box.top };
12938
14218
  }
14219
+ return box;
12939
14220
  }
12940
14221
  /** Finish a resize: keep the box, remember it, and re-read the pinned edges. */
12941
14222
  #commitResize(grip, box) {
12942
- this.#applyResize(grip, box);
12943
- this.#persistSize({ width: box.right - box.left, height: box.bottom - box.top });
14223
+ const held = this.#applyResize(grip, box);
14224
+ this.#persistSize({ width: held.right - held.left, height: held.bottom - held.top });
12944
14225
  this.#storeLauncherPosition();
12945
14226
  this.#syncResizeAnchor();
12946
14227
  }
@@ -12967,11 +14248,11 @@ ${quoted}`;
12967
14248
  /** Persist a dragged size per tab, alongside the collapsed/theme preferences. */
12968
14249
  #persistSize(size) {
12969
14250
  const stored = { ...this.#readSize(), ...size };
12970
- writeStoredItem(this.#storageKey(SIZE_KEY), JSON.stringify(stored));
14251
+ this.#writePreference(SIZE_KEY, JSON.stringify(stored));
12971
14252
  }
12972
14253
  /** The persisted size for this instance, or an empty record. */
12973
14254
  #readSize() {
12974
- const raw = this.#readScopedItem(SIZE_KEY);
14255
+ const raw = this.#readPreference(SIZE_KEY);
12975
14256
  if (raw === null) {
12976
14257
  return {};
12977
14258
  }
@@ -13098,10 +14379,81 @@ ${quoted}`;
13098
14379
  }
13099
14380
  return sessionStorage.getItem(base);
13100
14381
  }
14382
+ /**
14383
+ * Read a layout preference: where the widget sits, how big it is, which
14384
+ * theme it wears.
14385
+ *
14386
+ * These live in `localStorage` rather than beside the transcript, because a
14387
+ * layout preference is not a conversation. The transcript is deliberately
14388
+ * per-tab -- two tabs are two conversations, and closing the tab ends it --
14389
+ * and everything else inherited that scoping without earning it. A user who
14390
+ * dragged the panel clear of their own UI did it again in the next tab, and
14391
+ * again after every restart.
14392
+ *
14393
+ * Whether the widget is *currently open* stays per-tab with the transcript.
14394
+ * It is a statement about this tab rather than a preference: carrying it
14395
+ * across would pop the panel open on every new tab because it was opened
14396
+ * once, somewhere else.
14397
+ *
14398
+ * Falls back to the session value it used to be written to, so an existing
14399
+ * position survives the upgrade rather than resetting once.
14400
+ */
14401
+ #readPreference(base) {
14402
+ try {
14403
+ const stored = localStorage.getItem(this.#storageKey(base));
14404
+ if (stored !== null) {
14405
+ return stored;
14406
+ }
14407
+ } catch {
14408
+ }
14409
+ return this.#readScopedItem(base);
14410
+ }
14411
+ /**
14412
+ * Persist a layout preference as durably as this browser allows: to
14413
+ * `localStorage` so it outlives the tab, and to the per-tab store as well.
14414
+ *
14415
+ * The second write is not redundancy for its own sake. A privacy mode can
14416
+ * deny `localStorage` while allowing `sessionStorage`, and losing the
14417
+ * durable copy should degrade to the per-tab behaviour this replaced rather
14418
+ * than to no persistence at all. The read above prefers the durable copy, so
14419
+ * a tab that has both cannot be shadowed by its own stale one.
14420
+ *
14421
+ * Neither write is worth an exception. Losing where the panel sat is not
14422
+ * worth a warning either -- unlike the transcript, which says so once,
14423
+ * because losing that loses the conversation on the next reload.
14424
+ */
14425
+ #writePreference(base, value) {
14426
+ const key = this.#storageKey(base);
14427
+ try {
14428
+ localStorage.setItem(key, value);
14429
+ } catch {
14430
+ }
14431
+ writeStoredItem(key, value);
14432
+ }
14433
+ /**
14434
+ * Drop a layout preference from both stores.
14435
+ *
14436
+ * The mirror of {@link AgUiChat.#writePreference}, and it has to clear both
14437
+ * for the same reason that writes both: leaving either copy behind means the
14438
+ * value comes back on the next read.
14439
+ */
14440
+ #clearPreference(base) {
14441
+ const key = this.#storageKey(base);
14442
+ try {
14443
+ localStorage.removeItem(key);
14444
+ } catch {
14445
+ }
14446
+ try {
14447
+ sessionStorage.removeItem(key);
14448
+ } catch {
14449
+ }
14450
+ }
13101
14451
  /** Reflect the current theme on the toggle: show the destination's glyph. */
13102
14452
  #syncThemeGlyph() {
13103
14453
  const dark = this.getAttribute("theme") === "dark";
13104
- this.#themeToggle.textContent = dark ? "\u2600\uFE0F" : "\u{1F319}";
14454
+ this.#themeToggle.replaceChildren(
14455
+ this.#iconElement("theme", "theme-icon", dark ? ICON_SUN : ICON_MOON, null)
14456
+ );
13105
14457
  }
13106
14458
  /**
13107
14459
  * Open the thread-history drawer: the imperative route to the control that
@@ -13115,7 +14467,37 @@ ${quoted}`;
13115
14467
  openThreads() {
13116
14468
  this.#checkpoints.close();
13117
14469
  void this.#refreshDrawer();
14470
+ this.#drawer.setModal(!this.#threadsDock());
13118
14471
  this.#drawer.open();
14472
+ this.#syncThreadsState();
14473
+ }
14474
+ /**
14475
+ * Whether the conversation list docks beside the transcript rather than
14476
+ * covering it.
14477
+ *
14478
+ * Only the full-page placement, and only where there is room. A dedicated
14479
+ * route is the one surface with width to spare -- everywhere else the panel
14480
+ * is a few hundred pixels wide, and a list docked into that leaves a column
14481
+ * of transcript too narrow to read. The width is the panel's own rather than
14482
+ * the window's, because an embedded host can give a full-page-sized box to
14483
+ * something that is not a page.
14484
+ */
14485
+ #threadsDock() {
14486
+ return this.getAttribute("placement") === "page" && this.getBoundingClientRect().width >= THREADS_DOCK_MIN_WIDTH;
14487
+ }
14488
+ /**
14489
+ * Stamp whether the list is showing, and how.
14490
+ *
14491
+ * On the host rather than inside the shell because the transcript has to move
14492
+ * over for a docked list, and the drawer is the last child of the panel -- CSS
14493
+ * cannot select backwards from it to the rows it needs to shift.
14494
+ */
14495
+ #syncThreadsState() {
14496
+ if (this.#drawer.isOpen() && this.#threadsDock()) {
14497
+ this.setAttribute("data-threads-docked", "");
14498
+ } else {
14499
+ this.removeAttribute("data-threads-docked");
14500
+ }
13119
14501
  }
13120
14502
  /**
13121
14503
  * Open the checkpoints panel (the `::part(checkpoints-button)` route).
@@ -13129,6 +14511,16 @@ ${quoted}`;
13129
14511
  void this.#refreshCheckpoints();
13130
14512
  this.#checkpoints.open();
13131
14513
  }
14514
+ /**
14515
+ * Close the conversation list, if it is open.
14516
+ *
14517
+ * No state sync here: the drawer reports every close through `onVisibility`,
14518
+ * including the four that never reach this method, and doing it twice would
14519
+ * be a second place to keep right.
14520
+ */
14521
+ closeThreads() {
14522
+ this.#drawer.close();
14523
+ }
13132
14524
  /** Close the checkpoints panel, if it is open. */
13133
14525
  closeCheckpoints() {
13134
14526
  this.#checkpoints.close();
@@ -13169,6 +14561,8 @@ ${quoted}`;
13169
14561
  this.#client = null;
13170
14562
  this.#clearTranscript();
13171
14563
  this.#initialMessages = [];
14564
+ this.#sentDrafts.length = 0;
14565
+ this.#recallIndex = null;
13172
14566
  }
13173
14567
  /**
13174
14568
  * Wipe the rendered transcript and everything that indexes into it.
@@ -13505,7 +14899,7 @@ ${quoted}`;
13505
14899
  enablePanelDrag(header, {
13506
14900
  enabled: () => !this.collapsed && this.#launcherDraggable(),
13507
14901
  rect: () => this.getBoundingClientRect(),
13508
- apply: (box) => this.#movePanel(box),
14902
+ apply: (box, from) => this.#movePanel(box, from),
13509
14903
  commit: (box, from) => this.#commitPanel(box, from)
13510
14904
  });
13511
14905
  this.#messages.className = "messages";
@@ -13550,7 +14944,16 @@ ${quoted}`;
13550
14944
  this.#emptyWrap.setAttribute("part", "empty");
13551
14945
  const emptySlot = document.createElement("slot");
13552
14946
  emptySlot.name = "empty";
14947
+ const starters = this.#starterChips();
14948
+ if (starters !== null) {
14949
+ emptySlot.append(starters);
14950
+ }
13553
14951
  this.#emptyWrap.append(emptySlot);
14952
+ this.#queuedRow.className = "queued";
14953
+ this.#queuedRow.setAttribute("part", "queued");
14954
+ this.#queuedRow.setAttribute("role", "group");
14955
+ this.#queuedRow.setAttribute("aria-label", this.#strings.queued);
14956
+ this.#queuedRow.hidden = true;
13554
14957
  this.#messages.append(this.#emptyWrap);
13555
14958
  const inputRow = document.createElement("div");
13556
14959
  inputRow.className = "input-row";
@@ -13616,6 +15019,7 @@ ${quoted}`;
13616
15019
  this.#skillsMenu.palette,
13617
15020
  this.#skillsMenu.chips,
13618
15021
  this.#skillHint,
15022
+ this.#queuedRow,
13619
15023
  this.#attachSlot,
13620
15024
  inputRow,
13621
15025
  footer,
@@ -13640,8 +15044,13 @@ ${quoted}`;
13640
15044
  this.#badge.setAttribute("part", "launcher-badge");
13641
15045
  this.#badge.setAttribute("aria-hidden", "true");
13642
15046
  this.#badge.hidden = true;
15047
+ this.#railLabel.className = "rail-label";
15048
+ this.#railLabel.setAttribute("part", "rail-label");
15049
+ this.#railLabel.setAttribute("aria-hidden", "true");
15050
+ this.#railLabel.textContent = this.getAttribute("title-text") ?? this.#strings.title;
13643
15051
  this.#launcher.append(
13644
15052
  this.#iconElement("launcher", "launcher-icon", ICON_LAUNCHER, this.#launcherIconUrl()),
15053
+ this.#railLabel,
13645
15054
  this.#badge
13646
15055
  );
13647
15056
  this.#launcher.addEventListener("click", () => this.setCollapsed(false));
@@ -13667,7 +15076,9 @@ ${quoted}`;
13667
15076
  }
13668
15077
  this.#focusableGrip();
13669
15078
  this.#adoptStyles();
13670
- this.#root.append(this.#announcer, this.#chat, this.#launcher);
15079
+ this.#viewportProbe.className = "viewport-probe";
15080
+ this.#viewportProbe.setAttribute("aria-hidden", "true");
15081
+ this.#root.append(this.#viewportProbe, this.#announcer, this.#chat, this.#launcher);
13671
15082
  }
13672
15083
  /**
13673
15084
  * Attach the stylesheet without an inline `<style>` element.
@@ -13846,6 +15257,37 @@ ${quoted}`;
13846
15257
  this.#setUnread(this.#unread + 1);
13847
15258
  }
13848
15259
  /** Hide the empty-state region once the message list holds anything else. */
15260
+ /**
15261
+ * The prompts offered on an empty transcript, from `data-starters`.
15262
+ *
15263
+ * Different from the suggestion chips a run pushes, which are follow-ups to
15264
+ * something already said. These answer the blank-page question instead, and
15265
+ * they are the host's rather than the model's -- only the host knows what its
15266
+ * page is for. Shares the renderer, the count and the length limit, because
15267
+ * two rows of prompt chips that behaved differently would be the harder
15268
+ * thing to explain.
15269
+ *
15270
+ * Read once at connect: it is content for a state the widget is in before
15271
+ * anything happens, and a host that wants it to change has `slot="empty"`.
15272
+ */
15273
+ #starterChips() {
15274
+ const raw = this.getAttribute("data-starters");
15275
+ if (raw === null) {
15276
+ return null;
15277
+ }
15278
+ let parsed;
15279
+ try {
15280
+ parsed = JSON.parse(raw);
15281
+ } catch {
15282
+ console.warn(
15283
+ `<ag-ui-chat>: data-starters is not valid JSON, so no starters are shown. It takes an array of strings, e.g. data-starters='["Summarise this page"]'.`
15284
+ );
15285
+ return null;
15286
+ }
15287
+ return renderSuggestionChips({ prompts: parsed }, this.#strings, (prompt) => {
15288
+ void this.sendMessage(prompt);
15289
+ });
15290
+ }
13849
15291
  #updateEmptyState() {
13850
15292
  this.#emptyWrap.hidden = this.#messages.childElementCount > 1;
13851
15293
  }
@@ -13854,6 +15296,7 @@ ${quoted}`;
13854
15296
  this.#skillsMenu.onInput(this.#input.value);
13855
15297
  this.#skillHint.hidden = true;
13856
15298
  this.#autoGrow();
15299
+ this.#recallIndex = null;
13857
15300
  }
13858
15301
  #onKeydown(event) {
13859
15302
  if (this.#skillsMenu.onKeydown(event)) {
@@ -13868,7 +15311,44 @@ ${quoted}`;
13868
15311
  if (event.key === "Enter" && !event.shiftKey) {
13869
15312
  event.preventDefault();
13870
15313
  void this.#submit();
15314
+ return;
15315
+ }
15316
+ this.#recallHistory(event);
15317
+ }
15318
+ /**
15319
+ * Walk back through what the user has already sent, on the arrow keys.
15320
+ *
15321
+ * Only from an empty composer, and only with the palette closed -- which the
15322
+ * caller has already established, since the palette consumes arrows while it
15323
+ * is open. Both conditions matter: arrows inside text are how you move the
15324
+ * caret, and taking them would break editing to add a shortcut.
15325
+ *
15326
+ * The drafts are the user's own turns in this conversation, newest first,
15327
+ * which is what every shell and every coding agent means by this. Arrowing
15328
+ * forward past the newest empties the composer again rather than sticking on
15329
+ * it, so the way out is the same key that got you in.
15330
+ */
15331
+ #recallHistory(event) {
15332
+ const back = event.key === "ArrowUp";
15333
+ if (!back && event.key !== "ArrowDown" || this.#skillsMenu.isOpen()) {
15334
+ return;
15335
+ }
15336
+ const drafts = this.#sentDrafts;
15337
+ if (drafts.length === 0) {
15338
+ return;
15339
+ }
15340
+ if (this.#recallIndex === null && (!back || this.#input.value !== "")) {
15341
+ return;
15342
+ }
15343
+ const next = this.#recallIndex === null ? 0 : this.#recallIndex + (back ? 1 : -1);
15344
+ if (next >= drafts.length) {
15345
+ return;
13871
15346
  }
15347
+ event.preventDefault();
15348
+ this.#recallIndex = next < 0 ? null : next;
15349
+ this.#input.value = next < 0 ? "" : drafts[next];
15350
+ this.#input.setSelectionRange(this.#input.value.length, this.#input.value.length);
15351
+ this.#autoGrow();
13872
15352
  }
13873
15353
  /**
13874
15354
  * Stop the in-flight run: decline any confirmation card awaiting a decision
@@ -13877,6 +15357,13 @@ ${quoted}`;
13877
15357
  * observes the disconnect).
13878
15358
  */
13879
15359
  #cancelRun() {
15360
+ for (const text4 of this.#queued) {
15361
+ if (this.#sentDrafts[0] !== text4) {
15362
+ this.#sentDrafts.unshift(text4);
15363
+ }
15364
+ }
15365
+ this.#queued.length = 0;
15366
+ this.#renderQueued();
13880
15367
  this.#confirmAbort?.abort();
13881
15368
  this.#client?.cancel();
13882
15369
  }
@@ -13888,11 +15375,54 @@ ${quoted}`;
13888
15375
  * that it carries no text.
13889
15376
  */
13890
15377
  #setRunning(running) {
15378
+ const settled = this.#running && !running;
13891
15379
  this.#running = running;
13892
15380
  const label = running ? this.#strings.stop : this.#strings.send;
13893
15381
  this.#send.title = label;
13894
15382
  this.#send.setAttribute("aria-label", label);
13895
15383
  this.#send.dataset["state"] = running ? "running" : "idle";
15384
+ if (settled) {
15385
+ this.#flushQueued();
15386
+ }
15387
+ }
15388
+ /**
15389
+ * Send the next message that was typed while the run was going.
15390
+ *
15391
+ * One at a time, through the same path as anything else: each queued turn
15392
+ * starts a run of its own, and the next is sent when *that* one settles. Any
15393
+ * other shape would be a second sender racing the guard above.
15394
+ */
15395
+ #flushQueued() {
15396
+ const next = this.#queued.shift();
15397
+ this.#renderQueued();
15398
+ if (next !== void 0) {
15399
+ void this.sendMessage(next);
15400
+ }
15401
+ }
15402
+ /**
15403
+ * Draw what is waiting, as chips that can be taken back.
15404
+ *
15405
+ * Visible and removable, because a message the user typed and cannot see is
15406
+ * a message they will type again -- and one they changed their mind about
15407
+ * has to be retractable before it is sent on their behalf.
15408
+ */
15409
+ #renderQueued() {
15410
+ this.#queuedRow.replaceChildren();
15411
+ this.#queuedRow.hidden = this.#queued.length === 0;
15412
+ for (const [index, text4] of this.#queued.entries()) {
15413
+ const chip = document.createElement("button");
15414
+ chip.type = "button";
15415
+ chip.className = "queued-chip";
15416
+ chip.setAttribute("part", "queued-chip");
15417
+ chip.textContent = text4;
15418
+ chip.title = this.#strings.removeQueued.replace("{text}", text4);
15419
+ chip.setAttribute("aria-label", chip.title);
15420
+ chip.addEventListener("click", () => {
15421
+ this.#queued.splice(index, 1);
15422
+ this.#renderQueued();
15423
+ });
15424
+ this.#queuedRow.appendChild(chip);
15425
+ }
13896
15426
  }
13897
15427
  /**
13898
15428
  * Size the field to its content: one row when empty, growing with what is
@@ -13907,14 +15437,24 @@ ${quoted}`;
13907
15437
  this.#input.style.height = `${this.#input.scrollHeight}px`;
13908
15438
  }
13909
15439
  async #submit() {
13910
- if (this.#running) {
13911
- return;
13912
- }
13913
15440
  const content = this.#input.value.trim();
13914
15441
  const attachments = this.#attachTray?.readyRefs() ?? [];
13915
15442
  if (content === "" && attachments.length === 0) {
13916
15443
  return;
13917
15444
  }
15445
+ if (this.#running) {
15446
+ if (content !== "") {
15447
+ this.#queued.push(content);
15448
+ this.#renderQueued();
15449
+ this.#input.value = "";
15450
+ this.#autoGrow();
15451
+ }
15452
+ return;
15453
+ }
15454
+ if (content !== "" && this.#sentDrafts[0] !== content) {
15455
+ this.#sentDrafts.unshift(content);
15456
+ }
15457
+ this.#recallIndex = null;
13918
15458
  this.#input.value = "";
13919
15459
  this.#autoGrow();
13920
15460
  if (this.#attachTray?.hasPending() === true) {
@@ -14700,11 +16240,33 @@ ${quoted}`;
14700
16240
  this.#appendNotice("\u2728", this.#strings.usingSkill.replace("{name}", skill), "skill");
14701
16241
  return true;
14702
16242
  }
14703
- #appendNotice(icon, text4, kind) {
14704
- this.#ensureGroup().appendChild(renderRunNotice(icon, text4, kind));
16243
+ /**
16244
+ * An inline notice about something the run did.
16245
+ *
16246
+ * `undo` is offered only where the agent rearranged the user's own window --
16247
+ * see {@link renderRunNotice} for why a notice may carry that one control and
16248
+ * nothing else.
16249
+ */
16250
+ #appendNotice(icon, text4, kind, undo) {
16251
+ this.#ensureGroup().appendChild(renderRunNotice(icon, text4, kind, undo));
14705
16252
  this.#updateEmptyState();
14706
16253
  this.#scroller.follow();
14707
16254
  }
16255
+ /**
16256
+ * Say that the agent rearranged the user's window, and offer the way back.
16257
+ *
16258
+ * Only on the agent's path. A host calling {@link moveTo} is arranging its
16259
+ * own page and does not need telling what it just did; an agent doing it
16260
+ * mid-conversation is the case where a panel appears to move on its own.
16261
+ */
16262
+ #announceSurfaceChange(text4, undo) {
16263
+ this.#appendNotice(
16264
+ "\u2922",
16265
+ text4,
16266
+ "surface",
16267
+ undo === null ? void 0 : { label: this.#strings.undo, onActivate: undo }
16268
+ );
16269
+ }
14708
16270
  /**
14709
16271
  * Turn on chart rendering, by whichever route this consumer wants.
14710
16272
  *
@@ -14977,13 +16539,14 @@ function setControlValue(el2, value) {
14977
16539
  }
14978
16540
 
14979
16541
  // src/version.ts
14980
- var VERSION = "0.34.0";
16542
+ var VERSION = "0.35.1";
14981
16543
  export {
14982
16544
  ATTACHMENT_EVENT,
14983
16545
  AgUiChat,
14984
16546
  AgUiClient,
14985
16547
  CHART_ACTIVITY_TYPE,
14986
16548
  CHART_TOOL_NAME,
16549
+ CHAT_CORNERS,
14987
16550
  COMPACTION_ACTIVITY_TYPE,
14988
16551
  CUSTOM_AGENT_EVENT,
14989
16552
  CheckpointMenu,
@@ -15024,6 +16587,7 @@ export {
15024
16587
  attachQuoteOffer,
15025
16588
  chartSpecFrom,
15026
16589
  clickElement,
16590
+ createChatSurfaceTools,
15027
16591
  createHttpAgent,
15028
16592
  createPageActionTools,
15029
16593
  createPageMapContext,
@@ -15035,6 +16599,7 @@ export {
15035
16599
  flash,
15036
16600
  focusWithFlash,
15037
16601
  highlightThenClick,
16602
+ isChatCorner,
15038
16603
  isDestructive,
15039
16604
  isNavigates,
15040
16605
  mergeUiStrings,
@@ -15059,6 +16624,7 @@ export {
15059
16624
  setControlValue,
15060
16625
  setNativeChecked,
15061
16626
  setNativeValue,
16627
+ showHighlightOverlay,
15062
16628
  suggestionPrompts,
15063
16629
  toggleCheckbox,
15064
16630
  toggleControl,