@artooi/ag-ui-web-component 0.33.1 → 0.35.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/CHANGELOG.md +563 -1
  2. package/README.md +279 -16
  3. package/dist/ag-ui-web-component.bundle.js +676 -101
  4. package/dist/ag-ui-web-component.bundle.js.map +4 -4
  5. package/dist/constants.d.ts +80 -0
  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 +2288 -388
  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/chart_block.d.ts +18 -0
  22. package/dist/ui/chart_block.d.ts.map +1 -1
  23. package/dist/ui/clamp_launcher.d.ts +10 -5
  24. package/dist/ui/clamp_launcher.d.ts.map +1 -1
  25. package/dist/ui/clamp_panel.d.ts +13 -0
  26. package/dist/ui/clamp_panel.d.ts.map +1 -0
  27. package/dist/ui/launcher_drag.d.ts +2 -2
  28. package/dist/ui/launcher_drag.d.ts.map +1 -1
  29. package/dist/ui/launcher_placement.d.ts +14 -1
  30. package/dist/ui/launcher_placement.d.ts.map +1 -1
  31. package/dist/ui/panel_drag.d.ts +40 -0
  32. package/dist/ui/panel_drag.d.ts.map +1 -0
  33. package/dist/ui/place_widget.d.ts +31 -0
  34. package/dist/ui/place_widget.d.ts.map +1 -0
  35. package/dist/ui/run_notice.d.ts +14 -3
  36. package/dist/ui/run_notice.d.ts.map +1 -1
  37. package/dist/ui/styles.d.ts +1 -1
  38. package/dist/ui/styles.d.ts.map +1 -1
  39. package/dist/ui/thread_drawer.d.ts +21 -0
  40. package/dist/ui/thread_drawer.d.ts.map +1 -1
  41. package/dist/ui/ui_strings.d.ts +16 -0
  42. package/dist/ui/ui_strings.d.ts.map +1 -1
  43. package/package.json +1 -1
  44. package/src/constants.ts +87 -0
  45. package/src/core/ag_ui_chat.ts +1146 -38
  46. package/src/dom/animations.ts +30 -0
  47. package/src/dom/highlight_overlay.ts +256 -0
  48. package/src/index.ts +12 -0
  49. package/src/tools/chat_surface_tools.ts +207 -0
  50. package/src/tools/page_action_tools.ts +2 -0
  51. package/src/ui/chart_block.ts +222 -57
  52. package/src/ui/clamp_launcher.ts +25 -7
  53. package/src/ui/clamp_panel.ts +30 -0
  54. package/src/ui/launcher_drag.ts +11 -2
  55. package/src/ui/launcher_placement.ts +50 -60
  56. package/src/ui/panel_drag.ts +138 -0
  57. package/src/ui/place_widget.ts +64 -0
  58. package/src/ui/run_notice.ts +32 -3
  59. package/src/ui/styles.ts +622 -47
  60. package/src/ui/thread_drawer.ts +138 -8
  61. package/src/ui/ui_strings.ts +24 -0
  62. 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>`;
@@ -69,7 +71,12 @@ var ICON_FILE_IMAGE = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"
69
71
  var ICON_FILE_PDF = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/><rect class="glyph--solid" x="7.5" y="13.5" width="9" height="4.5" rx="1"/></svg>`;
70
72
  var ICON_FILE_TEXT = `<svg class="glyph" viewBox="0 0 24 24" aria-hidden="true"><path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8z"/><path d="M14 3v5h5"/><path d="M8.5 13.5h7M8.5 17h4.5"/></svg>`;
71
73
  var CHART_ACTIVITY_TYPE = "chart";
74
+ var EDGE_MARGIN = 24;
72
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;
73
80
 
74
81
  // src/core/ag_ui_chat.ts
75
82
  import { randomUUID as randomUUID5 } from "@ag-ui/client";
@@ -106,6 +113,95 @@ function parseSkills(value) {
106
113
  return value.filter(isSkill);
107
114
  }
108
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
+
109
205
  // src/tools/client_tool_registry.ts
