@syntrologie/adapt-chatbot 2.8.0-canary.31 → 2.8.0-canary.310

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 (126) hide show
  1. package/dist/AdaptiveChatBar.d.ts +85 -0
  2. package/dist/AdaptiveChatBar.d.ts.map +1 -0
  3. package/dist/AdaptiveChatBar.js +10 -0
  4. package/dist/AdaptiveChatBar.js.map +7 -0
  5. package/dist/AdaptiveChatBarMountable.d.ts +35 -0
  6. package/dist/AdaptiveChatBarMountable.d.ts.map +1 -0
  7. package/dist/AdaptiveChatTrail.d.ts +85 -0
  8. package/dist/AdaptiveChatTrail.d.ts.map +1 -0
  9. package/dist/AdaptiveChatTrail.js +9 -0
  10. package/dist/AdaptiveChatTrail.js.map +7 -0
  11. package/dist/AdaptiveChipsStrip.d.ts +1150 -0
  12. package/dist/AdaptiveChipsStrip.d.ts.map +1 -0
  13. package/dist/AdaptiveChipsStrip.js +11 -0
  14. package/dist/AdaptiveChipsStrip.js.map +7 -0
  15. package/dist/AdaptiveChipsStripMountable.d.ts +24 -0
  16. package/dist/AdaptiveChipsStripMountable.d.ts.map +1 -0
  17. package/dist/ChatAssistantLit.d.ts +60 -0
  18. package/dist/ChatAssistantLit.d.ts.map +1 -0
  19. package/dist/ChatAssistantLit.js +12 -0
  20. package/dist/ChatAssistantLit.js.map +7 -0
  21. package/dist/ChatSession.d.ts +178 -0
  22. package/dist/ChatSession.d.ts.map +1 -0
  23. package/dist/ChatTransport.d.ts +283 -0
  24. package/dist/ChatTransport.d.ts.map +1 -0
  25. package/dist/NavLinkMountable.d.ts +25 -0
  26. package/dist/NavLinkMountable.d.ts.map +1 -0
  27. package/dist/NavLinkMountable.test.d.ts +2 -0
  28. package/dist/NavLinkMountable.test.d.ts.map +1 -0
  29. package/dist/TextAnswerMountable.d.ts +17 -0
  30. package/dist/TextAnswerMountable.d.ts.map +1 -0
  31. package/dist/Turnstile.d.ts +83 -0
  32. package/dist/Turnstile.d.ts.map +1 -0
  33. package/dist/cdn.d.ts +2 -6
  34. package/dist/cdn.d.ts.map +1 -1
  35. package/dist/chunk-2ZUJ7DZI.js +8770 -0
  36. package/dist/chunk-2ZUJ7DZI.js.map +7 -0
  37. package/dist/chunk-BNN5YLN3.js +417 -0
  38. package/dist/chunk-BNN5YLN3.js.map +7 -0
  39. package/dist/chunk-FVNSOP7B.js +634 -0
  40. package/dist/chunk-FVNSOP7B.js.map +7 -0
  41. package/dist/chunk-ONGGPQER.js +3722 -0
  42. package/dist/chunk-ONGGPQER.js.map +7 -0
  43. package/dist/chunk-SCVTTLFJ.js +334 -0
  44. package/dist/chunk-SCVTTLFJ.js.map +7 -0
  45. package/dist/chunk-UVKRO5ER.js +13 -0
  46. package/dist/chunk-UVKRO5ER.js.map +7 -0
  47. package/dist/chunk-VLJ3WOEX.js +231 -0
  48. package/dist/chunk-VLJ3WOEX.js.map +7 -0
  49. package/dist/editor-lit.d.ts +37 -0
  50. package/dist/editor-lit.d.ts.map +1 -0
  51. package/dist/editor-lit.js +134 -0
  52. package/dist/editor-lit.js.map +7 -0
  53. package/dist/elements/ActionHandler.d.ts +34 -0
  54. package/dist/elements/ActionHandler.d.ts.map +1 -0
  55. package/dist/elements/ElementInstanceStore.d.ts +155 -0
  56. package/dist/elements/ElementInstanceStore.d.ts.map +1 -0
  57. package/dist/elements/ElementInstanceStore.test.d.ts +2 -0
  58. package/dist/elements/ElementInstanceStore.test.d.ts.map +1 -0
  59. package/dist/elements/ElementTypeHandler.d.ts +77 -0
  60. package/dist/elements/ElementTypeHandler.d.ts.map +1 -0
  61. package/dist/elements/ItemHandler.d.ts +60 -0
  62. package/dist/elements/ItemHandler.d.ts.map +1 -0
  63. package/dist/elements/ItemHandler.test.d.ts +2 -0
  64. package/dist/elements/ItemHandler.test.d.ts.map +1 -0
  65. package/dist/elements/TileHandler.d.ts +52 -0
  66. package/dist/elements/TileHandler.d.ts.map +1 -0
  67. package/dist/elements/blockRenderer.d.ts +46 -0
  68. package/dist/elements/blockRenderer.d.ts.map +1 -0
  69. package/dist/elements/blockRenderer.test.d.ts +13 -0
  70. package/dist/elements/blockRenderer.test.d.ts.map +1 -0
  71. package/dist/elements/blocks.d.ts +58 -0
  72. package/dist/elements/blocks.d.ts.map +1 -0
  73. package/dist/elements/envelope.d.ts +24 -0
  74. package/dist/elements/envelope.d.ts.map +1 -0
  75. package/dist/elements/fetcher.d.ts +40 -0
  76. package/dist/elements/fetcher.d.ts.map +1 -0
  77. package/dist/elements/index.d.ts +32 -0
  78. package/dist/elements/index.d.ts.map +1 -0
  79. package/dist/elements/types.d.ts +106 -0
  80. package/dist/elements/types.d.ts.map +1 -0
  81. package/dist/observer/__tests__/allowlist.test.d.ts +9 -0
  82. package/dist/observer/__tests__/allowlist.test.d.ts.map +1 -0
  83. package/dist/observer/__tests__/observer-isolation.test.d.ts +13 -0
  84. package/dist/observer/__tests__/observer-isolation.test.d.ts.map +1 -0
  85. package/dist/observer/__tests__/queue.test.d.ts +2 -0
  86. package/dist/observer/__tests__/queue.test.d.ts.map +1 -0
  87. package/dist/observer/__tests__/transport.test.d.ts +2 -0
  88. package/dist/observer/__tests__/transport.test.d.ts.map +1 -0
  89. package/dist/observer/allowlist.d.ts +32 -0
  90. package/dist/observer/allowlist.d.ts.map +1 -0
  91. package/dist/observer/index.d.ts +35 -0
  92. package/dist/observer/index.d.ts.map +1 -0
  93. package/dist/observer/queue.d.ts +57 -0
  94. package/dist/observer/queue.d.ts.map +1 -0
  95. package/dist/observer/transport.d.ts +26 -0
  96. package/dist/observer/transport.d.ts.map +1 -0
  97. package/dist/runtime-lit.d.ts +8 -0
  98. package/dist/runtime-lit.d.ts.map +1 -0
  99. package/dist/runtime.d.ts +8 -3
  100. package/dist/runtime.d.ts.map +1 -1
  101. package/dist/runtime.js +1649 -29
  102. package/dist/runtime.js.map +7 -0
  103. package/dist/schema.d.ts +3298 -11
  104. package/dist/schema.d.ts.map +1 -1
  105. package/dist/schema.js +126 -31
  106. package/dist/schema.js.map +7 -0
  107. package/dist/types.d.ts +47 -31
  108. package/dist/types.d.ts.map +1 -1
  109. package/package.json +50 -19
  110. package/dist/ChatAssistant.d.ts +0 -18
  111. package/dist/ChatAssistant.d.ts.map +0 -1
  112. package/dist/ChatAssistant.js +0 -178
  113. package/dist/actionParser.d.ts +0 -15
  114. package/dist/actionParser.d.ts.map +0 -1
  115. package/dist/actionParser.js +0 -52
  116. package/dist/apiClient.d.ts +0 -23
  117. package/dist/apiClient.d.ts.map +0 -1
  118. package/dist/apiClient.js +0 -51
  119. package/dist/cdn.js +0 -37
  120. package/dist/editor.d.ts +0 -17
  121. package/dist/editor.d.ts.map +0 -1
  122. package/dist/editor.js +0 -36
  123. package/dist/types.js +0 -6
  124. package/dist/useChat.d.ts +0 -25
  125. package/dist/useChat.d.ts.map +0 -1
  126. package/dist/useChat.js +0 -106