110
206
  var ClientToolRegistry = class {
111
207
  #tools = /* @__PURE__ */ new Map();
@@ -149,6 +245,133 @@ function isNavigates(parameters) {
149
245
  return parameters[X_NAVIGATES_KEY] === true;
150
246
  }
151
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
+
152
375
  // src/dom/native_setter.ts
153
376
  function prototypeSetter(proto, prop) {
154
377
  const setter = Object.getOwnPropertyDescriptor(proto, prop).set;
@@ -172,7 +395,7 @@ function setNativeChecked(el2, checked) {
172
395
  }
173
396
 
174
397
  // src/dom/animations.ts
175
- var ACCENT = "#4f46e5";
398
+ var ACCENT2 = "#4f46e5";
176
399
  var ACCENT_HALO = "rgba(79, 70, 229, 0.4)";
177
400
  var ACCENT_PROPERTY = "--ag-ui-accent";
178
401
  function delay(ms) {
@@ -213,7 +436,7 @@ async function highlightThenClick(el2, options = {}) {
213
436
  const highlightMs = options.highlightMs ?? 280;
214
437
  const previousOutline = el2.style.outline;
215
438
  const previousOffset = el2.style.outlineOffset;
216
- el2.style.outline = `2px solid ${accentColor(el2, ACCENT)}`;
439
+ el2.style.outline = `2px solid ${accentColor(el2, ACCENT2)}`;
217
440
  el2.style.outlineOffset = "2px";
218
441
  await delay(highlightMs);
219
442
  el2.style.outline = previousOutline;
@@ -260,10 +483,21 @@ async function flashRing(el2, options, focusByDefault) {
260
483
  if (flashMs <= 0) {
261
484
  return;
262
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
+ }
263
497
  const previousOutline = el2.style.outline;
264
498
  const previousOffset = el2.style.outlineOffset;
265
499
  const previousTransition = el2.style.transition;
266
- const color = options.color ?? accentColor(el2, ACCENT);
500
+ const color = options.color ?? accentColor(el2, ACCENT2);
267
501
  el2.style.outline = `3px solid ${color}`;
268
502
  el2.style.outlineOffset = "2px";
269
503
  const fadeMs = prefersReducedMotion() ? 0 : Math.round(flashMs * FLASH_FADE_RATIO);
@@ -313,7 +547,7 @@ async function selectOption(el2, value, options = {}) {
313
547
  const highlightMs = options.highlightMs ?? 220;
314
548
  const previousOutline = el2.style.outline;
315
549
  const previousOffset = el2.style.outlineOffset;
316
- el2.style.outline = `2px solid ${accentColor(el2, ACCENT)}`;
550
+ el2.style.outline = `2px solid ${accentColor(el2, ACCENT2)}`;
317
551
  el2.style.outlineOffset = "2px";
318
552
  await motionDelay(highlightMs);
319
553
  setNativeValue(el2, option.value);
@@ -336,7 +570,9 @@ async function toggleControl(el2, checked, options = {}) {
336
570
  // src/tools/page_action_tools.ts
337
571
  var PAGE_ACTIONS = {
338
572
  SCROLL: "scroll",
339
- DRAG: "drag"
573
+ DRAG: "drag",
574
+ /** The widget's own position: see `createChatSurfaceTools`. */
575
+ CHAT: "chat"
340
576
  };
341
577
  function createPageActionTools(enabled, resolveTarget) {
342
578
  const tools = [];
@@ -546,6 +782,14 @@ function createRouteTools(getRouteMap, getNavigate) {
546
782
  var DEFAULT_UI_STRINGS = {
547
783
  title: "Assistant",
548
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",
549
793
  newChat: "New chat",
550
794
  collapse: "Collapse",
551
795
  expand: "Expand",
@@ -1093,11 +1337,14 @@ function accepts(accept, file) {
1093
1337
 
1094
1338
  // src/ui/chart_block.ts
1095
1339
  var SVG_NS = "http://www.w3.org/2000/svg";
1096
- var WIDTH = 480;
1097
- var HEIGHT = 220;
1098
1340
  var PAD = { top: 20, right: 12, bottom: 30, left: 44 };
1099
- var PLOT_W = WIDTH - PAD.left - PAD.right;
1100
- var PLOT_H = HEIGHT - PAD.top - PAD.bottom;
1341
+ var DEFAULT_WIDTH = 480;
1342
+ var MIN_WIDTH = 220;
1343
+ var HEIGHT_RATIO = 220 / 480;
1344
+ var MIN_HEIGHT = 160;
1345
+ var MAX_HEIGHT = 320;
1346
+ var WIDTH_STEP = 8;
1347
+ var AXIS_CHAR_WIDTH = 5.6;
1101
1348
  var SERIES_COLORS = [
1102
1349
  "var(--ag-ui-chart-1, #4f7cff)",
1103
1350
  "var(--ag-ui-chart-2, #21b573)",
@@ -1109,6 +1356,16 @@ var SERIES_COLORS = [
1109
1356
  function seriesColor(index) {
1110
1357
  return SERIES_COLORS[index % SERIES_COLORS.length];
1111
1358
  }
1359
+ function geometryFor(width) {
1360
+ const w = Math.max(MIN_WIDTH, width);
1361
+ const h = Math.min(MAX_HEIGHT, Math.max(MIN_HEIGHT, Math.round(w * HEIGHT_RATIO)));
1362
+ return {
1363
+ width: w,
1364
+ height: h,
1365
+ plotW: w - PAD.left - PAD.right,
1366
+ plotH: h - PAD.top - PAD.bottom
1367
+ };
1368
+ }
1112
1369
  function el(name, attrs) {
1113
1370
  const node = document.createElementNS(SVG_NS, name);
1114
1371
  for (const [key, value] of Object.entries(attrs)) {
@@ -1143,21 +1400,26 @@ function extent(spec) {
1143
1400
  const min = Math.min(0, ...values);
1144
1401
  return max === min ? { min, max: max + 1 } : { min, max };
1145
1402
  }
1146
- function scaleY(value, min, max) {
1147
- return PAD.top + PLOT_H - (value - min) / (max - min) * PLOT_H;
1403
+ function scaleY(value, min, max, geo) {
1404
+ return PAD.top + geo.plotH - (value - min) / (max - min) * geo.plotH;
1148
1405
  }
1149
- function bandCentre(index, count) {
1150
- const step = PLOT_W / count;
1406
+ function bandCentre(index, count, geo) {
1407
+ const step = geo.plotW / count;
1151
1408
  return PAD.left + step * index + step / 2;
1152
1409
  }
1153
- function drawAxes(svg2, spec, min, max) {
1410
+ function labelStride(labels, geo) {
1411
+ const band = geo.plotW / labels.length;
1412
+ const widest = Math.max(...labels.map((label) => label.length)) * AXIS_CHAR_WIDTH;
1413
+ return Math.max(1, Math.ceil(widest / band));
1414
+ }
1415
+ function drawAxes(svg2, spec, geo, min, max) {
1154
1416
  for (const value of [min, max]) {
1155
- const y2 = scaleY(value, min, max);
1417
+ const y2 = scaleY(value, min, max, geo);
1156
1418
  svg2.appendChild(
1157
1419
  el("line", {
1158
1420
  x1: PAD.left,
1159
1421
  y1: y2,
1160
- x2: WIDTH - PAD.right,
1422
+ x2: geo.width - PAD.right,
1161
1423
  y2,
1162
1424
  stroke: "currentColor",
1163
1425
  "stroke-opacity": value === min ? 0.35 : 0.12
@@ -1167,23 +1429,27 @@ function drawAxes(svg2, spec, min, max) {
1167
1429
  text(String(Math.round(value)), { x: PAD.left - 6, y: y2 + 4, "text-anchor": "end" })
1168
1430
  );
1169
1431
  }
1432
+ const stride = labelStride(spec.labels, geo);
1170
1433
  spec.labels.forEach((label, i) => {
1434
+ if (i % stride !== 0) {
1435
+ return;
1436
+ }
1171
1437
  svg2.appendChild(
1172
1438
  text(label, {
1173
- x: bandCentre(i, spec.labels.length),
1174
- y: HEIGHT - PAD.bottom + 16,
1439
+ x: bandCentre(i, spec.labels.length, geo),
1440
+ y: geo.height - PAD.bottom + 16,
1175
1441
  "text-anchor": "middle"
1176
1442
  })
1177
1443
  );
1178
1444
  });
1179
1445
  }
1180
- function drawBars(svg2, spec, min, max) {
1181
- const step = PLOT_W / spec.labels.length;
1446
+ function drawBars(svg2, spec, geo, min, max) {
1447
+ const step = geo.plotW / spec.labels.length;
1182
1448
  const width = step * 0.7 / spec.series.length;
1183
- const base = scaleY(min, min, max);
1449
+ const base = scaleY(min, min, max, geo);
1184
1450
  spec.series.forEach((series, s) => {
1185
1451
  series.points.forEach((value, i) => {
1186
- const y2 = scaleY(value, min, max);
1452
+ const y2 = scaleY(value, min, max, geo);
1187
1453
  svg2.appendChild(
1188
1454
  el("rect", {
1189
1455
  x: PAD.left + step * i + step * 0.15 + width * s,
@@ -1197,8 +1463,8 @@ function drawBars(svg2, spec, min, max) {
1197
1463
  });
1198
1464
  });
1199
1465
  }
1200
- function drawStacked(svg2, spec, min, max) {
1201
- const step = PLOT_W / spec.labels.length;
1466
+ function drawStacked(svg2, spec, geo, min, max) {
1467
+ const step = geo.plotW / spec.labels.length;
1202
1468
  const width = step * 0.7;
1203
1469
  const running = spec.labels.map(() => 0);
1204
1470
  spec.series.forEach((series, s) => {
@@ -1206,22 +1472,24 @@ function drawStacked(svg2, spec, min, max) {
1206
1472
  const from = running[i] ?? 0;
1207
1473
  const to = from + value;
1208
1474
  running[i] = to;
1209
- const y2 = scaleY(to, min, max);
1475
+ const y2 = scaleY(to, min, max, geo);
1210
1476
  svg2.appendChild(
1211
1477
  el("rect", {
1212
1478
  x: PAD.left + step * i + step * 0.15,
1213
1479
  y: y2,
1214
1480
  width,
1215
- height: Math.max(1, scaleY(from, min, max) - y2),
1481
+ height: Math.max(1, scaleY(from, min, max, geo) - y2),
1216
1482
  fill: seriesColor(s)
1217
1483
  })
1218
1484
  );
1219
1485
  });
1220
1486
  });
1221
1487
  }
1222
- function drawLines(svg2, spec, min, max) {
1488
+ function drawLines(svg2, spec, geo, min, max) {
1223
1489
  spec.series.forEach((series, s) => {
1224
- const points = series.points.map((value, i) => `${bandCentre(i, spec.labels.length)},${scaleY(value, min, max)}`).join(" ");
1490
+ const points = series.points.map(
1491
+ (value, i) => `${bandCentre(i, spec.labels.length, geo)},${scaleY(value, min, max, geo)}`
1492
+ ).join(" ");
1225
1493
  svg2.appendChild(
1226
1494
  el("polyline", {
1227
1495
  points,
@@ -1233,13 +1501,13 @@ function drawLines(svg2, spec, min, max) {
1233
1501
  );
1234
1502
  });
1235
1503
  }
1236
- function drawScatter(svg2, spec, min, max) {
1504
+ function drawScatter(svg2, spec, geo, min, max) {
1237
1505
  spec.series.forEach((series, s) => {
1238
1506
  series.points.forEach((value, i) => {
1239
1507
  svg2.appendChild(
1240
1508
  el("circle", {
1241
- cx: bandCentre(i, spec.labels.length),
1242
- cy: scaleY(value, min, max),
1509
+ cx: bandCentre(i, spec.labels.length, geo),
1510
+ cy: scaleY(value, min, max, geo),
1243
1511
  r: 4,
1244
1512
  fill: seriesColor(s),
1245
1513
  "fill-opacity": 0.85
@@ -1248,11 +1516,11 @@ function drawScatter(svg2, spec, min, max) {
1248
1516
  });
1249
1517
  });
1250
1518
  }
1251
- function drawPie(svg2, points) {
1519
+ function drawPie(svg2, points, geo) {
1252
1520
  const total = points.reduce((sum, value) => sum + value, 0);
1253
- const cx = WIDTH / 2;
1254
- const cy = PAD.top + PLOT_H / 2;
1255
- const r = Math.min(PLOT_W, PLOT_H) / 2;
1521
+ const cx = geo.width / 2;
1522
+ const cy = PAD.top + geo.plotH / 2;
1523
+ const r = Math.min(geo.plotW, geo.plotH) / 2;
1256
1524
  if (total === 0) {
1257
1525
  svg2.appendChild(
1258
1526
  el("circle", { cx, cy, r, fill: "none", stroke: "currentColor", "stroke-opacity": 0.3 })
@@ -1281,6 +1549,35 @@ function drawPie(svg2, points) {
1281
1549
  angle = end;
1282
1550
  });
1283
1551
  }
1552
+ function drawSvg(spec, geo) {
1553
+ const svg2 = el("svg", {
1554
+ viewBox: `0 0 ${geo.width} ${geo.height}`,
1555
+ width: "100%",
1556
+ role: "img"
1557
+ });
1558
+ svg2.setAttribute("aria-label", spec.title ?? `${spec.kind} chart`);
1559
+ if (spec.kind === "pie") {
1560
+ const first = spec.series[0];
1561
+ drawPie(
1562
+ svg2,
1563
+ first.points.map((value) => Math.max(0, value)),
1564
+ geo
1565
+ );
1566
+ return svg2;
1567
+ }
1568
+ const { min, max } = extent(spec);
1569
+ drawAxes(svg2, spec, geo, min, max);
1570
+ if (spec.kind === "bar") {
1571
+ drawBars(svg2, spec, geo, min, max);
1572
+ } else if (spec.kind === "stacked") {
1573
+ drawStacked(svg2, spec, geo, min, max);
1574
+ } else if (spec.kind === "line") {
1575
+ drawLines(svg2, spec, geo, min, max);
1576
+ } else {
1577
+ drawScatter(svg2, spec, geo, min, max);
1578
+ }
1579
+ return svg2;
1580
+ }
1284
1581
  function buildLegend(entries2) {
1285
1582
  if (entries2.length < 2) {
1286
1583
  return null;
@@ -1299,6 +1596,12 @@ function buildLegend(entries2) {
1299
1596
  });
1300
1597
  return row;
1301
1598
  }
1599
+ function fitToWidth(block, redraw) {
1600
+ const observer = new ResizeObserver(() => {
1601
+ redraw(Math.round(block.clientWidth / WIDTH_STEP) * WIDTH_STEP);
1602
+ });
1603
+ observer.observe(block);
1604
+ }
1302
1605
  function renderChart(spec) {
1303
1606
  if (spec.labels.length === 0 || spec.series.length === 0) {
1304
1607
  return null;
@@ -1313,27 +1616,8 @@ function renderChart(spec) {
1313
1616
  heading.textContent = spec.title;
1314
1617
  block.appendChild(heading);
1315
1618
  }
1316
- const svg2 = el("svg", { viewBox: `0 0 ${WIDTH} ${HEIGHT}`, width: "100%", role: "img" });
1317
- svg2.setAttribute("aria-label", spec.title ?? `${spec.kind} chart`);
1318
- if (spec.kind === "pie") {
1319
- const first = spec.series[0];
1320
- drawPie(
1321
- svg2,
1322
- first.points.map((value) => Math.max(0, value))
1323
- );
1324
- } else {
1325
- const { min, max } = extent(spec);
1326
- drawAxes(svg2, spec, min, max);
1327
- if (spec.kind === "bar") {
1328
- drawBars(svg2, spec, min, max);
1329
- } else if (spec.kind === "stacked") {
1330
- drawStacked(svg2, spec, min, max);
1331
- } else if (spec.kind === "line") {
1332
- drawLines(svg2, spec, min, max);
1333
- } else {
1334
- drawScatter(svg2, spec, min, max);
1335
- }
1336
- }
1619
+ let width = DEFAULT_WIDTH;
1620
+ let svg2 = drawSvg(spec, geometryFor(width));
1337
1621
  block.appendChild(svg2);
1338
1622
  const legend = buildLegend(
1339
1623
  spec.kind === "pie" ? spec.labels : spec.series.map((series) => series.label)
@@ -1341,6 +1625,15 @@ function renderChart(spec) {
1341
1625
  if (legend !== null) {
1342
1626
  block.appendChild(legend);
1343
1627
  }
1628
+ fitToWidth(block, (measured) => {
1629
+ if (measured === width) {
1630
+ return;
1631
+ }
1632
+ width = measured;
1633
+ const next = drawSvg(spec, geometryFor(width));
1634
+ svg2.replaceWith(next);
1635
+ svg2 = next;
1636
+ });
1344
1637
  return block;
1345
1638
  }
1346
1639
 
@@ -1700,13 +1993,36 @@ var CheckpointMenu = class {
1700
1993
  };
1701
1994
 
1702
1995
  // src/ui/clamp_launcher.ts
1703
- 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));
1704
1999
  return {
1705
- left: Math.max(0, Math.min(launcher.left, viewport.width - launcher.width)),
1706
- 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
+ )
1707
2008
  };
1708
2009
  }
1709
2010
 
2011
+ // src/ui/clamp_panel.ts
2012
+ function clampPanel(host, viewport, margin = EDGE_MARGIN) {
2013
+ const width = host.right - host.left;
2014
+ const height = host.bottom - host.top;
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
+ );
2023
+ return { left, top, right: left + width, bottom: top + height };
2024
+ }
2025
+
1710
2026
  // src/ui/confirmation_card.ts
1711
2027
  function actionButton2(modifier, label) {
1712
2028
  const button2 = document.createElement("button");
@@ -1908,6 +2224,7 @@ function enableLauncherDrag(launcher, options) {
1908
2224
  const onUp = (up) => {
1909
2225
  window.removeEventListener("pointermove", onMove);
1910
2226
  window.removeEventListener("pointerup", onUp);
2227
+ window.removeEventListener("pointercancel", onUp);
1911
2228
  if (!dragging) {
1912
2229
  return;
1913
2230
  }
@@ -1925,6 +2242,7 @@ function enableLauncherDrag(launcher, options) {
1925
2242
  };
1926
2243
  window.addEventListener("pointermove", onMove);
1927
2244
  window.addEventListener("pointerup", onUp);
2245
+ window.addEventListener("pointercancel", onUp);
1928
2246
  });
1929
2247
  let pending = null;
1930
2248
  const settle = () => {
@@ -1963,49 +2281,53 @@ function enableLauncherDrag(launcher, options) {
1963
2281
  launcher.addEventListener("blur", settle);
1964
2282
  }
1965
2283
 
1966
- // src/ui/launcher_placement.ts
1967
- var EDGE_MARGIN = 24;
1968
- function launcherPlacement(launcher, panel, viewport, margin = EDGE_MARGIN) {
1969
- const roomRunningRight = viewport.width - launcher.left;
1970
- const roomRunningLeft = launcher.left + launcher.width;
1971
- const roomRunningDown = viewport.height - launcher.top;
1972
- const roomRunningUp = launcher.top + launcher.height;
1973
- const corner = {
1974
- x: roomRunningRight >= roomRunningLeft ? "left" : "right",
1975
- y: roomRunningDown >= roomRunningUp ? "top" : "bottom"
1976
- };
1977
- const wantedLeft = corner.x === "left" ? launcher.left : launcher.left + launcher.width - panel.width;
1978
- const wantedTop = corner.y === "top" ? launcher.top : launcher.top + launcher.height - panel.height;
1979
- const hostLeft = clamp(wantedLeft, margin, viewport.width - margin - panel.width);
1980
- const hostTop = clamp(wantedTop, margin, viewport.height - margin - panel.height);
1981
- const hostRight = hostLeft + panel.width;
1982
- const hostBottom = hostTop + panel.height;
2284
+ // src/ui/place_widget.ts
2285
+ function placeWidget(host, launcher, corner, screen) {
1983
2286
  return {
1984
- corner,
1985
2287
  hostInset: inset({
1986
- top: corner.y === "top" ? hostTop : null,
1987
- right: corner.x === "right" ? viewport.width - hostRight : null,
1988
- bottom: corner.y === "bottom" ? viewport.height - hostBottom : null,
1989
- left: corner.x === "left" ? hostLeft : null
2288
+ top: corner.y === "top" ? host.top : null,
2289
+ right: corner.x === "right" ? screen.width - host.right : null,
2290
+ bottom: corner.y === "bottom" ? screen.height - host.bottom : null,
2291
+ left: corner.x === "left" ? host.left : null
1990
2292
  }),
1991
- // Measured from the host box's pinned corner to the launcher's matching
1992
- // corner, so the launcher lands exactly where it was dropped.
1993
2293
  launcherInset: inset({
1994
- top: corner.y === "top" ? launcher.top - hostTop : null,
1995
- right: corner.x === "right" ? hostRight - (launcher.left + launcher.width) : null,
1996
- bottom: corner.y === "bottom" ? hostBottom - (launcher.top + launcher.height) : null,
1997
- left: corner.x === "left" ? launcher.left - hostLeft : null
2294
+ top: corner.y === "top" ? launcher.top - host.top : null,
2295
+ right: corner.x === "right" ? host.right - (launcher.left + launcher.width) : null,
2296
+ bottom: corner.y === "bottom" ? host.bottom - (launcher.top + launcher.height) : null,
2297
+ left: corner.x === "left" ? launcher.left - host.left : null
1998
2298
  })
1999
2299
  };
2000
2300
  }
2001
- function clamp(value, low, high) {
2002
- return Math.max(low, Math.min(value, high));
2003
- }
2004
2301
  function inset(sides) {
2005
2302
  const side = (value) => value === null ? "auto" : `${Math.round(value)}px`;
2006
2303
  return `${side(sides.top)} ${side(sides.right)} ${side(sides.bottom)} ${side(sides.left)}`;
2007
2304
  }
2008
2305
 
2306
+ // src/ui/launcher_placement.ts
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;
2312
+ const corner = {
2313
+ x: roomRunningRight >= roomRunningLeft ? "left" : "right",
2314
+ y: roomRunningDown >= roomRunningUp ? "top" : "bottom"
2315
+ };
2316
+ const wantedLeft = corner.x === "left" ? launcher.left : launcher.left + launcher.width - panel.width;
2317
+ const wantedTop = corner.y === "top" ? launcher.top : launcher.top + launcher.height - panel.height;
2318
+ const host = clampPanel(
2319
+ {
2320
+ left: wantedLeft,
2321
+ top: wantedTop,
2322
+ right: wantedLeft + panel.width,
2323
+ bottom: wantedTop + panel.height
2324
+ },
2325
+ viewport,
2326
+ margin
2327
+ );
2328
+ return { corner, ...placeWidget(host, launcher, corner, screen) };
2329
+ }
2330
+
2009
2331
  // src/ui/message_actions.ts
2010
2332
  var CONFIRM_MS2 = 1500;
2011
2333
  function attachMessageActions(bubble, options) {
@@ -2374,6 +2696,57 @@ function place(button2, rect) {
2374
2696
  button2.style.left = `${Math.min(Math.max(centre, half), width - half)}px`;
2375
2697
  }
2376
2698
 
2699
+ // src/ui/panel_drag.ts
2700
+ var DRAG_THRESHOLD2 = 4;
2701
+ var CONTROLS = "button, a[href], input, select, textarea, [contenteditable]";
2702
+ function enablePanelDrag(handle, options) {
2703
+ handle.addEventListener("pointerdown", (event) => {
2704
+ if (event.button !== 0 || !options.enabled() || onControl(event, handle)) {
2705
+ return;
2706
+ }
2707
+ const start = options.rect();
2708
+ const originX = event.clientX;
2709
+ const originY = event.clientY;
2710
+ let dragging = false;
2711
+ const boxAt2 = (x2, y2) => {
2712
+ const dx = x2 - originX;
2713
+ const dy = y2 - originY;
2714
+ return {
2715
+ left: start.left + dx,
2716
+ top: start.top + dy,
2717
+ right: start.right + dx,
2718
+ bottom: start.bottom + dy
2719
+ };
2720
+ };
2721
+ const onMove = (move) => {
2722
+ if (!dragging && Math.hypot(move.clientX - originX, move.clientY - originY) < DRAG_THRESHOLD2) {
2723
+ return;
2724
+ }
2725
+ dragging = true;
2726
+ handle.setAttribute("data-dragging", "true");
2727
+ options.apply(boxAt2(move.clientX, move.clientY), start);
2728
+ };
2729
+ const onUp = (up) => {
2730
+ window.removeEventListener("pointermove", onMove);
2731
+ window.removeEventListener("pointerup", onUp);
2732
+ window.removeEventListener("pointercancel", onUp);
2733
+ if (!dragging) {
2734
+ return;
2735
+ }
2736
+ handle.removeAttribute("data-dragging");
2737
+ options.commit(boxAt2(up.clientX, up.clientY), start);
2738
+ };
2739
+ event.preventDefault();
2740
+ window.addEventListener("pointermove", onMove);
2741
+ window.addEventListener("pointerup", onUp);
2742
+ window.addEventListener("pointercancel", onUp);
2743
+ });
2744
+ }
2745
+ function onControl(event, handle) {
2746
+ const path = event.composedPath();
2747
+ return path.slice(0, path.indexOf(handle)).some((node) => node instanceof Element && node.matches(CONTROLS));
2748
+ }
2749
+
2377
2750
  // src/ui/prettify_tool_name.ts
2378
2751
  function prettifyToolName(name) {
2379
2752
  const spaced = name.replace(/[._-]+/g, " ").trim();
@@ -2876,11 +3249,11 @@ function createDOMPurify() {
2876
3249
  const originalDocument = document2;
2877
3250
  const currentScript = originalDocument.currentScript;
2878
3251
  window2.DocumentFragment;
2879
- const HTMLTemplateElement = window2.HTMLTemplateElement, Node2 = window2.Node, Element = window2.Element, NodeFilter2 = window2.NodeFilter, _window$NamedNodeMap = window2.NamedNodeMap;
3252
+ const HTMLTemplateElement = window2.HTMLTemplateElement, Node2 = window2.Node, Element2 = window2.Element, NodeFilter2 = window2.NodeFilter, _window$NamedNodeMap = window2.NamedNodeMap;
2880
3253
  _window$NamedNodeMap === void 0 ? window2.NamedNodeMap || window2.MozNamedAttrMap : _window$NamedNodeMap;
2881
3254
  window2.HTMLFormElement;
2882
3255
  const DOMParser = window2.DOMParser, trustedTypes = window2.trustedTypes;
2883
- const ElementPrototype = Element.prototype;
3256
+ const ElementPrototype = Element2.prototype;
2884
3257
  const cloneNode = lookupGetter(ElementPrototype, "cloneNode");
2885
3258
  const remove = lookupGetter(ElementPrototype, "remove");
2886
3259
  const getNextSibling = lookupGetter(ElementPrototype, "nextSibling");
@@ -4110,111 +4483,111 @@ function createDOMPurify() {
4110
4483
  }
4111
4484
  var purify = createDOMPurify();
4112
4485
 
4113
- // node_modules/.pnpm/marked@18.0.10/node_modules/marked/lib/marked.esm.js
4114
- function C() {
4486
+ // node_modules/.pnpm/marked@18.0.11/node_modules/marked/lib/marked.esm.js
4487
+ function A() {
4115
4488
  return { async: false, breaks: false, extensions: null, gfm: true, hooks: null, pedantic: false, renderer: null, silent: false, tokenizer: null, walkTokens: null };
4116
4489
  }
4117
- var R = C();
4490
+ var R = A();
4118
4491
  function j(l3) {
4119
4492
  R = l3;
4120
4493
  }
4121
4494
  var z = { exec: () => null };
4122
- function A(l3) {
4495
+ function I(l3) {
4123
4496
  let e = [];
4124
4497
  return (t) => {
4125
4498
  let n = Math.max(0, Math.min(3, t - 1)), s = e[n];
4126
4499
  return s || (s = l3(n), e[n] = s), s;
4127
4500
  };
4128
4501
  }
4129
- function d(l3, e = "") {
4502
+ function k(l3, e = "") {
4130
4503
  let t = typeof l3 == "string" ? l3 : l3.source, n = { replace: (s, r) => {
4131
4504
  let i = typeof r == "string" ? r : r.source;
4132
4505
  return i = i.replace(m.caret, "$1"), t = t.replace(s, i), n;
4133
4506
  }, getRegex: () => new RegExp(t, e) };
4134
4507
  return n;
4135
4508
  }
4136
- var Te = ((l3 = "") => {
4509
+ var Oe = ((l3 = "") => {
4137
4510
  try {
4138
4511
  return !!new RegExp("(?<=1)(?<!1)" + l3);
4139
4512
  } catch {
4140
4513
  return false;
4141
4514
  }
4142
4515
  })();
4143
- 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}}>`)) };
4144
- 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|$))+/;
4145
4518
  var we = /^((?: {4}| {0,3}\t)[^\n]+(?:\n(?:[ \t]*(?:\n|$))*)?)+/;
4146
4519
  var ye = /^ {0,3}(`{3,}(?=[^`\n]*(?:\n|$))|~{3,})([^\n]*)(?:\n|$)(?:|([\s\S]*?)(?:\n|$))(?: {0,3}\1[~`]* *(?=\n|$)|$)/;
4147
4520
  var q = /^ {0,3}((?:-[\t ]*){3,}|(?:_[ \t]*){3,}|(?:\*[ \t]*){3,})(?:\n+|$)/;
4148
4521
  var Pe = /^ {0,3}(#{1,6})(?=\s|$)(.*)(?:\n+|$)/;
4149
4522
  var U = / {0,3}(?:[*+-]|\d{1,9}[.)])/;
4150
4523
  var oe = /^(?!bull |blockCode|fences|blockquote|heading|html|table)((?:.|\n(?!\s*?\n|bull |blockCode|fences|blockquote|heading|html|table))+?)\n {0,3}(=+|-+) *(?:\n+|$)/;
4151
- 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();
4152
- 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();
4153
4526
  var K = /^([^\n]+(?:\n(?!hr|heading|lheading|blockquote|fences|list|html|table|[ \t]+\n)[^\n]+)*)/;
4154
4527
  var _e = /^[^\n]+/;
4155
4528
  var W = /(?!\s*\])(?:\\[\s\S]|[^\[\]\\])+/;
4156
- 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();
4157
- 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();
4158
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";
4159
4532
  var X = /<!--(?:-?>|[\s\S]*?(?:-->|$))/;
4160
- 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();
4161
- 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();
4162
4535
  var ze = le(/ {0,3}(?:[*+-]|1[.)])[ \t]+[^ \t\n]/);
4163
- var Ee = le(/ {0,3}(?:[*+-]|\d{1,9}[.)])(?:[ \t]|\n|$)/);
4164
- var Ce = d(/^( {0,3}> ?(paragraph|[^\n]*)(?:\n|$))+/).replace("paragraph", Ee).getRegex();
4165
- 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 };
4166
- 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();
4167
- 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() };
4168
- 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} *[^
4169
4542
  ]`).replace("lheading", ae).replace("|table", "").replace("blockquote", " {0,3}>").replace("|fences", "").replace("|list", "").replace("|html", "").replace("|tag", "").getRegex() };
4170
4543
  var Be = /^\\([!"#$%&'()*+,\-./:;<=>?@\[\]\\^_`{|}~])/;
4171
4544
  var De = /^(`+)([^`]|[^`][\s\S]*?[^`])\1(?!`)/;
4172
- var pe = /^( {2,}|\\)\n(?!\s*$)/;
4545
+ var ue = /^( {2,}|\\)\n(?!\s*$)/;
4173
4546
  var qe = /^(`+|[^`])(?:(?= {2,}\n)|[\s\S]*?(?:(?=[\\<!\[`*_]|\b_|$)|[^ ](?= {2,}\n)))/;
4174
4547
  var _ = /[\p{P}\p{S}]/u;
4175
- var I = /[\s\p{P}\p{S}]/u;
4548
+ var C = /[\s\p{P}\p{S}]/u;
4176
4549
  var v = /[^\s\p{P}\p{S}]/u;
4177
- var ve = d(/^((?![*_])punctSpace)/, "u").replace(/punctSpace/g, I).getRegex();
4550
+ var ve = k(/^((?![*_])punctSpace)/, "u").replace(/punctSpace/g, C).getRegex();
4178
4551
  var He = /[\p{Pi}\p{Ps}"']/u;
4179
- var ue = /(?!~)[\p{P}\p{S}]/u;
4552
+ var pe = /(?!~)[\p{P}\p{S}]/u;
4180
4553
  var Ze = /(?!~)[\s\p{P}\p{S}]/u;
4181
4554
  var Ge = /(?:[^\s\p{P}\p{S}]|~)/u;
4182
- 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();
4183
4556
  var ce = /^(?:\*+(?:((?!\*)punct)|([^\s*]))?)|^_+(?:((?!_)punct)|([^\s_]))?/;
4184
- var Ne = d(ce, "u").replace(/punct/g, _).getRegex();
4185
- 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();
4186
4559
  var Fe = /^(?:\*+(?:((?!\*)(?!openQuote)punct)|([^\s*]))?)|^_+(?:((?!_)(?!openQuote)punct)|([^\s_]))?/;
4187
- 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();
4188
4561
  var he = "^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)punct(\\*+)(?=[\\s]|$)|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)|(?!\\*)punctSpace(\\*+)(?=notPunctSpace)|[\\s](\\*+)(?!\\*)(?=punct)|(?!\\*)punct(\\*+)(?!\\*)(?=punct)|notPunctSpace(\\*+)(?=notPunctSpace)";
4189
- var Ke = d(he, "gu").replace(/notPunctSpace/g, v).replace(/punctSpace/g, I).replace(/punct/g, _).getRegex();
4190
- 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();
4191
4564
  var Xe = "^[^_*]*?__[^_*]*?\\*[^_*]*?(?=__)|[^*]+(?=[^*])|(?!\\*)punct(\\*+)(?=[\\s]|$)|notPunctSpace(\\*+)(?!\\*)(?=punctSpace|$)|(?!\\*)[\\s](\\*+)(?=notPunctSpace)|[\\s](\\*+)(?!\\*)(?=punct)|(?!\\*)punct(\\*+)(?!\\*)(?=punct)|(?:(?!\\*)punct|notPunctSpace)(\\*+)(?!\\*)(?=notPunctSpace)";
4192
- var Je = d(Xe, "gu").replace(/notPunctSpace/g, v).replace(/punctSpace/g, I).replace(/punct/g, _).getRegex();
4193
- 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();
4194
4567
  var Ye = "^[^_*]*?\\*\\*[^_*]*?_[^_*]*?(?=\\*\\*)|[^_]+(?=[^_])|(?!_)punct(_+)(?=[\\s]|$)|notPunctSpace(_+)(?!_)(?=punctSpace|$)|(?!_)[\\s](_+)(?=notPunctSpace)|[\\s](_+)(?!_)(?=punct)|(?!_)punct(_+)(?!_)(?=punct)|(?:(?!_)punct|notPunctSpace)(_+)(?!_)(?=notPunctSpace)";
4195
- var et = d(Ye, "gu").replace(/notPunctSpace/g, v).replace(/punctSpace/g, I).replace(/punct/g, _).getRegex();
4196
- 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();
4197
4570
  var nt = "^[^~]+(?=[^~])|(?!~)punct(~~?)(?=[\\s]|$)|notPunctSpace(~~?)(?!~)(?=punctSpace|$)|(?!~)punctSpace(~~?)(?=notPunctSpace)|[\\s](~~?)(?!~)(?=punct)|(?!~)punct(~~?)(?!~)(?=punct)|notPunctSpace(~~?)(?=notPunctSpace)";
4198
- var rt = d(nt, "gu").replace(/notPunctSpace/g, v).replace(/punctSpace/g, I).replace(/punct/g, _).getRegex();
4199
- var st = d(/\\(punct)/, "gu").replace(/punct/g, _).getRegex();
4200
- 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();
4201
- var ot = d(X).replace("(?:-->|$)", "-->").getRegex();
4202
- 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();
4203
4576
  var G = /(?:\[(?:\\[\s\S]|[^\[\]\\])*\]|\\[\s\S]|`+(?!`)[^`]*?`+(?!`)|``+(?=\])|[^\[\]\\`])*?/;
4204
- 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();
4205
- var de = d(/^!?\[(label)\]\[(ref)\]/).replace("label", G).replace("ref", W).getRegex();
4206
- var ke = d(/^!?\[(ref)\](?:\[\])?/).replace("ref", W).getRegex();
4207
- 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();
4208
4581
  var ie = /[hH][tT][tT][pP][sS]?|[fF][tT][pP]/;
4209
- 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 };
4210
- var ut = { ...V, emStrongLDelim: Ue, emStrongRDelimAst: Je, emStrongRDelimUnd: et, link: d(/^!?\[(label)\]\((.*?)\)/).replace("label", G).getRegex(), reflink: d(/^!?\[(label)\]\s*\[([^\]]*)\]/).replace("label", G).getRegex() };
4211
- 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() };
4212
- var ct = { ...F, br: d(pe).replace("{2,}", "*").getRegex(), text: d(F.text).replace("\\b_", "\\b_| {2,}\\n").replace(/\{2,\}/g, "*").getRegex() };
4213
- var H = { normal: J, gfm: Ae, pedantic: Ie };
4214
- 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 };
4215
4588
  var ht = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" };
4216
4589
  var ge = (l3) => ht[l3];
4217
- function O(l3, e) {
4590
+ function T(l3, e) {
4218
4591
  if (e) {
4219
4592
  if (m.escapeTest.test(l3)) return l3.replace(m.escapeReplace, ge);
4220
4593
  } else if (m.escapeTestNoEncode.test(l3)) return l3.replace(m.escapeReplaceNoEncode, ge);
@@ -4230,9 +4603,9 @@ function Y(l3) {
4230
4603
  }
4231
4604
  function ee(l3, e) {
4232
4605
  let t = l3.replace(m.findPipe, (r, i, o) => {
4233
- let p = false, a = i;
4234
- for (; --a >= 0 && o[a] === "\\"; ) p = !p;
4235
- return p ? "|" : " |";
4606
+ let u = false, a = i;
4607
+ for (; --a >= 0 && o[a] === "\\"; ) u = !u;
4608
+ return u ? "|" : " |";
4236
4609
  }), n = t.split(m.splitPipe), s = 0;
4237
4610
  if (n[0].trim() || n.shift(), n.length > 0 && !n.at(-1)?.trim() && n.pop(), e) if (n.length > e) n.splice(e);
4238
4611
  else for (; n.length < e; ) n.push("");
@@ -4275,12 +4648,21 @@ function me(l3, e = 0) {
4275
4648
  return n;
4276
4649
  }
4277
4650
  function xe(l3, e, t, n, s) {
4278
- 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) === "!";
4279
4652
  n.state.inLink = true;
4280
- let p = { type: l3[0].charAt(0) === "!" ? "image" : "link", raw: t, href: r, title: i, text: o, tokens: n.inlineTokens(o) };
4281
- 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 };
4282
4664
  }
4283
- function dt(l3, e, t) {
4665
+ function kt(l3, e, t) {
4284
4666
  let n = l3.match(t.other.indentCodeCompensation);
4285
4667
  if (n === null) return e;
4286
4668
  let s = n[1];
@@ -4314,7 +4696,7 @@ var y = class {
4314
4696
  fences(e) {
4315
4697
  let t = this.rules.block.fences.exec(e);
4316
4698
  if (t) {
4317
- let n = t[0], s = dt(n, t[3] || "", this.rules);
4699
+ let n = t[0], s = kt(n, t[3] || "", this.rules);
4318
4700
  return { type: "code", raw: n, lang: t[2] ? t[2].trim().replace(this.rules.inline.anyPunctuation, "$1") : t[2], text: s };
4319
4701
  }
4320
4702
  }
@@ -4342,33 +4724,33 @@ var y = class {
4342
4724
  `).split(`
4343
4725
  `), s = "", r = "", i = [];
4344
4726
  for (; n.length > 0; ) {
4345
- let o = false, p = [], a;
4346
- for (a = 0; a < n.length; a++) if (this.rules.other.blockquoteStart.test(n[a])) p.push(n[a]), o = true;
4347
- 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]);
4348
4730
  else break;
4349
4731
  n = n.slice(a);
4350
- let u = p.join(`
4351
- `), c = u.replace(this.rules.other.blockquoteSetextReplace, `
4732
+ let p = u.join(`
4733
+ `), c = p.replace(this.rules.other.blockquoteSetextReplace, `
4352
4734
  $1`).replace(this.rules.other.blockquoteSetextReplace2, "");
4353
4735
  s = s ? `${s}
4354
- ${u}` : u, r = r ? `${r}
4736
+ ${p}` : p, r = r ? `${r}
4355
4737
  ${c}` : c;
4356
4738
  let h = this.lexer.state.top;
4357
4739
  if (this.lexer.state.top = true, this.lexer.blockTokens(c, i, true), this.lexer.state.top = h, n.length === 0) break;
4358
- let k = i.at(-1);
4359
- if (k?.type === "code") break;
4360
- if (k?.type === "blockquote") {
4361
- let T = k, g = n.join(`
4362
- `), w = T.raw + `
4363
- ` + g.replace(this.rules.other.blockquoteSetextReplace2, ""), M = this.blockquote(w);
4364
- i[i.length - 1] = M, s = `${s}
4365
- ${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;
4366
4748
  break;
4367
- } else if (k?.type === "list") {
4368
- let T = k, g = T.raw + `
4749
+ } else if (d?.type === "list") {
4750
+ let O = d, g = O.raw + `
4369
4751
  ` + n.join(`
4370
4752
  `), w = this.list(g);
4371
- 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(`
4372
4754
  `);
4373
4755
  continue;
4374
4756
  }
@@ -4383,44 +4765,44 @@ ${g}`, r = r.substring(0, r.length - T.text.length) + M.text;
4383
4765
  n = s ? `\\d{1,9}\\${n.slice(-1)}` : `\\${n}`, this.options.pedantic && (n = s ? n : "[*+-]");
4384
4766
  let i = this.rules.other.listItemRegex(n), o = false;
4385
4767
  for (; e; ) {
4386
- let a = false, u = "", c = "";
4768
+ let a = false, p = "", c = "";
4387
4769
  if (!(t = i.exec(e)) || this.rules.block.hr.test(e)) break;
4388
- u = t[0], e = e.substring(u.length);
4770
+ p = t[0], e = e.substring(p.length);
4389
4771
  let h = me(t[2].split(`
4390
- `, 1)[0], t[1].length), k = e.split(`
4391
- `, 1)[0], T = !h.trim(), g = 0;
4392
- 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 + `
4393
- `, e = e.substring(k.length + 1), a = true), !a) {
4394
- 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);
4395
4777
  for (; e; ) {
4396
4778
  let N = e.split(`
4397
4779
  `, 1)[0], D;
4398
- 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;
4399
- 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 += `
4400
4782
  ` + D.slice(g);
4401
4783
  else {
4402
- 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;
4403
4785
  c += `
4404
- ` + k;
4786
+ ` + d;
4405
4787
  }
4406
- T = !k.trim(), u += N + `
4788
+ O = !d.trim(), p += N + `
4407
4789
  `, e = e.substring(N.length + 1), h = D.slice(g);
4408
4790
  }
4409
4791
  }
4410
- 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;
4411
4793
  }
4412
- let p = r.items.at(-1);
4413
- 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();
4414
4796
  else return;
4415
4797
  r.raw = r.raw.trimEnd();
4416
4798
  for (let a of r.items) if (this.lexer.state.top = false, a.tokens = this.lexer.blockTokens(a.text, []), !r.loose) {
4417
- 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));
4418
4800
  r.loose = c;
4419
4801
  }
4420
4802
  for (let a of r.items) {
4421
- let u = a.tokens[0];
4422
- if (a.task && (u?.type === "text" || u?.type === "paragraph")) {
4423
- 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, "");
4424
4806
  for (let h = this.lexer.inlineQueue.length - 1; h >= 0; h--) if (this.rules.other.listIsTask.test(this.lexer.inlineQueue[h].src)) {
4425
4807
  this.lexer.inlineQueue[h].src = this.lexer.inlineQueue[h].src.replace(this.rules.other.listReplaceTask, "");
4426
4808
  break;
@@ -4434,7 +4816,7 @@ ${g}`, r = r.substring(0, r.length - T.text.length) + M.text;
4434
4816
  }
4435
4817
  if (r.loose) for (let a of r.items) {
4436
4818
  a.loose = true;
4437
- 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");
4438
4820
  }
4439
4821
  return r;
4440
4822
  }
@@ -4463,7 +4845,7 @@ ${g}`, r = r.substring(0, r.length - T.text.length) + M.text;
4463
4845
  if (n.length === s.length) {
4464
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);
4465
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] });
4466
- 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] })));
4467
4849
  return i;
4468
4850
  }
4469
4851
  }
@@ -4507,8 +4889,8 @@ ${g}`, r = r.substring(0, r.length - T.text.length) + M.text;
4507
4889
  let i = fe(t[2], "()");
4508
4890
  if (i === -2) return;
4509
4891
  if (i > -1) {
4510
- let p = (t[0].indexOf("!") === 0 ? 5 : 4) + t[1].length + i;
4511
- 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] = "";
4512
4894
  }
4513
4895
  }
4514
4896
  let s = t[2], r = "";
@@ -4534,25 +4916,25 @@ ${g}`, r = r.substring(0, r.length - T.text.length) + M.text;
4534
4916
  let s = this.rules.inline.emStrongLDelim.exec(e);
4535
4917
  if (!s || !s[1] && !s[2] && !s[3] && !s[4] || s[4] && n.match(this.rules.other.unicodeAlphaNumeric)) return;
4536
4918
  if (!(s[1] || s[3] || "") || !n || this.rules.inline.punctuation.exec(n)) {
4537
- 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;
4538
- 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; ) {
4539
4921
  if (o = s[1] || s[2] || s[3] || s[4] || s[5] || s[6], !o) continue;
4540
- if (p = [...o].length, s[3] || s[4]) {
4541
- a += p;
4922
+ if (u = [...o].length, s[3] || s[4]) {
4923
+ a += u;
4542
4924
  continue;
4543
4925
  } else if (s[5] || s[6]) {
4544
- if (i % 3 && !((i + p) % 3)) {
4545
- u += p;
4926
+ if (i % 3 && !((i + u) % 3)) {
4927
+ p += u;
4546
4928
  continue;
4547
4929
  }
4548
4930
  if (h) break;
4549
4931
  }
4550
- if (a -= p, a > 0) continue;
4551
- p = Math.min(p, p + a + u);
4552
- let T = [...s[0]][0].length, g = e.slice(0, i + s.index + T + p);
4553
- if (Math.min(i, p) % 2) {
4554
- let M = g.slice(1, -1);
4555
- 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) };
4556
4938
  }
4557
4939
  let w = g.slice(2, -2);
4558
4940
  return { type: "strong", raw: g, text: w, tokens: this.lexer.inlineTokens(w) };
@@ -4574,17 +4956,17 @@ ${g}`, r = r.substring(0, r.length - T.text.length) + M.text;
4574
4956
  let s = this.rules.inline.delLDelim.exec(e);
4575
4957
  if (!s) return;
4576
4958
  if (!(s[1] || "") || !n || this.rules.inline.punctuation.exec(n)) {
4577
- let i = [...s[0]].length - 1, o, p, a = i, u = this.rules.inline.delRDelim;
4578
- for (u.lastIndex = 0, t = t.slice(-1 * e.length + i); (s = u.exec(t)) !== null; ) {
4579
- 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;
4580
4962
  if (s[3] || s[4]) {
4581
- a += p;
4963
+ a += u;
4582
4964
  continue;
4583
4965
  }
4584
- if (a -= p, a > 0) continue;
4585
- p = Math.min(p, p + a);
4586
- let c = [...s[0]][0].length, h = e.slice(0, i + s.index + c + p), k = h.slice(i, -i);
4587
- 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) };
4588
4970
  }
4589
4971
  }
4590
4972
  }
@@ -4625,7 +5007,7 @@ var x = class l {
4625
5007
  inlineQueue;
4626
5008
  tokenizer;
4627
5009
  constructor(e) {
4628
- 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 };
4629
5011
  let t = { other: m, block: H.normal, inline: B.normal };
4630
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;
4631
5013
  }
@@ -4717,9 +5099,9 @@ var x = class l {
4717
5099
  }
4718
5100
  let i = e;
4719
5101
  if (this.options.extensions?.startBlock) {
4720
- let o = 1 / 0, p = e.slice(1), a;
4721
- this.options.extensions.startBlock.forEach((u) => {
4722
- 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));
4723
5105
  }), o < 1 / 0 && o >= 0 && (i = e.substring(0, o + 1));
4724
5106
  }
4725
5107
  if (this.state.top && (r = this.tokenizer.paragraph(i))) {
@@ -4749,16 +5131,34 @@ var x = class l {
4749
5131
  inline(e, t = []) {
4750
5132
  return this.inlineQueue.push({ src: e, tokens: t }), t;
4751
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
+ }
4752
5144
  inlineTokens(e, t = []) {
4753
5145
  this.tokenizer.lexer = this;
4754
5146
  let n = e;
4755
- if (this.tokens.links) {
4756
- let o = Object.keys(this.tokens.links);
4757
- 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);
4758
5158
  }
4759
- n = n.replace(this.tokenizer.rules.inline.anyPunctuation, (o) => "+".repeat(o.length)), n = n.replace(this.tokenizer.rules.inline.blockSkip, (o, p, a) => {
4760
- let u = a ? a.length : 0;
4761
- 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) + "]";
4762
5162
  }), n = this.options.hooks?.emStrongMask?.call({ lexer: this }, n) ?? n;
4763
5163
  let s = false, r = "", i = 1 / 0;
4764
5164
  for (; e; ) {
@@ -4812,14 +5212,14 @@ var x = class l {
4812
5212
  e = e.substring(o.raw.length), t.push(o);
4813
5213
  continue;
4814
5214
  }
4815
- let p = e;
5215
+ let u = e;
4816
5216
  if (this.options.extensions?.startInline) {
4817
- let a = 1 / 0, u = e.slice(1), c;
5217
+ let a = 1 / 0, p = e.slice(1), c;
4818
5218
  this.options.extensions.startInline.forEach((h) => {
4819
- c = h.call({ lexer: this }, u), typeof c == "number" && c >= 0 && (a = Math.min(a, c));
4820
- }), 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));
4821
5221
  }
4822
- if (o = this.tokenizer.inlineText(p)) {
5222
+ if (o = this.tokenizer.inlineText(u)) {
4823
5223
  e = e.substring(o.raw.length), o.raw.slice(-1) !== "_" && (r = o.raw.slice(-1)), s = true;
4824
5224
  let a = t.at(-1);
4825
5225
  a?.type === "text" ? (a.raw += o.raw, a.text += o.text) : t.push(o);
@@ -4850,8 +5250,8 @@ var P = class {
4850
5250
  code({ text: e, lang: t, escaped: n }) {
4851
5251
  let s = (t || "").match(m.notSpaceStart)?.[0], r = e.replace(m.endingNewline, "") + `
4852
5252
  `;
4853
- return s ? '<pre><code class="language-' + O(s) + '">' + (n ? r : O(r, true)) + `</code></pre>
4854
- ` : "<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>
4855
5255
  `;
4856
5256
  }
4857
5257
  blockquote({ tokens: e }) {
@@ -4876,8 +5276,8 @@ ${this.parser.parse(e)}</blockquote>
4876
5276
  list(e) {
4877
5277
  let t = e.ordered, n = e.start, s = "";
4878
5278
  for (let o = 0; o < e.items.length; o++) {
4879
- let p = e.items[o];
4880
- s += this.listitem(p);
5279
+ let u = e.items[o];
5280
+ s += this.listitem(u);
4881
5281
  }
4882
5282
  let r = t ? "ol" : "ul", i = t && n !== 1 ? ' start="' + n + '"' : "";
4883
5283
  return "<" + r + i + `>
@@ -4929,7 +5329,7 @@ ${e}</tr>
4929
5329
  return `<em>${this.parser.parseInline(e)}</em>`;
4930
5330
  }
4931
5331
  codespan({ text: e }) {
4932
- return `<code>${O(e, true)}</code>`;
5332
+ return `<code>${T(e, true)}</code>`;
4933
5333
  }
4934
5334
  br(e) {
4935
5335
  return "<br>";
@@ -4942,18 +5342,18 @@ ${e}</tr>
4942
5342
  if (r === null) return s;
4943
5343
  e = r;
4944
5344
  let i = '<a href="' + e + '"';
4945
- return t && (i += ' title="' + O(t) + '"'), i += ">" + s + "</a>", i;
5345
+ return t && (i += ' title="' + T(t) + '"'), i += ">" + s + "</a>", i;
4946
5346
  }
4947
5347
  image({ href: e, title: t, text: n, tokens: s }) {
4948
5348
  s && (n = this.parser.parseInline(s, this.parser.textRenderer));
4949
5349
  let r = Y(e);
4950
- if (r === null) return O(n);
5350
+ if (r === null) return T(n);
4951
5351
  e = r;
4952
- let i = `<img src="${e}" alt="${O(n)}"`;
4953
- 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;
4954
5354
  }
4955
5355
  text(e) {
4956
- 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);
4957
5357
  }
4958
5358
  };
4959
5359
  var L = class {
@@ -5168,7 +5568,7 @@ var S = class {
5168
5568
  }
5169
5569
  };
5170
5570
  var Z = class {
5171
- defaults = C();
5571
+ defaults = A();
5172
5572
  options = this.setOptions;
5173
5573
  parse = this.parseMarkdown(true);
5174
5574
  parseInline = this.parseMarkdown(false);
@@ -5214,8 +5614,8 @@ var Z = class {
5214
5614
  if ("renderer" in r) {
5215
5615
  let i = t.renderers[r.name];
5216
5616
  i ? t.renderers[r.name] = function(...o) {
5217
- let p = r.renderer.apply(this, o);
5218
- 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;
5219
5619
  } : t.renderers[r.name] = r.renderer;
5220
5620
  }
5221
5621
  if ("tokenizer" in r) {
@@ -5229,10 +5629,10 @@ var Z = class {
5229
5629
  for (let i in n.renderer) {
5230
5630
  if (!(i in r)) throw new Error(`renderer '${i}' does not exist`);
5231
5631
  if (["options", "parser"].includes(i)) continue;
5232
- let o = i, p = n.renderer[o], a = r[o];
5233
- r[o] = (...u) => {
5234
- let c = p.apply(r, u);
5235
- 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 || "";
5236
5636
  };
5237
5637
  }
5238
5638
  s.renderer = r;
@@ -5242,10 +5642,10 @@ var Z = class {
5242
5642
  for (let i in n.tokenizer) {
5243
5643
  if (!(i in r)) throw new Error(`tokenizer '${i}' does not exist`);
5244
5644
  if (["options", "rules", "lexer"].includes(i)) continue;
5245
- let o = i, p = n.tokenizer[o], a = r[o];
5246
- r[o] = (...u) => {
5247
- let c = p.apply(r, u);
5248
- 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;
5249
5649
  };
5250
5650
  }
5251
5651
  s.tokenizer = r;
@@ -5255,21 +5655,21 @@ var Z = class {
5255
5655
  for (let i in n.hooks) {
5256
5656
  if (!(i in r)) throw new Error(`hook '${i}' does not exist`);
5257
5657
  if (["options", "block"].includes(i)) continue;
5258
- let o = i, p = n.hooks[o], a = r[o];
5259
- 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) => {
5260
5660
  if (this.defaults.async && S.passThroughHooksRespectAsync.has(i)) return (async () => {
5261
- let h = await p.call(r, u);
5661
+ let h = await u.call(r, p);
5262
5662
  return a.call(r, h);
5263
5663
  })();
5264
- let c = p.call(r, u);
5664
+ let c = u.call(r, p);
5265
5665
  return a.call(r, c);
5266
- } : r[o] = (...u) => {
5666
+ } : r[o] = (...p) => {
5267
5667
  if (this.defaults.async) return (async () => {
5268
- let h = await p.apply(r, u);
5269
- 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;
5270
5670
  })();
5271
- let c = p.apply(r, u);
5272
- 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;
5273
5673
  };
5274
5674
  }
5275
5675
  s.hooks = r;
@@ -5277,8 +5677,8 @@ var Z = class {
5277
5677
  if (n.walkTokens) {
5278
5678
  let r = this.defaults.walkTokens, i = n.walkTokens;
5279
5679
  s.walkTokens = function(o) {
5280
- let p = [];
5281
- 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;
5282
5682
  };
5283
5683
  }
5284
5684
  this.defaults = { ...this.defaults, ...s };
@@ -5300,10 +5700,10 @@ var Z = class {
5300
5700
  if (typeof n > "u" || n === null) return o(new Error("marked(): input parameter is undefined or null"));
5301
5701
  if (typeof n != "string") return o(new Error("marked(): input parameter is of type " + Object.prototype.toString.call(n) + ", string expected"));
5302
5702
  if (i.hooks && (i.hooks.options = i, i.hooks.block = e), i.async) return (async () => {
5303
- 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;
5304
5704
  i.walkTokens && await Promise.all(this.walkTokens(c, i.walkTokens));
5305
- let k = await (i.hooks ? await i.hooks.provideParser(e) : e ? b.parse : b.parseInline)(c, i);
5306
- 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;
5307
5707
  })().catch(o);
5308
5708
  try {
5309
5709
  i.hooks && (n = i.hooks.preprocess(n));
@@ -5311,8 +5711,8 @@ var Z = class {
5311
5711
  i.hooks && (a = i.hooks.processAllTokens(a)), i.walkTokens && this.walkTokens(a, i.walkTokens);
5312
5712
  let c = (i.hooks ? i.hooks.provideParser(e) : e ? b.parse : b.parseInline)(a, i);
5313
5713
  return i.hooks && (c = i.hooks.postprocess(c)), c;
5314
- } catch (p) {
5315
- return o(p);
5714
+ } catch (u) {
5715
+ return o(u);
5316
5716
  }
5317
5717
  };
5318
5718
  }
@@ -5320,7 +5720,7 @@ var Z = class {
5320
5720
  return (n) => {
5321
5721
  if (n.message += `
5322
5722
  Please report this to https://github.com/markedjs/marked.`, e) {
5323
- 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>";
5324
5724
  return t ? Promise.resolve(s) : s;
5325
5725
  }
5326
5726
  if (t) return Promise.reject(n);
@@ -5328,23 +5728,23 @@ Please report this to https://github.com/markedjs/marked.`, e) {
5328
5728
  };
5329
5729
  }
5330
5730
  };
5331
- var E = new Z();
5731
+ var M = new Z();
5332
5732
  function f(l3, e) {
5333
- return E.parse(l3, e);
5733
+ return M.parse(l3, e);
5334
5734
  }
5335
5735
  f.options = f.setOptions = function(l3) {
5336
- 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;
5337
5737
  };
5338
- f.getDefaults = C;
5738
+ f.getDefaults = A;
5339
5739
  f.defaults = R;
5340
- function kt(...l3) {
5341
- 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;
5342
5742
  }
5343
- f.use = kt;
5743
+ f.use = dt;
5344
5744
  f.walkTokens = function(l3, e) {
5345
- return E.walkTokens(l3, e);
5745
+ return M.walkTokens(l3, e);
5346
5746
  };
5347
- f.parseInline = E.parseInline;
5747
+ f.parseInline = M.parseInline;
5348
5748
  f.Parser = b;
5349
5749
  f.parser = b.parse;
5350
5750
  f.Renderer = P;
@@ -5359,7 +5759,7 @@ var rn = f.setOptions;
5359
5759
  var sn = f.walkTokens;
5360
5760
  var on = f.parseInline;
5361
5761
  var ln = b.parse;
5362
- var pn = x.lex;
5762
+ var un = x.lex;
5363
5763
 
5364
5764
  // src/ui/render_markdown.ts
5365
5765
  var parser = new Z({ gfm: true, breaks: true });
@@ -5443,8 +5843,8 @@ function renderMarkdown(text4, options) {
5443
5843
  }
5444
5844
 
5445
5845
  // src/ui/resize_handle.ts
5446
- var MIN_WIDTH = 280;
5447
- var MIN_HEIGHT = 240;
5846
+ var MIN_WIDTH2 = 280;
5847
+ var MIN_HEIGHT2 = 240;
5448
5848
  var STEP2 = 16;
5449
5849
  var COARSE_STEP2 = 64;
5450
5850
  function createResizeHandle(grip, options) {
@@ -5526,11 +5926,11 @@ function movable(grip, axis) {
5526
5926
  return axis === "both" || grip.x !== void 0;
5527
5927
  }
5528
5928
  function boxAt(grip, axis, rect, x2, y2) {
5529
- const left = grip.x === "left" ? Math.min(x2, rect.right - MIN_WIDTH) : rect.left;
5530
- const right = grip.x === "right" ? Math.max(x2, rect.left + MIN_WIDTH) : rect.right;
5929
+ const left = grip.x === "left" ? Math.min(x2, rect.right - MIN_WIDTH2) : rect.left;
5930
+ const right = grip.x === "right" ? Math.max(x2, rect.left + MIN_WIDTH2) : rect.right;
5531
5931
  const vertical = axis === "both";
5532
- const top = vertical && grip.y === "top" ? Math.min(y2, rect.bottom - MIN_HEIGHT) : rect.top;
5533
- const bottom = vertical && grip.y === "bottom" ? Math.max(y2, rect.top + MIN_HEIGHT) : rect.bottom;
5932
+ const top = vertical && grip.y === "top" ? Math.min(y2, rect.bottom - MIN_HEIGHT2) : rect.top;
5933
+ const bottom = vertical && grip.y === "bottom" ? Math.max(y2, rect.top + MIN_HEIGHT2) : rect.bottom;
5534
5934
  return { left, top, right, bottom };
5535
5935
  }
5536
5936
 
@@ -5570,7 +5970,7 @@ function wrapWords(root) {
5570
5970
  }
5571
5971
 
5572
5972
  // src/ui/run_notice.ts
5573
- function renderRunNotice(icon, text4, kind) {
5973
+ function renderRunNotice(icon, text4, kind, undo) {
5574
5974
  const notice = document.createElement("div");
5575
5975
  notice.className = `run-notice run-notice--${kind}`;
5576
5976
  notice.setAttribute("part", `run-notice run-notice-${kind}`);
@@ -5585,6 +5985,18 @@ function renderRunNotice(icon, text4, kind) {
5585
5985
  label.setAttribute("part", "run-notice-text");
5586
5986
  label.textContent = text4;
5587
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
+ }
5588
6000
  return notice;
5589
6001
  }
5590
6002
 
@@ -5842,6 +6254,11 @@ var STYLES = `
5842
6254
  /* Message action row: the control box and the mark inside it. The box has a
5843
6255
  floor of 24px so it stays a reliable target at every density. */
5844
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);
5845
6262
  --_action-icon-size: var(--ag-ui-action-icon-size, 15px);
5846
6263
  --_tooltip-bg: var(--ag-ui-tooltip-bg, #1f2430);
5847
6264
  --_tooltip-fg: var(--ag-ui-tooltip-fg, #f5f6fa);
@@ -5903,6 +6320,11 @@ var STYLES = `
5903
6320
  --_glyph-size: var(--ag-ui-glyph-size, 18px);
5904
6321
  --_glyph-stroke: var(--ag-ui-glyph-stroke, 1.75);
5905
6322
 
6323
+ /* How wide a chart is allowed to get. A cap rather than a width: below it
6324
+ the chart fills its column, and above it a wider panel is just a wider
6325
+ panel. */
6326
+ --_chart-max-width: var(--ag-ui-chart-max-width, 480px);
6327
+
5906
6328
  /* Unread badge on the launcher. */
5907
6329
  --_badge-bg: var(--ag-ui-badge-bg, var(--_danger));
5908
6330
  --_badge-fg: var(--ag-ui-badge-fg, #ffffff);
@@ -5931,6 +6353,68 @@ var STYLES = `
5931
6353
  --_msg-pad: var(--ag-ui-msg-pad, 8px 12px);
5932
6354
  --_msg-radius: var(--ag-ui-msg-radius, 14px);
5933
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
+
5934
6418
  /* Layout \u2014 override from outside to dock the widget anywhere.
5935
6419
  Set --ag-ui-position: static (and place this element in your own
5936
6420
  grid/flex layout) to embed it in the page flow instead of floating. */
@@ -5938,14 +6422,49 @@ var STYLES = `
5938
6422
  --_z-index: var(--ag-ui-z-index, 2147483000);
5939
6423
  --_width: var(--ag-ui-width, 380px);
5940
6424
  --_height: var(--ag-ui-height, 560px);
5941
- --_inset: var(--ag-ui-inset, auto 24px 24px auto);
5942
- --_max-width: var(--ag-ui-max-width, calc(100vw - 48px));
5943
- --_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)));
5944
6461
  /* Reading-column width for placement="page" (full-bleed, centred content). */
5945
6462
  --_content-max-width: var(--ag-ui-content-max-width, 820px);
5946
6463
  /* Slim rail the sidebar placement collapses to. Only that placement reads
5947
6464
  it, but it is declared here so every alias has a default in one place. */
5948
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);
5949
6468
 
5950
6469
  position: var(--_position);
5951
6470
  inset: var(--_inset);
@@ -6016,23 +6535,30 @@ var STYLES = `
6016
6535
 
6017
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 */
6018
6537
  :host([placement="bottom-left"]) {
6019
- --_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
+ );
6020
6543
  }
6021
6544
 
6022
6545
  :host([placement="side"]) {
6023
- --_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);
6024
6547
  --_width: var(--ag-ui-width, 420px);
6025
- --_height: var(--ag-ui-height, 100vh);
6026
- --_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));
6027
6550
  --_radius: var(--ag-ui-radius, 0);
6028
6551
  }
6029
6552
 
6030
6553
  :host([placement="full"]) {
6031
- --_inset: var(--ag-ui-inset, 0);
6032
- --_width: var(--ag-ui-width, 100vw);
6033
- --_height: var(--ag-ui-height, 100vh);
6034
- --_max-width: var(--ag-ui-max-width, 100vw);
6035
- --_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));
6036
6562
  --_radius: var(--ag-ui-radius, 0);
6037
6563
  }
6038
6564
 
@@ -6042,11 +6568,14 @@ var STYLES = `
6042
6568
  composer rather than a per-row wrapper, so user pills still right-align and
6043
6569
  the assistant well spans the column. */
6044
6570
  :host([placement="page"]) {
6045
- --_inset: var(--ag-ui-inset, 0);
6046
- --_width: var(--ag-ui-width, 100vw);
6047
- --_height: var(--ag-ui-height, 100vh);
6048
- --_max-width: var(--ag-ui-max-width, 100vw);
6049
- --_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));
6050
6579
  --_radius: var(--ag-ui-radius, 0);
6051
6580
  }
6052
6581
 
@@ -6077,28 +6606,100 @@ var STYLES = `
6077
6606
  max-width: 100%;
6078
6607
  }
6079
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
+
6080
6663
  /* Sidebar: a full-height docked panel that slides open/closed and
6081
6664
  collapses to a slim icon rail (not the floating launcher). Docked right by
6082
6665
  default; data-side="left" docks it left. Overlay by default \u2014 set
6083
6666
  --ag-ui-position: static (and place this element in your own layout) for a
6084
6667
  host-managed push instead. */
6085
6668
  :host([placement="sidebar"]) {
6086
- --_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);
6087
6670
  --_width: var(--ag-ui-width, 420px);
6088
- --_height: var(--ag-ui-height, 100vh);
6089
- --_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));
6090
6673
  --_radius: var(--ag-ui-radius, 0);
6091
6674
  transition: width var(--_motion) var(--_ease);
6092
6675
  }
6093
6676
 
6094
6677
  :host([placement="sidebar"][data-side="left"]) {
6095
- --_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));
6096
6679
  }
6097
6680
 
6098
6681
  /* The docked panel is pinned to the edge it docks against rather than filling
6099
6682
  the host as a flex child. Collapsing shrinks the host to the rail width, and
6100
6683
  a flex child would be squashed to 52px on the way out instead of sliding out
6101
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
+
6102
6703
  :host([placement="sidebar"]) .chat {
6103
6704
  position: absolute;
6104
6705
  inset: 0 0 0 auto;
@@ -6114,11 +6715,15 @@ var STYLES = `
6114
6715
  /* Collapsed sidebar: shrink the host to the rail width and slide the panel out
6115
6716
  through the edge it docks against. Higher specificity than the generic
6116
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. */
6117
6723
  :host([placement="sidebar"][collapsed]) {
6118
6724
  width: var(--_rail-width);
6119
- height: 100vh;
6120
- max-height: 100vh;
6121
- bottom: 0;
6725
+ height: var(--_viewport-height);
6726
+ max-height: var(--_viewport-height);
6122
6727
  pointer-events: auto;
6123
6728
  }
6124
6729
 
@@ -6234,17 +6839,83 @@ var STYLES = `
6234
6839
 
6235
6840
  /* The sidebar collapses to an edge rail instead: full height, square, flush
6236
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. */
6237
6849
  :host([placement="sidebar"][collapsed]) .launcher {
6238
6850
  inset: 0;
6239
6851
  width: auto;
6240
6852
  height: auto;
6241
- align-items: flex-start;
6242
- padding-top: 16px;
6243
- 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);
6244
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%;
6245
6887
  background: var(--_header-bg);
6246
6888
  color: var(--_header-fg);
6247
- 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;
6248
6919
  transform: none;
6249
6920
  }
6250
6921
 
@@ -6293,6 +6964,41 @@ var STYLES = `
6293
6964
  color: var(--_header-fg);
6294
6965
  }
6295
6966
 
6967
+ /* The header is the panel's title bar: on the placements that let the widget
6968
+ be moved it drags the whole thing, matching the launcher's own drag. The
6969
+ selector lists those placements rather than asking JS to stamp an attribute,
6970
+ because a cursor that appears one frame late reads as a control that only
6971
+ sometimes exists. touch-action keeps a finger drag here moving the panel
6972
+ instead of scrolling the page behind it. */
6973
+ :host(:not([placement])) .header,
6974
+ :host([placement=""]) .header,
6975
+ :host([placement="floating"]) .header,
6976
+ :host([placement="bottom-left"]) .header {
6977
+ cursor: move;
6978
+ touch-action: none;
6979
+ }
6980
+
6981
+ /* A host that turned the drag off keeps a header that says so. */
6982
+ :host([data-launcher-drag="false"]) .header {
6983
+ cursor: auto;
6984
+ touch-action: auto;
6985
+ }
6986
+
6987
+ /* :host is carried for the specificity, not the scope: the placement rules
6988
+ above are a host selector plus a class plus an attribute, and a bare
6989
+ .header[data-dragging] loses to them -- silently, because the drag still
6990
+ works and only the cursor is wrong. */
6991
+ :host .header[data-dragging] {
6992
+ cursor: grabbing;
6993
+ }
6994
+
6995
+ /* The cursor inherits, so a control a host slots into the header would show
6996
+ the drag cursor over something the drag deliberately ignores. Named to
6997
+ match the controls panel_drag steps aside for. */
6998
+ .header ::slotted(:is(button, a, input, select, textarea)) {
6999
+ cursor: pointer;
7000
+ }
7001
+
6296
7002
  .header-title {
6297
7003
  flex: 1;
6298
7004
  min-width: 0;
@@ -6323,17 +7029,24 @@ var STYLES = `
6323
7029
 
6324
7030
  .header-controls {
6325
7031
  display: flex;
6326
- gap: 2px;
7032
+ gap: var(--_header-gap);
6327
7033
  flex: none;
6328
7034
  }
6329
7035
 
6330
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);
6331
7044
  border: none;
6332
7045
  background: transparent;
6333
7046
  color: inherit;
6334
7047
  font: inherit;
6335
7048
  line-height: 1;
6336
- padding: 4px 7px;
7049
+ padding: 0;
6337
7050
  border-radius: 6px;
6338
7051
  cursor: pointer;
6339
7052
  opacity: 0.85;
@@ -6355,9 +7068,9 @@ var STYLES = `
6355
7068
  Nothing paints there once the panel is gone, so the box only has to stop
6356
7069
  swallowing clicks: pointer events go to none and the launcher takes them.
6357
7070
 
6358
- Two placements collapse differently: "sidebar" slides to its rail (below),
6359
- while "embedded" and "page" keep the header bar, having no corner for a
6360
- 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. */
6361
7074
  :host([collapsed]) {
6362
7075
  pointer-events: none;
6363
7076
  /* A collapsed host has to be allowed to shrink, and in the layout hosts
@@ -6419,56 +7132,126 @@ var STYLES = `
6419
7132
  transform-origin: bottom right;
6420
7133
  }
6421
7134
 
6422
- /* The two in-flow placements keep the original collapse: hide the body, let
6423
- the host shrink to the header bar. */
6424
- :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"]) {
6425
7138
  height: auto;
6426
7139
  max-height: none;
6427
7140
  pointer-events: auto;
6428
7141
  }
6429
7142
 
6430
- :host([collapsed]:is([placement="embedded"], [placement="page"])) .chat {
7143
+ :host([collapsed][placement="embedded"]) .chat {
6431
7144
  opacity: 1;
6432
7145
  transform: none;
6433
7146
  visibility: visible;
6434
7147
  }
6435
7148
 
6436
- /* 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
6437
7150
  embedded host is position: static, which would let an absolutely-positioned
6438
7151
  circle escape the layout and land against whatever the page positions. */
6439
- :host([collapsed]:is([placement="embedded"], [placement="page"])) .launcher {
7152
+ :host([collapsed][placement="embedded"]) .launcher {
6440
7153
  visibility: hidden;
6441
7154
  opacity: 0;
6442
7155
  }
6443
7156
 
6444
- :host([collapsed]:is([placement="embedded"], [placement="page"])) .messages-wrap,
6445
- :host([collapsed]:is([placement="embedded"], [placement="page"])) .messages,
6446
- :host([collapsed]:is([placement="embedded"], [placement="page"])) .input-row,
6447
- :host([collapsed]:is([placement="embedded"], [placement="page"])) .skill-chips,
6448
- :host([collapsed]:is([placement="embedded"], [placement="page"])) .skill-palette,
6449
- :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 {
6450
7163
  display: none;
6451
7164
  }
6452
7165
 
6453
- /* Jump-to-latest: shown only once the reader has scrolled away *and* missed
6454
- something. Anchored to the panel rather than the list so it does not scroll
6455
- with the content it is offering to scroll to. */
6456
- /* The transcript's own box, and the only one whose foot is the transcript's
6457
- foot. The panel's foot is below the composer, the chips and the footer. */
6458
- .messages-wrap {
6459
- position: relative;
6460
- flex: 1;
6461
- min-height: 0;
6462
- display: flex;
6463
- flex-direction: column;
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;
6464
7182
  }
6465
7183
 
6466
- .jump-latest {
6467
- position: absolute;
6468
- left: 50%;
6469
- transform: translateX(-50%);
6470
- bottom: var(--_pad);
6471
- z-index: 2;
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 {
7222
+ display: none;
7223
+ }
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
+
7236
+ /* Jump-to-latest: shown only once the reader has scrolled away *and* missed
7237
+ something. Anchored to the panel rather than the list so it does not scroll
7238
+ with the content it is offering to scroll to. */
7239
+ /* The transcript's own box, and the only one whose foot is the transcript's
7240
+ foot. The panel's foot is below the composer, the chips and the footer. */
7241
+ .messages-wrap {
7242
+ position: relative;
7243
+ flex: 1;
7244
+ min-height: 0;
7245
+ display: flex;
7246
+ flex-direction: column;
7247
+ }
7248
+
7249
+ .jump-latest {
7250
+ position: absolute;
7251
+ left: 50%;
7252
+ transform: translateX(-50%);
7253
+ bottom: var(--_pad);
7254
+ z-index: 2;
6472
7255
  display: none;
6473
7256
  align-items: center;
6474
7257
  gap: 0.35em;
@@ -6537,6 +7320,28 @@ var STYLES = `
6537
7320
 
6538
7321
  The 1px box with clip-path, rather than width/height 0, is the shape that
6539
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
+
6540
7345
  .sr-only {
6541
7346
  position: absolute;
6542
7347
  width: 1px;
@@ -6552,6 +7357,8 @@ var STYLES = `
6552
7357
  .messages {
6553
7358
  flex: 1;
6554
7359
  overflow-y: auto;
7360
+ /* Scrolling past the end of this must not scroll the page behind it. */
7361
+ overscroll-behavior: contain;
6555
7362
  /* The browser's own scroll anchoring competes with the scroller for the same
6556
7363
  job and wins unpredictably -- it can hold the view still exactly when we
6557
7364
  want to follow. Turned off so following is decided in one place. Safari
@@ -6704,6 +7511,8 @@ var STYLES = `
6704
7511
  .message--assistant pre {
6705
7512
  padding: 8px 10px;
6706
7513
  overflow: auto;
7514
+ /* Scrolling past the end of this must not scroll the page behind it. */
7515
+ overscroll-behavior: contain;
6707
7516
  background: var(--_bg);
6708
7517
  border: 1px solid var(--_border);
6709
7518
  border-radius: 6px;
@@ -6882,6 +7691,8 @@ var STYLES = `
6882
7691
  border-left: 2px solid var(--_border);
6883
7692
  max-height: 220px;
6884
7693
  overflow: auto;
7694
+ /* Scrolling past the end of this must not scroll the page behind it. */
7695
+ overscroll-behavior: contain;
6885
7696
  white-space: pre-wrap;
6886
7697
  word-break: break-word;
6887
7698
  font-family: inherit;
@@ -7072,6 +7883,8 @@ var STYLES = `
7072
7883
  padding: 6px 8px;
7073
7884
  max-height: 160px;
7074
7885
  overflow: auto;
7886
+ /* Scrolling past the end of this must not scroll the page behind it. */
7887
+ overscroll-behavior: contain;
7075
7888
  background: var(--_bg);
7076
7889
  border: 1px solid var(--_border);
7077
7890
  border-radius: 6px;
@@ -7566,6 +8379,8 @@ var STYLES = `
7566
8379
  resize: none;
7567
8380
  max-height: var(--_composer-max-height);
7568
8381
  overflow-y: auto;
8382
+ /* Scrolling past the end of this must not scroll the page behind it. */
8383
+ overscroll-behavior: contain;
7569
8384
  padding: 6px 4px 2px;
7570
8385
  background: transparent;
7571
8386
  border: none;
@@ -7776,13 +8591,30 @@ var STYLES = `
7776
8591
  * values, and the axis furniture inherits currentColor at low opacity so it
7777
8592
  * reads correctly in either theme without a second palette.
7778
8593
  */
8594
+ /* A chart is sized like a message, not like a panel: it takes the width it
8595
+ needs and stops, rather than stretching into whatever room the transcript
8596
+ has. A message bubble caps at 80% for the same reason -- widening the panel
8597
+ should not resize what is already in it. The cap is a token so a host with a
8598
+ wide panel and a real reason can raise it; the default is the width this
8599
+ renderer has always drawn at. Below the cap the block still fills its column,
8600
+ which is what a narrow panel needs. */
7779
8601
  .chart-block {
7780
- align-self: stretch;
7781
- max-width: 100%;
8602
+ align-self: flex-start;
8603
+ width: 100%;
8604
+ max-width: var(--_chart-max-width);
7782
8605
  margin: 6px 0;
7783
8606
  color: var(--_fg);
7784
8607
  }
7785
8608
 
8609
+ /* The drawing fills the block's width and carries its own height in the
8610
+ viewBox, so it is laid out rather than magnified: block display keeps the
8611
+ inline baseline gap from adding a stripe under every chart. */
8612
+ .chart-block svg {
8613
+ display: block;
8614
+ width: 100%;
8615
+ height: auto;
8616
+ }
8617
+
7786
8618
  .chart-title {
7787
8619
  margin-bottom: 2px;
7788
8620
  font-size: 0.85em;
@@ -7833,6 +8665,30 @@ var STYLES = `
7833
8665
  opacity: 0.75;
7834
8666
  }
7835
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
+
7836
8692
  .run-notice-text {
7837
8693
  min-width: 0;
7838
8694
  overflow-wrap: anywhere;
@@ -7977,6 +8833,8 @@ var STYLES = `
7977
8833
  padding: 8px 10px;
7978
8834
  max-height: 140px;
7979
8835
  overflow: auto;
8836
+ /* Scrolling past the end of this must not scroll the page behind it. */
8837
+ overscroll-behavior: contain;
7980
8838
  font-size: 12px;
7981
8839
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
7982
8840
  background: var(--_assistant-bg);
@@ -8165,6 +9023,43 @@ var STYLES = `
8165
9023
  }
8166
9024
  }
8167
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
+
8168
9063
  .message-action[aria-pressed="true"] {
8169
9064
  opacity: 1;
8170
9065
  color: var(--_accent);
@@ -8353,6 +9248,8 @@ var STYLES = `
8353
9248
  flex-direction: column;
8354
9249
  max-height: 220px;
8355
9250
  overflow: auto;
9251
+ /* Scrolling past the end of this must not scroll the page behind it. */
9252
+ overscroll-behavior: contain;
8356
9253
  background: var(--_bg);
8357
9254
  border: 1px solid var(--_border);
8358
9255
  border-radius: 8px;
@@ -8441,6 +9338,8 @@ var STYLES = `
8441
9338
  box-shadow: 0 6px 24px rgb(0 0 0 / 12%);
8442
9339
  max-height: 60%;
8443
9340
  overflow-y: auto;
9341
+ /* Scrolling past the end of this must not scroll the page behind it. */
9342
+ overscroll-behavior: contain;
8444
9343
  transform-origin: top center;
8445
9344
  transition:
8446
9345
  opacity var(--_motion) var(--_ease),
@@ -8616,10 +9515,20 @@ var STYLES = `
8616
9515
  }
8617
9516
 
8618
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;
8619
9527
  font-weight: 600;
8620
9528
  }
8621
9529
 
8622
9530
  .drawer-new {
9531
+ flex: 0 0 auto;
8623
9532
  border: 1px solid var(--_border);
8624
9533
  border-radius: var(--_radius);
8625
9534
  background: var(--_bg);
@@ -8630,10 +9539,63 @@ var STYLES = `
8630
9539
  cursor: pointer;
8631
9540
  }
8632
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
+
8633
9593
  .drawer-list {
8634
9594
  flex: 1;
8635
9595
  min-height: 0;
8636
9596
  overflow-y: auto;
9597
+ /* Scrolling past the end of this must not scroll the page behind it. */
9598
+ overscroll-behavior: contain;
8637
9599
  }
8638
9600
 
8639
9601
  .drawer-empty {
@@ -8754,6 +9716,31 @@ var STYLES = `
8754
9716
  background: none;
8755
9717
  }
8756
9718
 
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
+
8757
9744
  :host([placement="embedded"]) .drawer-panel {
8758
9745
  width: 100%;
8759
9746
  border-right: none;
@@ -9021,6 +10008,7 @@ var ThoughtsBlock = class {
9021
10008
  };
9022
10009
 
9023
10010
  // src/ui/thread_drawer.ts
10011
+ var FILTER_FROM = 8;
9024
10012
  var ThreadDrawer = class {
9025
10013
  /** The drawer root (backdrop + panel). Append to the chat shell; hidden until opened. */
9026
10014
  element;
@@ -9033,6 +10021,23 @@ var ThreadDrawer = class {
9033
10021
  #strings;
9034
10022
  #threads = [];
9035
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;
9036
10041
  /** The element focused before the drawer opened, restored on close. */
9037
10042
  #lastFocused = null;
9038
10043
  constructor(callbacks, strings = DEFAULT_UI_STRINGS) {
@@ -9069,11 +10074,29 @@ var ThreadDrawer = class {
9069
10074
  this.close();
9070
10075
  this.#callbacks.onNew();
9071
10076
  });
9072
- 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
+ });
9073
10096
  this.#list = document.createElement("div");
9074
10097
  this.#list.className = "drawer-list";
9075
10098
  this.#list.setAttribute("part", "drawer-list");
9076
- this.#panel.append(header, this.#list);
10099
+ this.#panel.append(header, this.#filter, this.#list);
9077
10100
  this.element.append(backdrop, this.#panel);
9078
10101
  }
9079
10102
  /** Re-localize the drawer's chrome and rows (the host calls this on connect). */
@@ -9097,8 +10120,26 @@ var ThreadDrawer = class {
9097
10120
  this.#panel.setAttribute("aria-label", strings.chatHistory);
9098
10121
  this.#heading.textContent = strings.chats;
9099
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);
9100
10127
  this.#renderList();
9101
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
+ }
9102
10143
  isOpen() {
9103
10144
  return !this.element.hidden;
9104
10145
  }
@@ -9108,15 +10149,21 @@ var ThreadDrawer = class {
9108
10149
  }
9109
10150
  this.#lastFocused = this.#activeElement();
9110
10151
  this.element.hidden = false;
9111
- this.#newButton.focus();
10152
+ if (this.#modal) {
10153
+ this.#newButton.focus();
10154
+ }
10155
+ this.#callbacks.onVisibility?.(true);
9112
10156
  }
9113
10157
  close() {
9114
10158
  if (!this.isOpen()) {
9115
10159
  return;
9116
10160
  }
9117
10161
  this.element.hidden = true;
9118
- this.#lastFocused?.focus();
10162
+ if (this.#modal) {
10163
+ this.#lastFocused?.focus();
10164
+ }
9119
10165
  this.#lastFocused = null;
10166
+ this.#callbacks.onVisibility?.(false);
9120
10167
  }
9121
10168
  toggle() {
9122
10169
  if (this.isOpen()) {
@@ -9136,7 +10183,7 @@ var ThreadDrawer = class {
9136
10183
  this.close();
9137
10184
  return;
9138
10185
  }
9139
- if (event.key !== "Tab") {
10186
+ if (event.key !== "Tab" || !this.#modal) {
9140
10187
  return;
9141
10188
  }
9142
10189
  const focusables = Array.from(
@@ -9161,18 +10208,42 @@ var ThreadDrawer = class {
9161
10208
  }
9162
10209
  #renderList() {
9163
10210
  this.#list.replaceChildren();
9164
- 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) {
9165
10219
  const empty = document.createElement("div");
9166
10220
  empty.className = "drawer-empty";
9167
10221
  empty.setAttribute("part", "drawer-empty");
9168
- empty.textContent = this.#strings.noConversations;
10222
+ empty.textContent = this.#threads.length === 0 ? this.#strings.noConversations : this.#strings.noMatches;
9169
10223
  this.#list.appendChild(empty);
9170
10224
  return;
9171
10225
  }
9172
- for (const meta of this.#threads) {
10226
+ for (const meta of shown) {
9173
10227
  this.#list.appendChild(this.#renderRow(meta));
9174
10228
  }
9175
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
+ }
9176
10247
  #renderRow(meta) {
9177
10248
  const row = document.createElement("div");
9178
10249
  row.className = "drawer-row";
@@ -10698,6 +11769,13 @@ var COLLAPSED_KEY = "ag-ui-chat:collapsed";
10698
11769
  var SIZE_KEY = "ag-ui-chat:size";
10699
11770
  var THEME_KEY = "ag-ui-chat:theme";
10700
11771
  var LAUNCHER_KEY = "ag-ui-chat:launcher";
11772
+ function asPoint(value) {
11773
+ if (typeof value !== "object" || value === null) {
11774
+ return null;
11775
+ }
11776
+ const { left, top } = value;
11777
+ return typeof left === "number" && typeof top === "number" ? { left, top } : null;
11778
+ }
10701
11779
  var DRAGGABLE_PLACEMENTS = /* @__PURE__ */ new Set([null, "", "floating", "bottom-left"]);
10702
11780
  var RESIZE_GRIPS = [
10703
11781
  { y: "top" },
@@ -10937,7 +12015,7 @@ var AgUiChat = class extends HTMLElement {
10937
12015
  * Resolve a `scroll_to` / `drag_and_drop` target string to a host-page
10938
12016
  * element (or `null`). Defaults to a CSS-selector lookup; override to map
10939
12017
  * page-map element ids. The page-action tools are opt-in via the
10940
- * `data-page-actions` attribute (`"scroll"` / `"drag"`).
12018
+ * `data-page-actions` attribute (`"scroll"` / `"drag"` / `"chat"`).
10941
12019
  */
10942
12020
  resolvePageTarget = (target) => document.querySelector(target);
10943
12021
  /**
@@ -11053,6 +12131,12 @@ var AgUiChat = class extends HTMLElement {
11053
12131
  #root;
11054
12132
  /** Screen-reader-only status region -- see {@link AgUiChat.#announce}. */
11055
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");
11056
12140
  /** Return-to-foot affordance, shown only once something has been missed. */
11057
12141
  #jumpButton = document.createElement("button");
11058
12142
  /**
@@ -11114,6 +12198,8 @@ var AgUiChat = class extends HTMLElement {
11114
12198
  #launcher;
11115
12199
  /** The launcher's unread badge; hidden at zero, and when the host opts out. */
11116
12200
  #badge;
12201
+ /** The rail's vertical caption. Rendered only by the sidebar's edge rail. */
12202
+ #railLabel = document.createElement("span");
11117
12203
  // Answers that finished while the widget was collapsed. Expanding clears it.
11118
12204
  #unread = 0;
11119
12205
  /** Empty-state region at the top of the message list; hidden once anything renders. */
@@ -11126,6 +12212,26 @@ var AgUiChat = class extends HTMLElement {
11126
12212
  * position -- see #applyLauncherPlacement for what that costs the host.
11127
12213
  */
11128
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;
12223
+ /**
12224
+ * Where the user dragged the *panel*, in viewport coordinates, or null while
12225
+ * its position is still derived from the launcher's.
12226
+ *
12227
+ * The two gestures state different things and are restored differently. A
12228
+ * launcher drag says where the bubble goes and leaves the panel to open into
12229
+ * whatever space the viewport has, so it is re-derived every time -- which is
12230
+ * what lets a widget re-decide its direction when the window changes under
12231
+ * it. A header drag states the panel's own position, and re-deriving that
12232
+ * from the launcher would move the panel the user just placed.
12233
+ */
12234
+ #panelPos = null;
11129
12235
  /**
11130
12236
  * The corner the panel opens away from, once this element is placing itself.
11131
12237
  * Null means the host's layout still decides, and the anchor is measured.
@@ -11144,8 +12250,53 @@ var AgUiChat = class extends HTMLElement {
11144
12250
  * a field so `removeEventListener` on disconnect gets the same reference.
11145
12251
  */
11146
12252
  #onViewportResize = () => {
12253
+ this.#publishVisualViewport();
12254
+ if (this.#dragging()) {
12255
+ return;
12256
+ }
11147
12257
  this.#restoreLauncherPosition();
12258
+ if (this.#drawer.isOpen()) {
12259
+ this.#drawer.setModal(!this.#threadsDock());
12260
+ this.#syncThreadsState();
12261
+ }
11148
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
+ }
11149
12300
  /** Mic button mount point (input row); the control mounts on connect when enabled. */
11150
12301
  #voiceSlot;
11151
12302
  /** Voice-input control; created on connect when transcription is available. */
@@ -11267,6 +12418,9 @@ var AgUiChat = class extends HTMLElement {
11267
12418
  },
11268
12419
  onDelete: (threadId) => {
11269
12420
  this.#deleteThread(threadId);
12421
+ },
12422
+ onVisibility: () => {
12423
+ this.#syncThreadsState();
11270
12424
  }
11271
12425
  });
11272
12426
  this.#checkpoints = new CheckpointMenu((runId, verb) => {
@@ -11355,11 +12509,15 @@ var AgUiChat = class extends HTMLElement {
11355
12509
  if (name === "placement") {
11356
12510
  this.#releaseOwnedAxes();
11357
12511
  this.#releaseLauncherPosition();
12512
+ if (!this.#collapsible() && this.collapsed) {
12513
+ this.setCollapsed(false);
12514
+ }
11358
12515
  requestAnimationFrame(() => this.#syncResizeAnchor());
11359
12516
  return;
11360
12517
  }
11361
12518
  if (name === "title-text") {
11362
12519
  this.#title.textContent = value ?? this.#strings.title;
12520
+ this.#railLabel.textContent = this.#title.textContent;
11363
12521
  return;
11364
12522
  }
11365
12523
  if (name === "user-key") {
@@ -11468,7 +12626,10 @@ var AgUiChat = class extends HTMLElement {
11468
12626
  const enabled = new Set(
11469
12627
  attr.split(",").map((token) => token.trim()).filter((token) => token !== "")
11470
12628
  );
11471
- 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
+ ];
11472
12633
  }
11473
12634
  /** All built-in (route + page + page-action + ask_user) frontend tools. */
11474
12635
  #builtinTools() {
@@ -11708,7 +12869,7 @@ var AgUiChat = class extends HTMLElement {
11708
12869
  });
11709
12870
  this.#strings = mergeUiStrings({ ...this.#readStringOverrides(), ...this.strings });
11710
12871
  if (this.getAttribute("data-theme-toggle") !== null) {
11711
- const saved = this.#readScopedItem(THEME_KEY);
12872
+ const saved = this.#readPreference(THEME_KEY);
11712
12873
  if (saved !== null) {
11713
12874
  this.setAttribute("theme", saved);
11714
12875
  }
@@ -11716,7 +12877,7 @@ var AgUiChat = class extends HTMLElement {
11716
12877
  this.#render();
11717
12878
  this.#drawer.setStrings(this.#strings);
11718
12879
  this.#checkpoints.setStrings(this.#strings);
11719
- if (this.#readScopedItem(COLLAPSED_KEY) === "1") {
12880
+ if (this.#collapsible() && this.#startsCollapsed()) {
11720
12881
  this.setAttribute("collapsed", "");
11721
12882
  }
11722
12883
  this.#syncLauncher();
@@ -11727,6 +12888,9 @@ var AgUiChat = class extends HTMLElement {
11727
12888
  this.conversationStore = this.#builtinStore;
11728
12889
  }
11729
12890
  window.addEventListener("resize", this.#onViewportResize);
12891
+ window.visualViewport?.addEventListener("resize", this.#onViewportResize);
12892
+ window.visualViewport?.addEventListener("scroll", this.#onViewportResize);
12893
+ this.#publishVisualViewport();
11730
12894
  this.#wireThreadStore();
11731
12895
  this.#wireAttachments();
11732
12896
  this.#wireVoice();
@@ -11790,6 +12954,8 @@ var AgUiChat = class extends HTMLElement {
11790
12954
  disconnectedCallback() {
11791
12955
  this.#connected = false;
11792
12956
  window.removeEventListener("resize", this.#onViewportResize);
12957
+ window.visualViewport?.removeEventListener("resize", this.#onViewportResize);
12958
+ window.visualViewport?.removeEventListener("scroll", this.#onViewportResize);
11793
12959
  if (this.#claimedNs !== null) {
11794
12960
  CLAIMED_NAMESPACES.delete(this.#claimedNs);
11795
12961
  this.#claimedNs = null;
@@ -11862,7 +13028,7 @@ var AgUiChat = class extends HTMLElement {
11862
13028
  this.#fileInput.accept = accept;
11863
13029
  this.#attachButton.hidden = false;
11864
13030
  this.#enableDragAndDrop();
11865
- this.#enablePaste();
13031
+ this.#enablePaste(tray);
11866
13032
  }
11867
13033
  /** The built-in multipart upload handler for `data-attachments-url`, or `null`. */
11868
13034
  #defaultUploadHandler(url) {
@@ -12097,6 +13263,62 @@ ${quoted}`;
12097
13263
  }
12098
13264
  });
12099
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
+ }
12100
13322
  /**
12101
13323
  * Accept files pasted into the composer.
12102
13324
  *
@@ -12110,7 +13332,7 @@ ${quoted}`;
12110
13332
  * clipboard, and swallowing the words someone meant to paste in order to
12111
13333
  * attach a picture they did not is the worse of the two failures.
12112
13334
  */
12113
- #enablePaste() {
13335
+ #enablePaste(tray) {
12114
13336
  this.#chat.addEventListener("paste", (event) => {
12115
13337
  const clipboard = event.clipboardData ?? null;
12116
13338
  if (clipboard === null) {
@@ -12118,6 +13340,7 @@ ${quoted}`;
12118
13340
  }
12119
13341
  const files = Array.from(clipboard.files);
12120
13342
  if (files.length === 0) {
13343
+ this.#pasteLongTextAsFile(event, clipboard, tray);
12121
13344
  return;
12122
13345
  }
12123
13346
  if (clipboard.getData("text/plain") === "") {
@@ -12278,7 +13501,13 @@ ${quoted}`;
12278
13501
  * per-tab, and emit a {@link TOGGLE_EVENT} so a host can mirror the state in
12279
13502
  * its own chrome.
12280
13503
  */
12281
- 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
+ }
12282
13511
  if (!collapsed) {
12283
13512
  this.#restoreLauncherPosition();
12284
13513
  }
@@ -12306,6 +13535,167 @@ ${quoted}`;
12306
13535
  get unread() {
12307
13536
  return this.#unread;
12308
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
+ }
12309
13699
  /** Flip the collapsed state. Bound to the built-in header toggle. */
12310
13700
  toggleCollapsed() {
12311
13701
  this.setCollapsed(!this.collapsed);
@@ -12319,7 +13709,7 @@ ${quoted}`;
12319
13709
  toggleTheme() {
12320
13710
  const next = this.getAttribute("theme") === "dark" ? "light" : "dark";
12321
13711
  this.setAttribute("theme", next);
12322
- writeStoredItem(this.#storageKey(THEME_KEY), next);
13712
+ this.#writePreference(THEME_KEY, next);
12323
13713
  this.#syncThemeGlyph();
12324
13714
  }
12325
13715
  /**
@@ -12447,9 +13837,81 @@ ${quoted}`;
12447
13837
  #launcherDraggable() {
12448
13838
  return this.getAttribute("data-launcher-drag") !== "false" && DRAGGABLE_PLACEMENTS.has(this.getAttribute("placement"));
12449
13839
  }
12450
- /** 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
+ */
12451
13853
  #viewport() {
12452
- 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
+ );
12453
13915
  }
12454
13916
  /**
12455
13917
  * The launcher's box in viewport coordinates, with its transform divided out.
@@ -12493,11 +13955,13 @@ ${quoted}`;
12493
13955
  return;
12494
13956
  }
12495
13957
  this.#launcherPos = at2;
13958
+ this.#panelPos = null;
12496
13959
  const panel = this.getBoundingClientRect();
12497
13960
  const placement = launcherPlacement(
12498
13961
  this.#launcherBox(),
12499
13962
  { width: panel.width, height: panel.height },
12500
- this.#viewport()
13963
+ this.#viewport(),
13964
+ this.#screen()
12501
13965
  );
12502
13966
  this.style.setProperty("--ag-ui-inset", placement.hostInset);
12503
13967
  this.style.setProperty("--ag-ui-launcher-inset", placement.launcherInset);
@@ -12514,13 +13978,141 @@ ${quoted}`;
12514
13978
  this.#moveLauncher(left, top);
12515
13979
  this.#storeLauncherPosition();
12516
13980
  }
12517
- /** Write the current launcher position, if this element owns one. */
13981
+ /**
13982
+ * Move the panel live during a header drag, without persisting.
13983
+ *
13984
+ * Only the host box is written, and that is the whole trick: the launcher is
13985
+ * positioned *inside* that box, so leaving its own inset alone carries it
13986
+ * along by exactly the distance the panel travelled -- which is what a person
13987
+ * dragging a window expects of the thing it collapses into. Placing it on the
13988
+ * panel's pinned corner instead, as an earlier version did, sent it leaping
13989
+ * across the panel the moment the drag re-picked that corner.
13990
+ *
13991
+ * The corner is therefore held for the length of the gesture. Both insets are
13992
+ * measured from it, and rewriting one of them from a new corner while the
13993
+ * other still names the old one would move the launcher for no reason.
13994
+ */
13995
+ #movePanel(box, from) {
13996
+ if (!this.#launcherDraggable()) {
13997
+ return { held: box, launcher: null };
13998
+ }
13999
+ if (this.#launcherPos === null) {
14000
+ const resting = this.#launcherBox();
14001
+ this.#launcherPos = { left: resting.left, top: resting.top };
14002
+ }
14003
+ const start = this.#launcherPos;
14004
+ const held = clampPanel(box, this.#viewport(), SCREEN_EDGE_MARGIN);
14005
+ const corner = this.#expandCorner ?? this.#anchor;
14006
+ const screen = this.#screen();
14007
+ this.style.setProperty(
14008
+ "--ag-ui-inset",
14009
+ [
14010
+ corner.y === "top" ? `${Math.round(held.top)}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",
14013
+ corner.x === "left" ? `${Math.round(held.left)}px` : "auto"
14014
+ ].join(" ")
14015
+ );
14016
+ this.#panelPos = { left: held.left, top: held.top };
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 };
14028
+ }
14029
+ /**
14030
+ * Finish a header drag: settle where both halves ended up, and remember it.
14031
+ *
14032
+ * The launcher travels the distance the panel actually travelled, which is
14033
+ * the clamped distance rather than the pointer's -- a panel held against the
14034
+ * viewport margin stops, and so does the bubble attached to it. Measured from
14035
+ * the box the press started on, so a long drag cannot accumulate the rounding
14036
+ * each move writes into the inset.
14037
+ *
14038
+ * Only now is the corner re-picked, from where the launcher has ended up, so
14039
+ * the panel opens into clear space next time. Re-picking it moves nothing:
14040
+ * both insets are rewritten from positions that are already decided.
14041
+ */
14042
+ #commitPanel(box, from) {
14043
+ const { held, launcher } = this.#movePanel(box, from);
14044
+ if (launcher === null) {
14045
+ return;
14046
+ }
14047
+ this.#placePanelAndLauncher(held, launcher);
14048
+ this.#storeLauncherPosition();
14049
+ }
14050
+ /**
14051
+ * Write both insets for a panel and launcher that are already positioned,
14052
+ * re-picking the corner they are measured from.
14053
+ */
14054
+ #placePanelAndLauncher(host, launcher) {
14055
+ const viewport = this.#viewport();
14056
+ const screen = this.#screen();
14057
+ const size = { width: host.right - host.left, height: host.bottom - host.top };
14058
+ const { corner } = launcherPlacement(launcher, size, viewport, screen);
14059
+ const insets = placeWidget(host, launcher, corner, screen);
14060
+ this.style.setProperty("--ag-ui-inset", insets.hostInset);
14061
+ this.style.setProperty("--ag-ui-launcher-inset", insets.launcherInset);
14062
+ this.#launcherPos = { left: launcher.left, top: launcher.top };
14063
+ this.#panelPos = { left: host.left, top: host.top };
14064
+ this.#expandCorner = corner;
14065
+ this.setAttribute("data-expand-corner", `${corner.y}-${corner.x}`);
14066
+ this.#syncResizeAnchor();
14067
+ }
14068
+ /**
14069
+ * Re-apply a panel position the user stated, against the current viewport.
14070
+ *
14071
+ * The launcher keeps its offset from the panel through the clamp -- it was
14072
+ * put where it is relative to the panel, and a viewport that has since shrunk
14073
+ * is no reason to move one without the other -- and is then held on screen in
14074
+ * its own right.
14075
+ */
14076
+ #restorePanelPosition(at2) {
14077
+ if (!this.#launcherDraggable()) {
14078
+ return;
14079
+ }
14080
+ const rect = this.getBoundingClientRect();
14081
+ const held = clampPanel(
14082
+ { left: at2.left, top: at2.top, right: at2.left + rect.width, bottom: at2.top + rect.height },
14083
+ this.#viewport(),
14084
+ SCREEN_EDGE_MARGIN
14085
+ );
14086
+ const launcher = this.#launcherBox();
14087
+ const carried = {
14088
+ ...launcher,
14089
+ left: launcher.left + (held.left - at2.left),
14090
+ top: launcher.top + (held.top - at2.top)
14091
+ };
14092
+ this.#placePanelAndLauncher(held, {
14093
+ ...carried,
14094
+ ...clampLauncher(carried, this.#viewport())
14095
+ });
14096
+ }
14097
+ /**
14098
+ * Write the current position, if this element owns one.
14099
+ *
14100
+ * The panel's own position rides along only when the user stated it, because
14101
+ * its presence is what tells a restore which of the two gestures to honour:
14102
+ * with it, the panel goes back where it was put; without it, the panel is
14103
+ * re-derived from the launcher and opens into whatever room the viewport has
14104
+ * now.
14105
+ */
12518
14106
  #storeLauncherPosition() {
12519
14107
  const position = this.#launcherPos;
12520
14108
  if (position === null) {
12521
14109
  return;
12522
14110
  }
12523
- writeStoredItem(this.#storageKey(LAUNCHER_KEY), JSON.stringify(position));
14111
+ const panel = this.#panelPos;
14112
+ this.#writePreference(
14113
+ LAUNCHER_KEY,
14114
+ JSON.stringify(panel === null ? position : { ...position, panel })
14115
+ );
12524
14116
  }
12525
14117
  /**
12526
14118
  * Re-apply the dragged position against the current viewport: on connect,
@@ -12529,18 +14121,25 @@ ${quoted}`;
12529
14121
  * unreachable -- it is the only way back to a collapsed conversation.
12530
14122
  */
12531
14123
  #restoreLauncherPosition() {
12532
- const stored = this.#launcherPos ?? this.#readLauncherPosition();
12533
- if (stored === null) {
14124
+ const stored = this.#readLauncherPosition();
14125
+ const launcher = this.#launcherPos ?? stored;
14126
+ if (launcher === null) {
14127
+ return;
14128
+ }
14129
+ const panel = this.#panelPos ?? stored?.panel ?? null;
14130
+ if (panel !== null) {
14131
+ this.#launcherPos = { left: launcher.left, top: launcher.top };
14132
+ this.#restorePanelPosition(panel);
12534
14133
  return;
12535
14134
  }
12536
14135
  const box = this.#launcherBox();
12537
14136
  this.#applyLauncherPlacement(
12538
- clampLauncher({ ...box, left: stored.left, top: stored.top }, this.#viewport())
14137
+ clampLauncher({ ...box, left: launcher.left, top: launcher.top }, this.#viewport())
12539
14138
  );
12540
14139
  }
12541
- /** The persisted launcher position for this instance, or null. */
14140
+ /** The persisted position for this instance, or null. */
12542
14141
  #readLauncherPosition() {
12543
- const raw = this.#readScopedItem(LAUNCHER_KEY);
14142
+ const raw = this.#readPreference(LAUNCHER_KEY);
12544
14143
  if (raw === null) {
12545
14144
  return null;
12546
14145
  }
@@ -12549,8 +14148,12 @@ ${quoted}`;
12549
14148
  if (typeof parsed !== "object" || parsed === null) {
12550
14149
  return null;
12551
14150
  }
12552
- const { left, top } = parsed;
12553
- return typeof left === "number" && typeof top === "number" ? { left, top } : null;
14151
+ const { left, top, panel } = parsed;
14152
+ if (typeof left !== "number" || typeof top !== "number") {
14153
+ return null;
14154
+ }
14155
+ const at2 = asPoint(panel);
14156
+ return at2 === null ? { left, top } : { left, top, panel: at2 };
12554
14157
  } catch {
12555
14158
  return null;
12556
14159
  }
@@ -12565,6 +14168,7 @@ ${quoted}`;
12565
14168
  return;
12566
14169
  }
12567
14170
  this.#launcherPos = null;
14171
+ this.#panelPos = null;
12568
14172
  this.#expandCorner = null;
12569
14173
  this.style.removeProperty("--ag-ui-inset");
12570
14174
  this.style.removeProperty("--ag-ui-launcher-inset");
@@ -12585,19 +14189,20 @@ ${quoted}`;
12585
14189
  * drags the edge it was holding.
12586
14190
  */
12587
14191
  #applyResize(grip, box) {
14192
+ box = this.#withinViewport(box);
12588
14193
  this.#applySize({ width: box.right - box.left, height: box.bottom - box.top });
12589
14194
  if (grip.x !== this.#anchor.x && grip.y !== this.#anchor.y) {
12590
- return;
14195
+ return box;
12591
14196
  }
12592
- const viewport = this.#viewport();
14197
+ const screen = this.#screen();
12593
14198
  const anchor = this.#anchor;
12594
14199
  const side = (value) => `${Math.round(value)}px`;
12595
14200
  this.style.setProperty(
12596
14201
  "--ag-ui-inset",
12597
14202
  [
12598
14203
  anchor.y === "top" ? side(box.top) : "auto",
12599
- anchor.x === "right" ? side(viewport.width - box.right) : "auto",
12600
- 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",
12601
14206
  anchor.x === "left" ? side(box.left) : "auto"
12602
14207
  ].join(" ")
12603
14208
  );
@@ -12608,11 +14213,15 @@ ${quoted}`;
12608
14213
  top: anchor.y === "top" ? box.top : box.bottom - size
12609
14214
  };
12610
14215
  }
14216
+ if (this.#panelPos !== null) {
14217
+ this.#panelPos = { left: box.left, top: box.top };
14218
+ }
14219
+ return box;
12611
14220
  }
12612
14221
  /** Finish a resize: keep the box, remember it, and re-read the pinned edges. */
12613
14222
  #commitResize(grip, box) {
12614
- this.#applyResize(grip, box);
12615
- 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 });
12616
14225
  this.#storeLauncherPosition();
12617
14226
  this.#syncResizeAnchor();
12618
14227
  }
@@ -12639,11 +14248,11 @@ ${quoted}`;
12639
14248
  /** Persist a dragged size per tab, alongside the collapsed/theme preferences. */
12640
14249
  #persistSize(size) {
12641
14250
  const stored = { ...this.#readSize(), ...size };
12642
- writeStoredItem(this.#storageKey(SIZE_KEY), JSON.stringify(stored));
14251
+ this.#writePreference(SIZE_KEY, JSON.stringify(stored));
12643
14252
  }
12644
14253
  /** The persisted size for this instance, or an empty record. */
12645
14254
  #readSize() {
12646
- const raw = this.#readScopedItem(SIZE_KEY);
14255
+ const raw = this.#readPreference(SIZE_KEY);
12647
14256
  if (raw === null) {
12648
14257
  return {};
12649
14258
  }
@@ -12770,10 +14379,81 @@ ${quoted}`;
12770
14379
  }
12771
14380
  return sessionStorage.getItem(base);
12772
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
+ }
12773
14451
  /** Reflect the current theme on the toggle: show the destination's glyph. */
12774
14452
  #syncThemeGlyph() {
12775
14453
  const dark = this.getAttribute("theme") === "dark";
12776
- 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
+ );
12777
14457
  }
12778
14458
  /**
12779
14459
  * Open the thread-history drawer: the imperative route to the control that
@@ -12787,7 +14467,37 @@ ${quoted}`;
12787
14467
  openThreads() {
12788
14468
  this.#checkpoints.close();
12789
14469
  void this.#refreshDrawer();
14470
+ this.#drawer.setModal(!this.#threadsDock());
12790
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
+ }
12791
14501
  }
12792
14502
  /**
12793
14503
  * Open the checkpoints panel (the `::part(checkpoints-button)` route).
@@ -12801,6 +14511,16 @@ ${quoted}`;
12801
14511
  void this.#refreshCheckpoints();
12802
14512
  this.#checkpoints.open();
12803
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
+ }
12804
14524
  /** Close the checkpoints panel, if it is open. */
12805
14525
  closeCheckpoints() {
12806
14526
  this.#checkpoints.close();
@@ -12841,6 +14561,8 @@ ${quoted}`;
12841
14561
  this.#client = null;
12842
14562
  this.#clearTranscript();
12843
14563
  this.#initialMessages = [];
14564
+ this.#sentDrafts.length = 0;
14565
+ this.#recallIndex = null;
12844
14566
  }
12845
14567
  /**
12846
14568
  * Wipe the rendered transcript and everything that indexes into it.
@@ -13174,6 +14896,12 @@ ${quoted}`;
13174
14896
  }
13175
14897
  controls.append(collapse2);
13176
14898
  header.append(title, headerActions, controls);
14899
+ enablePanelDrag(header, {
14900
+ enabled: () => !this.collapsed && this.#launcherDraggable(),
14901
+ rect: () => this.getBoundingClientRect(),
14902
+ apply: (box, from) => this.#movePanel(box, from),
14903
+ commit: (box, from) => this.#commitPanel(box, from)
14904
+ });
13177
14905
  this.#messages.className = "messages";
13178
14906
  this.#messages.setAttribute("part", "messages");
13179
14907
  this.#messages.setAttribute("role", "log");
@@ -13216,7 +14944,16 @@ ${quoted}`;
13216
14944
  this.#emptyWrap.setAttribute("part", "empty");
13217
14945
  const emptySlot = document.createElement("slot");
13218
14946
  emptySlot.name = "empty";
14947
+ const starters = this.#starterChips();
14948
+ if (starters !== null) {
14949
+ emptySlot.append(starters);
14950
+ }
13219
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;
13220
14957
  this.#messages.append(this.#emptyWrap);
13221
14958
  const inputRow = document.createElement("div");
13222
14959
  inputRow.className = "input-row";
@@ -13282,6 +15019,7 @@ ${quoted}`;
13282
15019
  this.#skillsMenu.palette,
13283
15020
  this.#skillsMenu.chips,
13284
15021
  this.#skillHint,
15022
+ this.#queuedRow,
13285
15023
  this.#attachSlot,
13286
15024
  inputRow,
13287
15025
  footer,
@@ -13306,8 +15044,13 @@ ${quoted}`;
13306
15044
  this.#badge.setAttribute("part", "launcher-badge");
13307
15045
  this.#badge.setAttribute("aria-hidden", "true");
13308
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;
13309
15051
  this.#launcher.append(
13310
15052
  this.#iconElement("launcher", "launcher-icon", ICON_LAUNCHER, this.#launcherIconUrl()),
15053
+ this.#railLabel,
13311
15054
  this.#badge
13312
15055
  );
13313
15056
  this.#launcher.addEventListener("click", () => this.setCollapsed(false));
@@ -13333,7 +15076,9 @@ ${quoted}`;
13333
15076
  }
13334
15077
  this.#focusableGrip();
13335
15078
  this.#adoptStyles();
13336
- 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);
13337
15082
  }
13338
15083
  /**
13339
15084
  * Attach the stylesheet without an inline `<style>` element.
@@ -13512,6 +15257,37 @@ ${quoted}`;
13512
15257
  this.#setUnread(this.#unread + 1);
13513
15258
  }
13514
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
+ }
13515
15291
  #updateEmptyState() {
13516
15292
  this.#emptyWrap.hidden = this.#messages.childElementCount > 1;
13517
15293
  }
@@ -13520,6 +15296,7 @@ ${quoted}`;
13520
15296
  this.#skillsMenu.onInput(this.#input.value);
13521
15297
  this.#skillHint.hidden = true;
13522
15298
  this.#autoGrow();
15299
+ this.#recallIndex = null;
13523
15300
  }
13524
15301
  #onKeydown(event) {
13525
15302
  if (this.#skillsMenu.onKeydown(event)) {
@@ -13534,7 +15311,44 @@ ${quoted}`;
13534
15311
  if (event.key === "Enter" && !event.shiftKey) {
13535
15312
  event.preventDefault();
13536
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;
13537
15342
  }
15343
+ const next = this.#recallIndex === null ? 0 : this.#recallIndex + (back ? 1 : -1);
15344
+ if (next >= drafts.length) {
15345
+ return;
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();
13538
15352
  }
13539
15353
  /**
13540
15354
  * Stop the in-flight run: decline any confirmation card awaiting a decision
@@ -13543,6 +15357,13 @@ ${quoted}`;
13543
15357
  * observes the disconnect).
13544
15358
  */
13545
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();
13546
15367
  this.#confirmAbort?.abort();
13547
15368
  this.#client?.cancel();
13548
15369
  }
@@ -13554,11 +15375,54 @@ ${quoted}`;
13554
15375
  * that it carries no text.
13555
15376
  */
13556
15377
  #setRunning(running) {
15378
+ const settled = this.#running && !running;
13557
15379
  this.#running = running;
13558
15380
  const label = running ? this.#strings.stop : this.#strings.send;
13559
15381
  this.#send.title = label;
13560
15382
  this.#send.setAttribute("aria-label", label);
13561
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
+ }
13562
15426
  }
13563
15427
  /**
13564
15428
  * Size the field to its content: one row when empty, growing with what is
@@ -13573,14 +15437,24 @@ ${quoted}`;
13573
15437
  this.#input.style.height = `${this.#input.scrollHeight}px`;
13574
15438
  }
13575
15439
  async #submit() {
13576
- if (this.#running) {
13577
- return;
13578
- }
13579
15440
  const content = this.#input.value.trim();
13580
15441
  const attachments = this.#attachTray?.readyRefs() ?? [];
13581
15442
  if (content === "" && attachments.length === 0) {
13582
15443
  return;
13583
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;
13584
15458
  this.#input.value = "";
13585
15459
  this.#autoGrow();
13586
15460
  if (this.#attachTray?.hasPending() === true) {
@@ -14366,11 +16240,33 @@ ${quoted}`;
14366
16240
  this.#appendNotice("\u2728", this.#strings.usingSkill.replace("{name}", skill), "skill");
14367
16241
  return true;
14368
16242
  }
14369
- #appendNotice(icon, text4, kind) {
14370
- 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));
14371
16252
  this.#updateEmptyState();
14372
16253
  this.#scroller.follow();
14373
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
+ }
14374
16270
  /**
14375
16271
  * Turn on chart rendering, by whichever route this consumer wants.
14376
16272
  *
@@ -14643,13 +16539,14 @@ function setControlValue(el2, value) {
14643
16539
  }
14644
16540
 
14645
16541
  // src/version.ts
14646
- var VERSION = "0.33.1";
16542
+ var VERSION = "0.35.0";
14647
16543
  export {
14648
16544
  ATTACHMENT_EVENT,
14649
16545
  AgUiChat,
14650
16546
  AgUiClient,
14651
16547
  CHART_ACTIVITY_TYPE,
14652
16548
  CHART_TOOL_NAME,
16549
+ CHAT_CORNERS,
14653
16550
  COMPACTION_ACTIVITY_TYPE,
14654
16551
  CUSTOM_AGENT_EVENT,
14655
16552
  CheckpointMenu,
@@ -14690,6 +16587,7 @@ export {
14690
16587
  attachQuoteOffer,
14691
16588
  chartSpecFrom,
14692
16589
  clickElement,
16590
+ createChatSurfaceTools,
14693
16591
  createHttpAgent,
14694
16592
  createPageActionTools,
14695
16593
  createPageMapContext,
@@ -14701,6 +16599,7 @@ export {
14701
16599
  flash,
14702
16600
  focusWithFlash,
14703
16601
  highlightThenClick,
16602
+ isChatCorner,
14704
16603
  isDestructive,
14705
16604
  isNavigates,
14706
16605
  mergeUiStrings,
@@ -14725,6 +16624,7 @@ export {
14725
16624
  setControlValue,
14726
16625
  setNativeChecked,
14727
16626
  setNativeValue,
16627
+ showHighlightOverlay,
14728
16628
  suggestionPrompts,
14729
16629
  toggleCheckbox,
14730
16630
  toggleControl,