@@ -0,0 +1,417 @@
1
+ import {
2
+ renderMarkdown
3
+ } from "./chunk-ONGGPQER.js";
4
+
5
+ // src/AdaptiveChatTrail.ts
6
+ import { html, LitElement, nothing } from "lit";
7
+ import { styleMap } from "lit/directives/style-map.js";
8
+ import { unsafeHTML } from "lit/directives/unsafe-html.js";
9
+ var DEFAULT_VISIBLE = 3;
10
+ var OPACITY_STEP = 0.22;
11
+ var OPACITY_FLOOR = 0.18;
12
+ var Y_DRIFT_PX = -2;
13
+ var TRAIL_BLUR_VEIL_PX = 72;
14
+ var TRAIL_BLUR_MAX_PX = 5;
15
+ var AdaptiveChatTrail = class extends LitElement {
16
+ constructor() {
17
+ super(...arguments);
18
+ this.messages = [];
19
+ this.visibleCount = DEFAULT_VISIBLE;
20
+ this.expanded = false;
21
+ this.forceExpanded = false;
22
+ /**
23
+ * Pre-conversation phantom assistant message. Rendered only when
24
+ * `messages` is empty. NOT injected into chatSession state — the
25
+ * greeting is configuration, not conversation, so it disappears
26
+ * automatically when the first real message arrives.
27
+ */
28
+ this.greeting = void 0;
29
+ this._onExpand = () => {
30
+ this.expanded = true;
31
+ this.dispatchEvent(new CustomEvent("trail-expand", { bubbles: true, composed: true }));
32
+ };
33
+ /**
34
+ * Approve a pending client tool call. The trail emits a generic
35
+ * event so the host can decide whether to forward to chatSession
36
+ * (the common path) or override. Keeps the trail itself free of
37
+ * direct chatSession coupling — it's a pure view component.
38
+ */
39
+ this._onToolCallClick = (tc) => {
40
+ if (tc.status !== "pending") return;
41
+ this.dispatchEvent(
42
+ new CustomEvent("trail-toolcall-approved", {
43
+ detail: { toolCallId: tc.id, approved: true },
44
+ bubbles: true,
45
+ composed: true
46
+ })
47
+ );
48
+ };
49
+ this._onCollapse = () => {
50
+ this.expanded = false;
51
+ this.dispatchEvent(new CustomEvent("trail-collapse", { bubbles: true, composed: true }));
52
+ };
53
+ }
54
+ createRenderRoot() {
55
+ return this;
56
+ }
57
+ connectedCallback() {
58
+ super.connectedCallback();
59
+ ensureStreamingKeyframes();
60
+ }
61
+ updated(changed) {
62
+ if (changed.has("expanded") || changed.has("messages")) {
63
+ requestAnimationFrame(() => {
64
+ const container = this.querySelector("[data-syntro-chat-trail]");
65
+ if (!container) return;
66
+ const distanceFromBottom = container.scrollHeight - container.scrollTop - container.clientHeight;
67
+ if (distanceFromBottom < 200 || changed.has("expanded")) {
68
+ container.scrollTop = container.scrollHeight;
69
+ }
70
+ });
71
+ }
72
+ }
73
+ render() {
74
+ if (this.messages.length === 0 && !this.greeting) return nothing;
75
+ if (this.messages.length === 0 && this.greeting) {
76
+ return html`<div data-syntro-chat-trail style=${styleMap({
77
+ display: "flex",
78
+ flexDirection: "column",
79
+ gap: "6px",
80
+ justifyContent: "flex-end",
81
+ width: "100%",
82
+ padding: "0",
83
+ pointerEvents: "auto"
84
+ })}>
85
+ <div
86
+ data-trail-chip
87
+ data-role="assistant"
88
+ data-status="greeting"
89
+ style=${styleMap(chipStyles("assistant", 0, { isStreaming: false, isError: false }))}
90
+ >${unsafeHTML(renderMarkdown(stripTrailingWhitespace(this.greeting)))}</div>
91
+ </div>`;
92
+ }
93
+ const visible = this.expanded || this.forceExpanded ? this.messages : this.messages.slice(-this.visibleCount);
94
+ const hidden = this.messages.length - visible.length;
95
+ const baseStyles = {
96
+ display: "flex",
97
+ flexDirection: "column",
98
+ gap: "6px",
99
+ // NO `justifyContent: flex-end` here — combined with `overflow: auto`
100
+ // it's a known cross-browser bug: oversized flex children get
101
+ // pushed ABOVE the container without contributing to scrollHeight,
102
+ // so the scrollbar never appears. We rely on `:first-child {
103
+ // margin-top: auto }` (injected as global CSS) to push short
104
+ // content to the bottom of the box while leaving overflow handling
105
+ // to the native scroll container.
106
+ width: "100%",
107
+ // The trail itself has no chrome — chips sit on the host page bg.
108
+ padding: "0",
109
+ pointerEvents: "auto"
110
+ };
111
+ const modeStyles = this.forceExpanded ? {
112
+ // Full-screen host: fill all available vertical space and
113
+ // scroll the overflow. ``flex: 1 1 0`` (basis 0, not auto)
114
+ // makes the trail shrink to 0 first when the parent runs
115
+ // out of space — so the header above and the input row
116
+ // below stay pinned at their natural sizes instead of one
117
+ // of them being pushed off-screen.
118
+ flex: "1 1 0",
119
+ minHeight: "0",
120
+ overflowY: "auto",
121
+ // ``contain`` prevents scroll from bubbling out to the host
122
+ // page when the trail hits top or bottom. Required for the
123
+ // panel-scroll-leak regression test in
124
+ // SyntroBottomSheet.test.ts.
125
+ overscrollBehavior: "contain",
126
+ paddingTop: "8px",
127
+ borderTop: "var(--sc-content-bubble-border, 1px solid rgba(255, 255, 255, 0.18))",
128
+ scrollBehavior: "smooth",
129
+ scrollbarWidth: "thin"
130
+ } : this.expanded ? {
131
+ maxHeight: "min(320px, 40vh)",
132
+ overflowY: "auto",
133
+ paddingTop: "8px",
134
+ borderTop: "var(--sc-content-bubble-border, 1px solid rgba(255, 255, 255, 0.18))",
135
+ scrollBehavior: "smooth",
136
+ scrollbarWidth: "thin"
137
+ } : {
138
+ // Collapsed mode = ambient peek surface. Never a scrollbar:
139
+ // long messages slide up behind the blur veil instead of
140
+ // exposing a scrollbar. The "↑ N more · expand" affordance
141
+ // is the only entry into history; clicking it switches to
142
+ // expanded mode where scroll IS allowed.
143
+ // Height kept short so the trail reads as a peek strip above
144
+ // the chat bar, not a panel — long single replies fade into
145
+ // the blur veil at the top, user expands to read the rest.
146
+ maxHeight: "min(140px, 22vh)",
147
+ overflow: "hidden"
148
+ };
149
+ const containerStyles = { ...baseStyles, ...modeStyles };
150
+ const frameStyles = this.forceExpanded ? {
151
+ position: "relative",
152
+ width: "100%",
153
+ display: "flex",
154
+ flexDirection: "column",
155
+ flex: "1 1 0",
156
+ minHeight: "0"
157
+ } : {
158
+ position: "relative",
159
+ width: "100%"
160
+ };
161
+ return html`
162
+ ${// Single toggle button above the frame. Same DOM node morphs:
163
+ // collapsed → "↑ N more · expand" / "↑ expand" (calls _onExpand)
164
+ // expanded → "⌄ minimize" (calls _onCollapse)
165
+ // Lives OUTSIDE the frame so the absolute blur veil (top:0,
166
+ // z-index:2 inside the frame) doesn't cover it. Shown whenever
167
+ // there's anything in the trail — the user expects this affordance
168
+ // available regardless of how many chips are on screen.
169
+ // When ``forceExpanded`` is set by the host (full-screen panel,
170
+ // etc.), there's nowhere to collapse INTO and the affordance
171
+ // would just confuse the user — suppress it entirely.
172
+ this.messages.length === 0 || this.forceExpanded ? nothing : this.expanded ? html`<button
173
+ type="button"
174
+ data-trail-toggle
175
+ data-trail-collapse
176
+ @click=${this._onCollapse}
177
+ style=${styleMap(moreStyles())}
178
+ >⌄ minimize</button>` : html`<button
179
+ type="button"
180
+ data-trail-toggle
181
+ data-trail-more
182
+ @click=${this._onExpand}
183
+ style=${styleMap(moreStyles())}
184
+ >${hidden > 0 ? html`↑ ${hidden} more · expand` : html`↑ expand`}</button>`}
185
+ <div data-syntro-chat-trail-frame style=${styleMap(frameStyles)}>
186
+ <div data-syntro-chat-trail style=${styleMap(containerStyles)}>
187
+ ${visible.map((m, i) => {
188
+ const pos = this.expanded || this.forceExpanded ? 0 : visible.length - 1 - i;
189
+ const isStreaming = m.status === "streaming";
190
+ const isError = m.status === "error" || m.role === "system";
191
+ const toolCalls = m.toolCalls ?? [];
192
+ const renderedText = m.role === "assistant" ? html`${unsafeHTML(renderMarkdown(stripTrailingWhitespace(m.text)))}` : html`${m.text}`;
193
+ return html`<div
194
+ data-trail-chip
195
+ data-role=${m.role}
196
+ data-status=${m.status ?? "complete"}
197
+ style=${styleMap(chipStyles(m.role, pos, { isStreaming, isError }))}
198
+ >${renderedText}${isStreaming ? html`<span
199
+ data-trail-caret
200
+ aria-hidden="true"
201
+ style=${styleMap(caretStyles())}
202
+ ></span>` : nothing}${toolCalls.length > 0 ? html`<div data-trail-toolcalls style=${styleMap(toolCallStripStyles())}>
203
+ ${toolCalls.map(
204
+ (tc) => html`<button
205
+ type="button"
206
+ data-trail-toolcall
207
+ data-tool-id=${tc.id}
208
+ data-tool-status=${tc.status}
209
+ @click=${() => this._onToolCallClick(tc)}
210
+ ?disabled=${tc.status !== "pending"}
211
+ style=${styleMap(toolCallChipStyles(tc.status))}
212
+ title="${tc.name} · ${tc.status}"
213
+ >${toolCallIcon(tc.status)} ${tc.name}</button>`
214
+ )}
215
+ </div>` : nothing}</div>`;
216
+ })}
217
+ </div>
218
+ ${// The blur veil only fires in collapsed (ambient) mode.
219
+ // Expanded = the user explicitly asked for history; blurring
220
+ // the top of the panel would hide content (including the
221
+ // minimize button just above the trail's top edge) the user
222
+ // came here to see.
223
+ this.expanded || this.forceExpanded ? nothing : html`<div
224
+ data-trail-blur-veil
225
+ aria-hidden="true"
226
+ style=${styleMap(blurVeilStyles())}
227
+ ></div>`}
228
+ </div>
229
+ `;
230
+ }
231
+ };
232
+ AdaptiveChatTrail.properties = {
233
+ messages: { attribute: false },
234
+ visibleCount: { type: Number },
235
+ expanded: { type: Boolean },
236
+ greeting: { type: String },
237
+ /** When true: trail renders fully expanded AND the
238
+ * expand/collapse affordance is hidden. Used by hosts that mount
239
+ * the chat in a full-screen surface (the mobile panel) where
240
+ * collapse makes no sense — there's nothing to collapse INTO. */
241
+ forceExpanded: { type: Boolean }
242
+ };
243
+ function blurVeilStyles() {
244
+ const mask = "linear-gradient(to bottom, black 0%, black 40%, transparent 100%)";
245
+ return {
246
+ position: "absolute",
247
+ top: "0",
248
+ left: "0",
249
+ right: "0",
250
+ height: `${TRAIL_BLUR_VEIL_PX}px`,
251
+ pointerEvents: "none",
252
+ backdropFilter: `blur(${TRAIL_BLUR_MAX_PX}px)`,
253
+ WebkitBackdropFilter: `blur(${TRAIL_BLUR_MAX_PX}px)`,
254
+ maskImage: mask,
255
+ WebkitMaskImage: mask,
256
+ zIndex: "2"
257
+ };
258
+ }
259
+ function chipStyles(role, pos, state = { isStreaming: false, isError: false }) {
260
+ const opacity = Math.max(OPACITY_FLOOR, 1 - pos * OPACITY_STEP);
261
+ const yPx = pos * Y_DRIFT_PX;
262
+ const border = state.isError ? "var(--sc-content-bubble-border-error, 1px solid rgba(220, 80, 80, 0.55))" : state.isStreaming ? "var(--sc-content-bubble-border-user, 1px solid rgba(255, 255, 255, 0.28))" : role === "user" ? "var(--sc-content-bubble-border-user, 1px solid rgba(255, 255, 255, 0.28))" : "var(--sc-content-bubble-border, 1px solid rgba(255, 255, 255, 0.18))";
263
+ const background = state.isError ? "var(--sc-content-bubble-background-error, rgba(140, 40, 40, 0.42))" : role === "user" ? "var(--sc-content-bubble-background-user, rgba(255, 255, 255, 0.10))" : "var(--sc-content-bubble-background, rgba(20, 22, 24, 0.35))";
264
+ const base = {
265
+ alignSelf: role === "user" ? "flex-end" : "flex-start",
266
+ maxWidth: "85%",
267
+ fontSize: "11px",
268
+ lineHeight: "1.45",
269
+ padding: "4px 10px",
270
+ borderRadius: "10px",
271
+ border,
272
+ background,
273
+ backdropFilter: "blur(8px)",
274
+ WebkitBackdropFilter: "blur(8px)",
275
+ color: state.isError ? "var(--sc-content-bubble-text-error, rgba(255, 220, 220, 0.95))" : "var(--sc-tile-title-color, inherit)",
276
+ transition: "opacity 240ms cubic-bezier(0.22, 1, 0.36, 1), transform 240ms cubic-bezier(0.22, 1, 0.36, 1)",
277
+ // Per-position opacity + y-drift falloff is still per-chip — those
278
+ // operate naturally per element. Blur is NOT per-chip anymore: it's
279
+ // applied via an absolute-positioned veil at the top of the scroll
280
+ // viewport (TRAIL_BLUR_VEIL_PX above), so a single long message
281
+ // doesn't get uniformly blurred across its full body.
282
+ opacity: String(toFixedTrim(opacity)),
283
+ transform: `translateY(${yPx}px)`,
284
+ whiteSpace: "pre-wrap",
285
+ wordBreak: "break-word"
286
+ };
287
+ return base;
288
+ }
289
+ function toolCallStripStyles() {
290
+ return {
291
+ display: "flex",
292
+ flexWrap: "wrap",
293
+ gap: "4px",
294
+ marginTop: "4px",
295
+ paddingTop: "4px",
296
+ borderTop: "1px dashed var(--sc-content-divider-color, rgba(255, 255, 255, 0.12))"
297
+ };
298
+ }
299
+ function toolCallChipStyles(status) {
300
+ const isPending = status === "pending";
301
+ const isError = status === "error";
302
+ const isDone = status === "done";
303
+ return {
304
+ display: "inline-flex",
305
+ alignItems: "center",
306
+ gap: "4px",
307
+ padding: "2px 7px",
308
+ border: isError ? "var(--sc-content-bubble-border-error, 1px solid rgba(220, 80, 80, 0.45))" : isPending ? "var(--sc-content-bubble-border-user, 1px solid rgba(255, 255, 255, 0.28))" : "var(--sc-content-bubble-border, 1px solid rgba(255, 255, 255, 0.18))",
309
+ background: isError ? "var(--sc-content-bubble-background-error, rgba(140, 40, 40, 0.35))" : isPending ? "var(--sc-content-bubble-background-user, rgba(255, 255, 255, 0.10))" : isDone ? "var(--sc-content-bubble-background-idle, rgba(40, 44, 50, 0.4))" : "var(--sc-content-bubble-background, rgba(20, 22, 24, 0.35))",
310
+ color: "var(--sc-tile-text-color, inherit)",
311
+ borderRadius: "999px",
312
+ fontSize: "10px",
313
+ fontWeight: "500",
314
+ fontFamily: "ui-monospace, SF Mono, Menlo, monospace",
315
+ cursor: isPending ? "pointer" : "default",
316
+ opacity: isDone ? "0.7" : "1"
317
+ };
318
+ }
319
+ function toolCallIcon(status) {
320
+ switch (status) {
321
+ case "args-streaming":
322
+ case "running":
323
+ return "\u22EF";
324
+ case "pending":
325
+ return "?";
326
+ case "done":
327
+ return "\u2713";
328
+ case "error":
329
+ return "\u2717";
330
+ default:
331
+ return "\xB7";
332
+ }
333
+ }
334
+ function caretStyles() {
335
+ return {
336
+ display: "inline-block",
337
+ width: "6px",
338
+ height: "12px",
339
+ marginLeft: "3px",
340
+ verticalAlign: "-1px",
341
+ background: "hsl(var(--sc-accent-color) / 0.85)",
342
+ borderRadius: "1px",
343
+ animation: "syntro-trail-caret 1s steps(2, end) infinite"
344
+ };
345
+ }
346
+ function moreStyles() {
347
+ return {
348
+ // Center horizontally regardless of the parent's display model.
349
+ // ``alignSelf: center`` only works when the parent is a flex/grid
350
+ // container with cross-axis alignment; the chat-bar's flex column
351
+ // stretches us full-width instead. ``display: block`` + auto inline
352
+ // margins centers the auto-sized button without needing the parent
353
+ // to opt in.
354
+ display: "block",
355
+ marginLeft: "auto",
356
+ marginRight: "auto",
357
+ marginBottom: "6px",
358
+ fontSize: "10px",
359
+ fontWeight: "500",
360
+ letterSpacing: "0.06em",
361
+ padding: "4px 10px",
362
+ border: "1px solid hsl(var(--sc-accent-color) / 0.32)",
363
+ borderRadius: "999px",
364
+ background: "hsl(var(--sc-accent-color) / 0.10)",
365
+ color: "var(--sc-tile-text-color, currentColor)",
366
+ cursor: "pointer",
367
+ opacity: "0.85",
368
+ backdropFilter: "blur(8px)",
369
+ WebkitBackdropFilter: "blur(8px)",
370
+ transition: "opacity 150ms ease, background 150ms ease"
371
+ };
372
+ }
373
+ function ensureStreamingKeyframes() {
374
+ if (typeof document === "undefined") return;
375
+ if (document.head.querySelector("style[data-syntro-trail-caret-keyframes]")) return;
376
+ const style = document.createElement("style");
377
+ style.setAttribute("data-syntro-trail-caret-keyframes", "true");
378
+ style.textContent = [
379
+ "@keyframes syntro-trail-caret { 0%{opacity:1} 50%{opacity:0} 100%{opacity:1} }",
380
+ // Anchor short content to the bottom of the scrollable trail —
381
+ // see baseStyles comment for why we can't use justify-content.
382
+ "[data-syntro-chat-trail] > :first-child { margin-top: auto }",
383
+ // Tight spacing — trail chips are an 11px-line-height pill. Default
384
+ // browser <p>/<ul> margins (1em ≈ 11px each side) would dominate the
385
+ // chip. Keep paragraph + list separators minimal (4px) so multi-
386
+ // paragraph replies read as one continuous flow, not a vertical
387
+ // stack of fragments.
388
+ "[data-trail-chip] > p:first-child { margin-top: 0 }",
389
+ "[data-trail-chip] > p:last-child { margin-bottom: 0 }",
390
+ "[data-trail-chip] p { margin: 4px 0 }",
391
+ "[data-trail-chip] br + br { display: none }",
392
+ "[data-trail-chip] ul, [data-trail-chip] ol { margin: 4px 0; padding-left: 1.1em }",
393
+ "[data-trail-chip] li { margin: 0 }",
394
+ "[data-trail-chip] li + li { margin-top: 1px }",
395
+ "[data-trail-chip] pre { margin: 6px 0; padding: 6px 8px; background: var(--sc-content-code-background-block, rgba(0, 0, 0, 0.35)); border-radius: 6px; overflow-x: auto; font-size: 10px }",
396
+ "[data-trail-chip] code { font-family: ui-monospace, SF Mono, Menlo, monospace; font-size: 10px; background: var(--sc-content-code-background, rgba(0, 0, 0, 0.25)); padding: 1px 4px; border-radius: 3px }",
397
+ "[data-trail-chip] pre code { background: transparent; padding: 0 }",
398
+ "[data-trail-chip] a { color: var(--sc-content-link-color, var(--sc-color-primary, #b72e2a)); text-decoration: underline }",
399
+ "[data-trail-chip] strong { font-weight: 600 }"
400
+ ].join(" ");
401
+ document.head.appendChild(style);
402
+ }
403
+ function stripTrailingWhitespace(text) {
404
+ return text.replace(/[ \t]+$/gm, "").replace(/\n{3,}/g, "\n\n").trim();
405
+ }
406
+ function toFixedTrim(n) {
407
+ const s = n.toFixed(2);
408
+ return s.replace(/\.?0+$/, "") || "0";
409
+ }
410
+ if (!customElements.get("adaptive-chat-trail")) {
411
+ customElements.define("adaptive-chat-trail", AdaptiveChatTrail);
412
+ }
413
+
414
+ export {
415
+ AdaptiveChatTrail
416
+ };
417
+ //# sourceMappingURL=chunk-BNN5YLN3.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/AdaptiveChatTrail.ts"],
4
+ "sourcesContent": ["/**\n * AdaptiveChatTrail \u2014 the \"bubble-up\" message column that sits above the\n * chat bar in the canvas lid.\n *\n * Messages appear immediately above the chat bar and drift upward as\n * newer ones arrive. Each stack position carries an opacity + blur\n * falloff so older messages read as fading from the page. Beyond a\n * `visibleCount` cap (default 3) the trail collapses into an \"N more \u00B7\n * expand\" affordance \u2014 clicking emits `trail-expand`.\n *\n * Light DOM (no shadow root) so the host page's CSS variables and the\n * surrounding canvas tokens flow through without a nested shadow\n * boundary.\n *\n * See PRD \u00A74.3 (chat trail) for the canonical motion + falloff spec.\n */\n\nimport { renderMarkdown } from '@syntrologie/chat';\nimport { html, LitElement, nothing } from 'lit';\nimport { styleMap } from 'lit/directives/style-map.js';\nimport { unsafeHTML } from 'lit/directives/unsafe-html.js';\n\nexport interface TrailToolCall {\n id: string;\n name: string;\n status: 'args-streaming' | 'pending' | 'running' | 'done' | 'error';\n}\n\nexport interface TrailMessage {\n /** Stable identity for keyed rendering (must be unique within the trail). */\n id: number | string;\n role: 'user' | 'assistant' | 'system';\n text: string;\n /** Streaming \u2192 assistant text still arriving from backend. Complete \u2192 final. Error \u2192 fatal during stream. */\n status?: 'streaming' | 'complete' | 'error';\n /** Tool calls attached to an assistant message (rendered as compact chips). */\n toolCalls?: TrailToolCall[];\n}\n\n/** PRD \u00A74.3 constants. */\nconst DEFAULT_VISIBLE = 3;\nconst OPACITY_STEP = 0.22;\nconst OPACITY_FLOOR = 0.18;\nconst Y_DRIFT_PX = -2;\n/**\n * Height of the absolute blur veil at the top of the trail's scroll\n * area. Pixel-based, NOT message-count-based: with the old per-chip\n * `filter: blur(pos * step)` model, a single long markdown response\n * at pos=1 would have its entire body uniformly blurred \u2014 including\n * the chunk visually adjacent to the crisp newest message \u2014 because\n * blur is a property of the chip element, not of pixels. Switching\n * to a fixed-height backdrop-blur veil keeps the bottom of the\n * visible area (newest content) crisp regardless of message length.\n */\nconst TRAIL_BLUR_VEIL_PX = 72;\n/** Max blur applied at the very top of the veil; tapers to 0 at the bottom edge. */\nconst TRAIL_BLUR_MAX_PX = 5;\n\nexport class AdaptiveChatTrail extends LitElement {\n static override properties = {\n messages: { attribute: false },\n visibleCount: { type: Number },\n expanded: { type: Boolean },\n greeting: { type: String },\n /** When true: trail renders fully expanded AND the\n * expand/collapse affordance is hidden. Used by hosts that mount\n * the chat in a full-screen surface (the mobile panel) where\n * collapse makes no sense \u2014 there's nothing to collapse INTO. */\n forceExpanded: { type: Boolean },\n };\n\n messages: TrailMessage[] = [];\n visibleCount = DEFAULT_VISIBLE;\n expanded = false;\n forceExpanded = false;\n /**\n * Pre-conversation phantom assistant message. Rendered only when\n * `messages` is empty. NOT injected into chatSession state \u2014 the\n * greeting is configuration, not conversation, so it disappears\n * automatically when the first real message arrives.\n */\n greeting: string | undefined = undefined;\n\n override createRenderRoot(): HTMLElement {\n // Light DOM \u2014 canvas-level CSS vars reach the chips directly.\n return this;\n }\n\n override connectedCallback(): void {\n super.connectedCallback();\n ensureStreamingKeyframes();\n }\n\n private _onExpand = (): void => {\n // Self-manage: clicking the inline link expands the trail in place.\n // We still dispatch the event so any parent that wants to react\n // (telemetry, mirror state to a side panel, etc.) can listen.\n this.expanded = true;\n this.dispatchEvent(new CustomEvent('trail-expand', { bubbles: true, composed: true }));\n };\n\n /**\n * Approve a pending client tool call. The trail emits a generic\n * event so the host can decide whether to forward to chatSession\n * (the common path) or override. Keeps the trail itself free of\n * direct chatSession coupling \u2014 it's a pure view component.\n */\n private _onToolCallClick = (tc: TrailToolCall): void => {\n if (tc.status !== 'pending') return;\n this.dispatchEvent(\n new CustomEvent<{ toolCallId: string; approved: boolean }>('trail-toolcall-approved', {\n detail: { toolCallId: tc.id, approved: true },\n bubbles: true,\n composed: true,\n })\n );\n };\n\n private _onCollapse = (): void => {\n // Mirror of _onExpand for the minimize affordance shown while\n // expanded. Self-manages + dispatches `trail-collapse` for parent\n // observers (telemetry, side-panel mirrors).\n this.expanded = false;\n this.dispatchEvent(new CustomEvent('trail-collapse', { bubbles: true, composed: true }));\n };\n\n override updated(changed: Map<string, unknown>): void {\n // Anchor the scroll to the bottom whenever:\n // - the trail flips into expanded mode (UX continuity), OR\n // - new messages or text deltas arrive (so a long streaming chip\n // stays pinned to the latest line, not the start of the chip).\n // Skip when the user has manually scrolled UP into history \u2014 we'd\n // rather lose pinning than yank them out of what they're reading.\n if (changed.has('expanded') || changed.has('messages')) {\n requestAnimationFrame(() => {\n const container = this.querySelector<HTMLElement>('[data-syntro-chat-trail]');\n if (!container) return;\n const distanceFromBottom =\n container.scrollHeight - container.scrollTop - container.clientHeight;\n // 200px tolerance \u2014 streaming deltas can append a paragraph in\n // one update, easily 80-150px. A tighter window would strand\n // the viewer at the top of the chip mid-stream because the\n // first delta blew past the threshold. User-driven scroll-up\n // of >200px disables auto-pin until they scroll back down.\n if (distanceFromBottom < 200 || changed.has('expanded')) {\n container.scrollTop = container.scrollHeight;\n }\n });\n }\n }\n\n override render() {\n if (this.messages.length === 0 && !this.greeting) return nothing;\n if (this.messages.length === 0 && this.greeting) {\n // Render the greeting as a pure-display assistant chip. No\n // tool-calls, no streaming caret, single chip at pos=0.\n return html`<div data-syntro-chat-trail style=${styleMap({\n display: 'flex',\n flexDirection: 'column',\n gap: '6px',\n justifyContent: 'flex-end',\n width: '100%',\n padding: '0',\n pointerEvents: 'auto',\n })}>\n <div\n data-trail-chip\n data-role=\"assistant\"\n data-status=\"greeting\"\n style=${styleMap(chipStyles('assistant', 0, { isStreaming: false, isError: false }))}\n >${unsafeHTML(renderMarkdown(stripTrailingWhitespace(this.greeting)))}</div>\n </div>`;\n }\n\n // Choose the slice we actually paint. Newest are at the END of the\n // array (closest to the chat bar). When not expanded, take the last\n // `visibleCount`; when expanded, take all.\n const visible =\n this.expanded || this.forceExpanded ? this.messages : this.messages.slice(-this.visibleCount);\n const hidden = this.messages.length - visible.length;\n\n const baseStyles: Record<string, string> = {\n display: 'flex',\n flexDirection: 'column',\n gap: '6px',\n // NO `justifyContent: flex-end` here \u2014 combined with `overflow: auto`\n // it's a known cross-browser bug: oversized flex children get\n // pushed ABOVE the container without contributing to scrollHeight,\n // so the scrollbar never appears. We rely on `:first-child {\n // margin-top: auto }` (injected as global CSS) to push short\n // content to the bottom of the box while leaving overflow handling\n // to the native scroll container.\n width: '100%',\n // The trail itself has no chrome \u2014 chips sit on the host page bg.\n padding: '0',\n pointerEvents: 'auto',\n };\n\n // Expanded mode: cap height, scroll, and add a visible top border so\n // the panel reads as a bounded region. NO background tint or shadow\n // \u2014 the ambient (collapsed) treatment is fine and we don't want to\n // suddenly introduce panel chrome that wasn't there before. The\n // border alone marks the panel's top edge.\n // Falloff is turned off per-chip below \u2014 readability wins once the\n // user has explicitly asked for history. See PRD \u00A74.3.\n // Collapsed mode also gets a (looser) cap + scroll so a single long\n // markdown chip doesn't blow the chat-bar off-screen with no\n // scroll affordance. Tighter than expanded so the bubble-up trail\n // doesn't dominate the surface visually.\n const modeStyles: Record<string, string> = this.forceExpanded\n ? {\n // Full-screen host: fill all available vertical space and\n // scroll the overflow. ``flex: 1 1 0`` (basis 0, not auto)\n // makes the trail shrink to 0 first when the parent runs\n // out of space \u2014 so the header above and the input row\n // below stay pinned at their natural sizes instead of one\n // of them being pushed off-screen.\n flex: '1 1 0',\n minHeight: '0',\n overflowY: 'auto',\n // ``contain`` prevents scroll from bubbling out to the host\n // page when the trail hits top or bottom. Required for the\n // panel-scroll-leak regression test in\n // SyntroBottomSheet.test.ts.\n overscrollBehavior: 'contain',\n paddingTop: '8px',\n borderTop: 'var(--sc-content-bubble-border, 1px solid rgba(255, 255, 255, 0.18))',\n scrollBehavior: 'smooth',\n scrollbarWidth: 'thin',\n }\n : this.expanded\n ? {\n maxHeight: 'min(320px, 40vh)',\n overflowY: 'auto',\n paddingTop: '8px',\n borderTop: 'var(--sc-content-bubble-border, 1px solid rgba(255, 255, 255, 0.18))',\n scrollBehavior: 'smooth',\n scrollbarWidth: 'thin',\n }\n : {\n // Collapsed mode = ambient peek surface. Never a scrollbar:\n // long messages slide up behind the blur veil instead of\n // exposing a scrollbar. The \"\u2191 N more \u00B7 expand\" affordance\n // is the only entry into history; clicking it switches to\n // expanded mode where scroll IS allowed.\n // Height kept short so the trail reads as a peek strip above\n // the chat bar, not a panel \u2014 long single replies fade into\n // the blur veil at the top, user expands to read the rest.\n maxHeight: 'min(140px, 22vh)',\n overflow: 'hidden',\n };\n\n const containerStyles = { ...baseStyles, ...modeStyles };\n\n // Wrap the scroll container in a positioning frame so the absolute\n // blur veil (rendered at the top) anchors to the scroll viewport's\n // top edge \u2014 NOT to a chip that scrolls under it. Pixel-anchored\n // blur means: the top TRAIL_BLUR_VEIL_PX of the visible area is\n // veiled regardless of how many messages occupy those pixels.\n // The frame wraps the scroll container so the absolute blur veil\n // anchors to the scroll viewport's top edge. In ``forceExpanded``\n // mode it ALSO has to participate in the flex column laid out by\n // the trail host \u2014 otherwise the scroll container's ``flex: 1 1\n // 0`` has no flex context, falls back to content height, and the\n // trail's overflow:hidden clips at the TOP of the scroll\n // (showing oldest messages, hiding the newest at the bottom).\n const frameStyles: Record<string, string> = this.forceExpanded\n ? {\n position: 'relative',\n width: '100%',\n display: 'flex',\n flexDirection: 'column',\n flex: '1 1 0',\n minHeight: '0',\n }\n : {\n position: 'relative',\n width: '100%',\n };\n\n return html`\n ${\n // Single toggle button above the frame. Same DOM node morphs:\n // collapsed \u2192 \"\u2191 N more \u00B7 expand\" / \"\u2191 expand\" (calls _onExpand)\n // expanded \u2192 \"\u2304 minimize\" (calls _onCollapse)\n // Lives OUTSIDE the frame so the absolute blur veil (top:0,\n // z-index:2 inside the frame) doesn't cover it. Shown whenever\n // there's anything in the trail \u2014 the user expects this affordance\n // available regardless of how many chips are on screen.\n // When ``forceExpanded`` is set by the host (full-screen panel,\n // etc.), there's nowhere to collapse INTO and the affordance\n // would just confuse the user \u2014 suppress it entirely.\n this.messages.length === 0 || this.forceExpanded\n ? nothing\n : this.expanded\n ? html`<button\n type=\"button\"\n data-trail-toggle\n data-trail-collapse\n @click=${this._onCollapse}\n style=${styleMap(moreStyles())}\n >\u2304 minimize</button>`\n : html`<button\n type=\"button\"\n data-trail-toggle\n data-trail-more\n @click=${this._onExpand}\n style=${styleMap(moreStyles())}\n >${hidden > 0 ? html`\u2191 ${hidden} more \u00B7 expand` : html`\u2191 expand`}</button>`\n }\n <div data-syntro-chat-trail-frame style=${styleMap(frameStyles)}>\n <div data-syntro-chat-trail style=${styleMap(containerStyles)}>\n ${visible.map((m, i) => {\n // Stack position from the bar: 0 = closest, N-1 = oldest.\n // visible[last] is closest, so reverse the index from the\n // tail of the visible slice. When expanded, the ambient\n // falloff is suppressed (pos = 0) so every chip reads at\n // full opacity \u2014 explicit history view, not ambient trail.\n const pos = this.expanded || this.forceExpanded ? 0 : visible.length - 1 - i;\n const isStreaming = m.status === 'streaming';\n const isError = m.status === 'error' || m.role === 'system';\n const toolCalls = m.toolCalls ?? [];\n // Assistant text is markdown-formatted by the agent (lists,\n // code, bold, links). renderMarkdown sanitizes via DOMPurify,\n // then unsafeHTML injects the safe HTML. User + system chips\n // stay plain text \u2014 no markdown risk + no expansion attack\n // surface on user-typed input.\n const renderedText =\n m.role === 'assistant'\n ? html`${unsafeHTML(renderMarkdown(stripTrailingWhitespace(m.text)))}`\n : html`${m.text}`;\n return html`<div\n data-trail-chip\n data-role=${m.role}\n data-status=${m.status ?? 'complete'}\n style=${styleMap(chipStyles(m.role, pos, { isStreaming, isError }))}\n >${renderedText}${\n isStreaming\n ? html`<span\n data-trail-caret\n aria-hidden=\"true\"\n style=${styleMap(caretStyles())}\n ></span>`\n : nothing\n }${\n toolCalls.length > 0\n ? html`<div data-trail-toolcalls style=${styleMap(toolCallStripStyles())}>\n ${toolCalls.map(\n (tc) => html`<button\n type=\"button\"\n data-trail-toolcall\n data-tool-id=${tc.id}\n data-tool-status=${tc.status}\n @click=${() => this._onToolCallClick(tc)}\n ?disabled=${tc.status !== 'pending'}\n style=${styleMap(toolCallChipStyles(tc.status))}\n title=\"${tc.name} \u00B7 ${tc.status}\"\n >${toolCallIcon(tc.status)} ${tc.name}</button>`\n )}\n </div>`\n : nothing\n }</div>`;\n })}\n </div>\n ${\n // The blur veil only fires in collapsed (ambient) mode.\n // Expanded = the user explicitly asked for history; blurring\n // the top of the panel would hide content (including the\n // minimize button just above the trail's top edge) the user\n // came here to see.\n this.expanded || this.forceExpanded\n ? nothing\n : html`<div\n data-trail-blur-veil\n aria-hidden=\"true\"\n style=${styleMap(blurVeilStyles())}\n ></div>`\n }\n </div>\n `;\n }\n}\n\n/**\n * Absolute blur veil at the top of the scroll viewport. Uses\n * `backdrop-filter: blur(...)` so whatever pixels sit beneath the veil\n * (top of the scroll content) become blurred, regardless of which chip\n * those pixels belong to. A linear mask fades the blur effect from full\n * at the top to zero at the bottom of the veil \u2014 newest content stays\n * crisp because it lives below the veil's height.\n *\n * Note: `mask-image` controls where the BACKDROP-FILTER applies (via\n * masking the veil element), so the bottom edge of the veil naturally\n * dissolves into the unblurred scroll area below.\n */\nfunction blurVeilStyles(): Record<string, string> {\n const mask = 'linear-gradient(to bottom, black 0%, black 40%, transparent 100%)';\n return {\n position: 'absolute',\n top: '0',\n left: '0',\n right: '0',\n height: `${TRAIL_BLUR_VEIL_PX}px`,\n pointerEvents: 'none',\n backdropFilter: `blur(${TRAIL_BLUR_MAX_PX}px)`,\n WebkitBackdropFilter: `blur(${TRAIL_BLUR_MAX_PX}px)`,\n maskImage: mask,\n WebkitMaskImage: mask,\n zIndex: '2',\n };\n}\n\nfunction chipStyles(\n role: TrailMessage['role'],\n pos: number,\n state: { isStreaming: boolean; isError: boolean } = { isStreaming: false, isError: false }\n): Record<string, string> {\n const opacity = Math.max(OPACITY_FLOOR, 1 - pos * OPACITY_STEP);\n const yPx = pos * Y_DRIFT_PX;\n\n // Error chips override role-specific styling \u2014 they shouldn't read\n // as \"an assistant reply.\" Streaming chips get a subtle accent ring\n // so the in-progress state is visually distinct from a settled reply.\n // All four states resolve to design-system tokens via the theme; the\n // fallbacks here are conservative neutrals so the widget still renders\n // sensibly on a host page that hasn't set tokens yet.\n const border = state.isError\n ? 'var(--sc-content-bubble-border-error, 1px solid rgba(220, 80, 80, 0.55))'\n : state.isStreaming\n ? 'var(--sc-content-bubble-border-user, 1px solid rgba(255, 255, 255, 0.28))'\n : role === 'user'\n ? 'var(--sc-content-bubble-border-user, 1px solid rgba(255, 255, 255, 0.28))'\n : 'var(--sc-content-bubble-border, 1px solid rgba(255, 255, 255, 0.18))';\n const background = state.isError\n ? 'var(--sc-content-bubble-background-error, rgba(140, 40, 40, 0.42))'\n : role === 'user'\n ? 'var(--sc-content-bubble-background-user, rgba(255, 255, 255, 0.10))'\n : 'var(--sc-content-bubble-background, rgba(20, 22, 24, 0.35))';\n\n const base: Record<string, string> = {\n alignSelf: role === 'user' ? 'flex-end' : 'flex-start',\n maxWidth: '85%',\n fontSize: '11px',\n lineHeight: '1.45',\n padding: '4px 10px',\n borderRadius: '10px',\n border,\n background,\n backdropFilter: 'blur(8px)',\n WebkitBackdropFilter: 'blur(8px)',\n color: state.isError\n ? 'var(--sc-content-bubble-text-error, rgba(255, 220, 220, 0.95))'\n : 'var(--sc-tile-title-color, inherit)',\n transition:\n 'opacity 240ms cubic-bezier(0.22, 1, 0.36, 1), transform 240ms cubic-bezier(0.22, 1, 0.36, 1)',\n // Per-position opacity + y-drift falloff is still per-chip \u2014 those\n // operate naturally per element. Blur is NOT per-chip anymore: it's\n // applied via an absolute-positioned veil at the top of the scroll\n // viewport (TRAIL_BLUR_VEIL_PX above), so a single long message\n // doesn't get uniformly blurred across its full body.\n opacity: String(toFixedTrim(opacity)),\n transform: `translateY(${yPx}px)`,\n whiteSpace: 'pre-wrap',\n wordBreak: 'break-word',\n };\n return base;\n}\n\nfunction toolCallStripStyles(): Record<string, string> {\n return {\n display: 'flex',\n flexWrap: 'wrap',\n gap: '4px',\n marginTop: '4px',\n paddingTop: '4px',\n borderTop: '1px dashed var(--sc-content-divider-color, rgba(255, 255, 255, 0.12))',\n };\n}\n\nfunction toolCallChipStyles(status: TrailToolCall['status']): Record<string, string> {\n const isPending = status === 'pending';\n const isError = status === 'error';\n const isDone = status === 'done';\n return {\n display: 'inline-flex',\n alignItems: 'center',\n gap: '4px',\n padding: '2px 7px',\n border: isError\n ? 'var(--sc-content-bubble-border-error, 1px solid rgba(220, 80, 80, 0.45))'\n : isPending\n ? 'var(--sc-content-bubble-border-user, 1px solid rgba(255, 255, 255, 0.28))'\n : 'var(--sc-content-bubble-border, 1px solid rgba(255, 255, 255, 0.18))',\n background: isError\n ? 'var(--sc-content-bubble-background-error, rgba(140, 40, 40, 0.35))'\n : isPending\n ? 'var(--sc-content-bubble-background-user, rgba(255, 255, 255, 0.10))'\n : isDone\n ? 'var(--sc-content-bubble-background-idle, rgba(40, 44, 50, 0.4))'\n : 'var(--sc-content-bubble-background, rgba(20, 22, 24, 0.35))',\n color: 'var(--sc-tile-text-color, inherit)',\n borderRadius: '999px',\n fontSize: '10px',\n fontWeight: '500',\n fontFamily: 'ui-monospace, SF Mono, Menlo, monospace',\n cursor: isPending ? 'pointer' : 'default',\n opacity: isDone ? '0.7' : '1',\n };\n}\n\nfunction toolCallIcon(status: TrailToolCall['status']): string {\n switch (status) {\n case 'args-streaming':\n case 'running':\n return '\u22EF';\n case 'pending':\n return '?';\n case 'done':\n return '\u2713';\n case 'error':\n return '\u2717';\n default:\n return '\u00B7';\n }\n}\n\nfunction caretStyles(): Record<string, string> {\n return {\n display: 'inline-block',\n width: '6px',\n height: '12px',\n marginLeft: '3px',\n verticalAlign: '-1px',\n background: 'hsl(var(--sc-accent-color) / 0.85)',\n borderRadius: '1px',\n animation: 'syntro-trail-caret 1s steps(2, end) infinite',\n };\n}\n\nfunction moreStyles(): Record<string, string> {\n return {\n // Center horizontally regardless of the parent's display model.\n // ``alignSelf: center`` only works when the parent is a flex/grid\n // container with cross-axis alignment; the chat-bar's flex column\n // stretches us full-width instead. ``display: block`` + auto inline\n // margins centers the auto-sized button without needing the parent\n // to opt in.\n display: 'block',\n marginLeft: 'auto',\n marginRight: 'auto',\n marginBottom: '6px',\n fontSize: '10px',\n fontWeight: '500',\n letterSpacing: '0.06em',\n padding: '4px 10px',\n border: '1px solid hsl(var(--sc-accent-color) / 0.32)',\n borderRadius: '999px',\n background: 'hsl(var(--sc-accent-color) / 0.10)',\n color: 'var(--sc-tile-text-color, currentColor)',\n cursor: 'pointer',\n opacity: '0.85',\n backdropFilter: 'blur(8px)',\n WebkitBackdropFilter: 'blur(8px)',\n transition: 'opacity 150ms ease, background 150ms ease',\n };\n}\n\n/**\n * Inject the streaming-caret keyframes once per document. Light-DOM\n * components can't ship CSS via Lit's `styles` static \u2014 the rules\n * need to be present in the document's stylesheet. Idempotent via\n * a data attribute on the injected style tag.\n */\nfunction ensureStreamingKeyframes(): void {\n if (typeof document === 'undefined') return;\n if (document.head.querySelector('style[data-syntro-trail-caret-keyframes]')) return;\n const style = document.createElement('style');\n style.setAttribute('data-syntro-trail-caret-keyframes', 'true');\n // Caret animation + markdown reset for assistant chips. The trail\n // chips are tiny (11px line-height); default <p>/<ul>/<pre> margins\n // would push the chip vertically and break the falloff layout.\n style.textContent = [\n '@keyframes syntro-trail-caret { 0%{opacity:1} 50%{opacity:0} 100%{opacity:1} }',\n // Anchor short content to the bottom of the scrollable trail \u2014\n // see baseStyles comment for why we can't use justify-content.\n '[data-syntro-chat-trail] > :first-child { margin-top: auto }',\n // Tight spacing \u2014 trail chips are an 11px-line-height pill. Default\n // browser <p>/<ul> margins (1em \u2248 11px each side) would dominate the\n // chip. Keep paragraph + list separators minimal (4px) so multi-\n // paragraph replies read as one continuous flow, not a vertical\n // stack of fragments.\n '[data-trail-chip] > p:first-child { margin-top: 0 }',\n '[data-trail-chip] > p:last-child { margin-bottom: 0 }',\n '[data-trail-chip] p { margin: 4px 0 }',\n '[data-trail-chip] br + br { display: none }',\n '[data-trail-chip] ul, [data-trail-chip] ol { margin: 4px 0; padding-left: 1.1em }',\n '[data-trail-chip] li { margin: 0 }',\n '[data-trail-chip] li + li { margin-top: 1px }',\n '[data-trail-chip] pre { margin: 6px 0; padding: 6px 8px; background: var(--sc-content-code-background-block, rgba(0, 0, 0, 0.35)); border-radius: 6px; overflow-x: auto; font-size: 10px }',\n '[data-trail-chip] code { font-family: ui-monospace, SF Mono, Menlo, monospace; font-size: 10px; background: var(--sc-content-code-background, rgba(0, 0, 0, 0.25)); padding: 1px 4px; border-radius: 3px }',\n '[data-trail-chip] pre code { background: transparent; padding: 0 }',\n '[data-trail-chip] a { color: var(--sc-content-link-color, var(--sc-color-primary, #b72e2a)); text-decoration: underline }',\n '[data-trail-chip] strong { font-weight: 600 }',\n ].join(' ');\n document.head.appendChild(style);\n}\n\n/**\n * Pre-process LLM markdown before rendering. Three common artifacts:\n *\n * 1. Trailing \" \" (two spaces = markdown hard-break) sprinkled at\n * end-of-line, even inside list items where it just leaks\n * invisible whitespace into the DOM.\n * 2. Runs of 3+ blank lines used as visual separators. Markdown\n * treats any number of blank lines as a single paragraph break,\n * so the extras add nothing semantically \u2014 but combined with\n * paragraph margins they create huge gaps in the tiny chip layout.\n * 3. Leading / trailing newlines on the whole message. Trailing\n * `\\n\\n` is the common one \u2014 marked turns it into a final empty\n * <p> that still occupies a full line-height of vertical space,\n * leaving a visible gap between the last text and the chat bar.\n *\n * Collapsing all three keeps the rendered HTML tight without changing\n * the agent's semantic intent.\n */\nfunction stripTrailingWhitespace(text: string): string {\n return text\n .replace(/[ \\t]+$/gm, '') // trailing whitespace per line\n .replace(/\\n{3,}/g, '\\n\\n') // collapse runs of blank lines\n .trim(); // drop leading/trailing whitespace+newlines on the whole message\n}\n\n/** Two-decimal trim that avoids trailing zeros (\".50\" \u2192 \".5\"). */\nfunction toFixedTrim(n: number): string {\n const s = n.toFixed(2);\n return s.replace(/\\.?0+$/, '') || '0';\n}\n\nif (!customElements.get('adaptive-chat-trail')) {\n customElements.define('adaptive-chat-trail', AdaptiveChatTrail);\n}\n\ndeclare global {\n interface HTMLElementTagNameMap {\n 'adaptive-chat-trail': AdaptiveChatTrail;\n }\n}\n"],
5
+ "mappings": "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",
6
+ "names": []
7
+ }