@syntrologie/adapt-chatbot 2.8.0-canary.417 → 2.8.0-canary.419

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.
@@ -2,8 +2,8 @@ import {
2
2
  AdaptiveChatBar
3
3
  } from "./chunk-2NX6765N.js";
4
4
  import "./chunk-EWPPVPJ4.js";
5
- import "./chunk-LTG5G6WN.js";
6
- import "./chunk-5PW6T3QF.js";
5
+ import "./chunk-LSJFN2BR.js";
6
+ import "./chunk-QF7ZTKXA.js";
7
7
  import "./chunk-UVKRO5ER.js";
8
8
  export {
9
9
  AdaptiveChatBar
@@ -0,0 +1,2 @@
1
+ import './AdaptiveChatTrail';
2
+ //# sourceMappingURL=AdaptiveChatTrail.anchor-scroll.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AdaptiveChatTrail.anchor-scroll.test.d.ts","sourceRoot":"","sources":["../src/AdaptiveChatTrail.anchor-scroll.test.ts"],"names":[],"mappings":"AA0BA,OAAO,qBAAqB,CAAC"}
@@ -171,6 +171,9 @@ export declare class AdaptiveChatTrail extends LitElement {
171
171
  mountInlineWidget: {
172
172
  attribute: boolean;
173
173
  };
174
+ _messagesBelow: {
175
+ state: boolean;
176
+ };
174
177
  };
175
178
  messages: TrailMessage[];
176
179
  tileReceipts: TileReceipt[];
@@ -192,6 +195,46 @@ export declare class AdaptiveChatTrail extends LitElement {
192
195
  * automatically when the first real message arrives.
193
196
  */
194
197
  greeting: string | undefined;
198
+ /** Follow the bottom on content growth. On by default (history
199
+ * browsing); off from a new user message until the visitor opts back in
200
+ * (pill tap or a user-gesture scroll that reaches the bottom). */
201
+ private _stickToBottom;
202
+ /** The user message the current turn is anchored to. */
203
+ private _anchorMsgId;
204
+ /** Whether a turn anchor is live (drives expand-mid-turn landing). */
205
+ private _anchorActive;
206
+ /** Keep enforcing chip-at-top on updates (late geometry, mode flips)
207
+ * until the user scrolls or opts into following. */
208
+ private _anchorHold;
209
+ /** Number of messages whose BOTTOM edge is still below the viewport —
210
+ * messages the visitor hasn't scrolled past yet. Drives the
211
+ * "⌄ N messages" pill and stays accurate as they scroll (one long
212
+ * streaming reply counts as 1 until its end has been read). Reactive. */
213
+ private _messagesBelow;
214
+ /** Timestamp of the last user scroll gesture (wheel/touch): scroll
215
+ * events outside this window are programmatic (our own pin/anchor) and
216
+ * must not re-engage following — anchoring the newest message clamps AT
217
+ * the bottom in any longer conversation. */
218
+ private _lastUserScrollIntentTs;
219
+ private static readonly _AT_BOTTOM_EPSILON_PX;
220
+ private static readonly _USER_SCROLL_INTENT_WINDOW_MS;
221
+ private get _scrollable();
222
+ private _trailContainer;
223
+ private _isAtBottom;
224
+ /** Count chips whose bottom edge hasn't entered the viewport yet. */
225
+ private _countMessagesBelow;
226
+ private _syncContentBelow;
227
+ private _markUserScrollIntent;
228
+ private _onTrailScroll;
229
+ /** Pill tap: jump to the newest content and follow for the rest of the turn. */
230
+ private _onScrollToRecent;
231
+ /** Scroll so the anchored user chip sits at the top of the trail
232
+ * viewport. Idempotent — safe to re-apply on every update while the
233
+ * hold is live (content grows BELOW the anchor, so the target is
234
+ * stable; re-applying also covers late geometry, e.g. expanding
235
+ * mid-turn from the ambient strip). */
236
+ private _applyAnchor;
237
+ private _pinToBottom;
195
238
  private _onExpand;
196
239
  /**
197
240
  * Approve a pending client tool call. The trail emits a generic
@@ -1 +1 @@
1
- {"version":3,"file":"AdaptiveChatTrail.d.ts","sourceRoot":"","sources":["../src/AdaptiveChatTrail.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"AdaptiveChatTrail.d.ts","sourceRoot":"","sources":["../src/AdaptiveChatTrail.ts"],"names":[],"mappings":"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"}
@@ -1,8 +1,8 @@
1
1
  import {
2
2
  AdaptiveChatTrail,
3
3
  looksLikeSerializedModelResponse
4
- } from "./chunk-LTG5G6WN.js";
5
- import "./chunk-5PW6T3QF.js";
4
+ } from "./chunk-LSJFN2BR.js";
5
+ import "./chunk-QF7ZTKXA.js";
6
6
  import "./chunk-UVKRO5ER.js";
7
7
  export {
8
8
  AdaptiveChatTrail,
@@ -3,7 +3,7 @@ import {
3
3
  renderFallbackHtml
4
4
  } from "./chunk-CWRCHWWJ.js";
5
5
  import "./chunk-EWPPVPJ4.js";
6
- import "./chunk-5PW6T3QF.js";
6
+ import "./chunk-QF7ZTKXA.js";
7
7
  import "./chunk-KPWW7N27.js";
8
8
  import "./chunk-UVKRO5ER.js";
9
9
  export {
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  renderMarkdown
3
- } from "./chunk-5PW6T3QF.js";
3
+ } from "./chunk-QF7ZTKXA.js";
4
4
 
5
5
  // src/AdaptiveChatTrail.ts
6
6
  import { css, html, LitElement, nothing } from "lit";
@@ -13,7 +13,7 @@ var OPACITY_FLOOR = 0.18;
13
13
  var Y_DRIFT_PX = -2;
14
14
  var TRAIL_BLUR_VEIL_PX = 72;
15
15
  var TRAIL_BLUR_MAX_PX = 5;
16
- var AdaptiveChatTrail = class extends LitElement {
16
+ var _AdaptiveChatTrail = class _AdaptiveChatTrail extends LitElement {
17
17
  constructor() {
18
18
  super(...arguments);
19
19
  this.messages = [];
@@ -33,6 +33,59 @@ var AdaptiveChatTrail = class extends LitElement {
33
33
  * automatically when the first real message arrives.
34
34
  */
35
35
  this.greeting = void 0;
36
+ // ── Anchor-scroll model (FEAT-1783535427, shared contract with
37
+ // <syntro-chat>) — applies to the SCROLLABLE modes (expanded /
38
+ // forceExpanded). The ambient peek strip keeps its bottom pin: it is
39
+ // overflow-hidden (not user-scrollable) and the pin is what keeps the
40
+ // latest line visible under the blur veil.
41
+ /** Follow the bottom on content growth. On by default (history
42
+ * browsing); off from a new user message until the visitor opts back in
43
+ * (pill tap or a user-gesture scroll that reaches the bottom). */
44
+ this._stickToBottom = true;
45
+ /** The user message the current turn is anchored to. */
46
+ this._anchorMsgId = null;
47
+ /** Whether a turn anchor is live (drives expand-mid-turn landing). */
48
+ this._anchorActive = false;
49
+ /** Keep enforcing chip-at-top on updates (late geometry, mode flips)
50
+ * until the user scrolls or opts into following. */
51
+ this._anchorHold = false;
52
+ /** Number of messages whose BOTTOM edge is still below the viewport —
53
+ * messages the visitor hasn't scrolled past yet. Drives the
54
+ * "⌄ N messages" pill and stays accurate as they scroll (one long
55
+ * streaming reply counts as 1 until its end has been read). Reactive. */
56
+ this._messagesBelow = 0;
57
+ /** Timestamp of the last user scroll gesture (wheel/touch): scroll
58
+ * events outside this window are programmatic (our own pin/anchor) and
59
+ * must not re-engage following — anchoring the newest message clamps AT
60
+ * the bottom in any longer conversation. */
61
+ this._lastUserScrollIntentTs = 0;
62
+ this._markUserScrollIntent = () => {
63
+ this._lastUserScrollIntentTs = Date.now();
64
+ };
65
+ this._onTrailScroll = () => {
66
+ const container = this._trailContainer();
67
+ if (!container) return;
68
+ const atBottom = this._isAtBottom(container);
69
+ this._messagesBelow = this._scrollable && !atBottom ? this._countMessagesBelow(container) : 0;
70
+ const isUserDriven = Date.now() - this._lastUserScrollIntentTs < _AdaptiveChatTrail._USER_SCROLL_INTENT_WINDOW_MS;
71
+ if (isUserDriven) {
72
+ this._anchorHold = false;
73
+ this._stickToBottom = atBottom;
74
+ if (atBottom) this._anchorActive = false;
75
+ }
76
+ };
77
+ /** Pill tap: jump to the newest content and follow for the rest of the turn. */
78
+ this._onScrollToRecent = () => {
79
+ this._stickToBottom = true;
80
+ this._anchorHold = false;
81
+ this._anchorActive = false;
82
+ requestAnimationFrame(() => {
83
+ const container = this._trailContainer();
84
+ if (!container) return;
85
+ container.scrollTop = container.scrollHeight;
86
+ this._syncContentBelow();
87
+ });
88
+ };
36
89
  // No `createRenderRoot()` override → default open shadow root. CSS
37
90
  // custom properties (--sc-tile-text-color, --sc-content-bubble-*,
38
91
  // etc.) inherit through the shadow boundary, so canvas-level theming
@@ -143,6 +196,55 @@ var AdaptiveChatTrail = class extends LitElement {
143
196
  );
144
197
  };
145
198
  }
199
+ get _scrollable() {
200
+ return this.expanded || this.forceExpanded;
201
+ }
202
+ _trailContainer() {
203
+ return this.renderRoot.querySelector("[data-syntro-chat-trail]");
204
+ }
205
+ _isAtBottom(container) {
206
+ return container.scrollHeight - container.scrollTop - container.clientHeight <= _AdaptiveChatTrail._AT_BOTTOM_EPSILON_PX;
207
+ }
208
+ /** Count chips whose bottom edge hasn't entered the viewport yet. */
209
+ _countMessagesBelow(container) {
210
+ const viewportBottom = container.getBoundingClientRect().top + container.clientHeight;
211
+ let count = 0;
212
+ for (const chip of this.renderRoot.querySelectorAll("[data-trail-chip]")) {
213
+ if (chip.getBoundingClientRect().bottom > viewportBottom + 4) count += 1;
214
+ }
215
+ return count > 0 ? count : 1;
216
+ }
217
+ _syncContentBelow() {
218
+ const container = this._trailContainer();
219
+ if (!container) return;
220
+ this._messagesBelow = this._scrollable && !this._isAtBottom(container) ? this._countMessagesBelow(container) : 0;
221
+ }
222
+ /** Scroll so the anchored user chip sits at the top of the trail
223
+ * viewport. Idempotent — safe to re-apply on every update while the
224
+ * hold is live (content grows BELOW the anchor, so the target is
225
+ * stable; re-applying also covers late geometry, e.g. expanding
226
+ * mid-turn from the ambient strip). */
227
+ _applyAnchor() {
228
+ requestAnimationFrame(() => {
229
+ const container = this._trailContainer();
230
+ if (!container || this._anchorMsgId === null) return;
231
+ const chip = this.renderRoot.querySelector(
232
+ `[data-trail-chip-id="${String(this._anchorMsgId)}"]`
233
+ );
234
+ if (!chip) return;
235
+ const top = chip.getBoundingClientRect().top - container.getBoundingClientRect().top + container.scrollTop;
236
+ container.scrollTop = top;
237
+ this._syncContentBelow();
238
+ });
239
+ }
240
+ _pinToBottom() {
241
+ requestAnimationFrame(() => {
242
+ const container = this._trailContainer();
243
+ if (!container) return;
244
+ container.scrollTop = container.scrollHeight;
245
+ this._syncContentBelow();
246
+ });
247
+ }
146
248
  /**
147
249
  * Whether to render the thinking-dots chip after the last message.
148
250
  * True iff a request is in flight AND the assistant hasn't started
@@ -245,15 +347,36 @@ var AdaptiveChatTrail = class extends LitElement {
245
347
  entry.propsTarget.props = w.props;
246
348
  }
247
349
  updated(changed) {
248
- if (changed.has("expanded") || changed.has("messages")) {
249
- requestAnimationFrame(() => {
250
- const container = this.renderRoot.querySelector("[data-syntro-chat-trail]");
251
- if (!container) return;
252
- const distanceFromBottom = container.scrollHeight - container.scrollTop - container.clientHeight;
253
- if (distanceFromBottom < 200 || changed.has("expanded")) {
254
- container.scrollTop = container.scrollHeight;
350
+ if (!changed.has("expanded") && !changed.has("forceExpanded") && !changed.has("messages")) {
351
+ return;
352
+ }
353
+ if (changed.has("messages")) {
354
+ let lastUserIdx = -1;
355
+ for (let i = this.messages.length - 1; i >= 0; i--) {
356
+ if (this.messages[i].role === "user") {
357
+ lastUserIdx = i;
358
+ break;
255
359
  }
256
- });
360
+ }
361
+ const lastUser = lastUserIdx >= 0 ? this.messages[lastUserIdx] : void 0;
362
+ const isLiveTurn = lastUser !== void 0 && this.messages.slice(lastUserIdx + 1).every((m) => m.role !== "user" && m.status === "streaming");
363
+ if (lastUser && isLiveTurn && lastUser.id !== this._anchorMsgId) {
364
+ this._anchorMsgId = lastUser.id;
365
+ this._anchorActive = true;
366
+ this._anchorHold = true;
367
+ this._stickToBottom = false;
368
+ }
369
+ }
370
+ if (!this._scrollable) {
371
+ this._pinToBottom();
372
+ return;
373
+ }
374
+ if (this._anchorActive && this._anchorHold) {
375
+ this._applyAnchor();
376
+ } else if (this._stickToBottom) {
377
+ this._pinToBottom();
378
+ } else {
379
+ requestAnimationFrame(() => this._syncContentBelow());
257
380
  }
258
381
  }
259
382
  /** Render a set of inline widgets (reco carousel etc.) as
@@ -507,7 +630,13 @@ var AdaptiveChatTrail = class extends LitElement {
507
630
  style=${styleMap(moreStyles())}
508
631
  >${hidden > 0 ? html`↑ ${hidden} more · expand` : html`↑ expand`}</button>`}
509
632
  <div data-syntro-chat-trail-frame style=${styleMap(frameStyles)}>
510
- <div data-syntro-chat-trail style=${styleMap(containerStyles)}>
633
+ <div
634
+ data-syntro-chat-trail
635
+ style=${styleMap(containerStyles)}
636
+ @scroll=${this._onTrailScroll}
637
+ @wheel=${this._markUserScrollIntent}
638
+ @touchmove=${this._markUserScrollIntent}
639
+ >
511
640
  ${visible.map((m, i) => {
512
641
  const pos = this.expanded || this.forceExpanded ? 0 : visible.length - 1 - i;
513
642
  const state = chipVisualState(m);
@@ -529,6 +658,7 @@ var AdaptiveChatTrail = class extends LitElement {
529
658
  } : chipStyles(state, pos);
530
659
  return html`<div
531
660
  data-trail-chip
661
+ data-trail-chip-id=${String(m.id)}
532
662
  data-role=${m.role}
533
663
  data-status=${m.status ?? "complete"}
534
664
  style=${styleMap(chipStyle)}
@@ -626,6 +756,18 @@ var AdaptiveChatTrail = class extends LitElement {
626
756
  aria-hidden="true"
627
757
  style=${styleMap(blurVeilStyles())}
628
758
  ></div>`}
759
+ ${// "Scroll to most recent" pill (FEAT-1783535427): shown only in
760
+ // the scrollable modes when content extends below the viewport —
761
+ // the visitor is reading the reply top-down and this is the
762
+ // one-tap opt-in back to the newest content. Absolute inside the
763
+ // positioning frame so it floats over the scroller's bottom edge.
764
+ this._scrollable && this._messagesBelow > 0 ? html`<button
765
+ type="button"
766
+ data-trail-scroll-recent
767
+ aria-label="Scroll to most recent — ${this._messagesBelow} ${this._messagesBelow === 1 ? "message" : "messages"} below"
768
+ @click=${this._onScrollToRecent}
769
+ style=${styleMap(scrollRecentPillStyles())}
770
+ ><span aria-hidden="true" style="font-size:11px;line-height:1;">⌄</span><span>${this._messagesBelow} ${this._messagesBelow === 1 ? "message" : "messages"}</span></button>` : nothing}
629
771
  </div>
630
772
  `;
631
773
  }
@@ -651,7 +793,7 @@ var AdaptiveChatTrail = class extends LitElement {
651
793
  // workaround. As a bonus the markdown reset rules can't leak out
652
794
  // onto the host page, and the keyframes are owned by the trail's
653
795
  // own tree scope where the chip + dot elements live.
654
- AdaptiveChatTrail.styles = css`
796
+ _AdaptiveChatTrail.styles = css`
655
797
  [data-syntro-chat-trail] > :first-child {
656
798
  margin-top: auto;
657
799
  }
@@ -746,7 +888,7 @@ AdaptiveChatTrail.styles = css`
746
888
  }
747
889
  }
748
890
  `;
749
- AdaptiveChatTrail.properties = {
891
+ _AdaptiveChatTrail.properties = {
750
892
  messages: { attribute: false },
751
893
  visibleCount: { type: Number },
752
894
  expanded: { type: Boolean },
@@ -798,8 +940,36 @@ AdaptiveChatTrail.properties = {
798
940
  * Registry-backed mounter for inline widgets whose id is not their
799
941
  * custom-element tag. Injected from the chat-bar mountable.
800
942
  */
801
- mountInlineWidget: { attribute: false }
943
+ mountInlineWidget: { attribute: false },
944
+ _messagesBelow: { state: true }
802
945
  };
946
+ _AdaptiveChatTrail._AT_BOTTOM_EPSILON_PX = 50;
947
+ _AdaptiveChatTrail._USER_SCROLL_INTENT_WINDOW_MS = 1200;
948
+ var AdaptiveChatTrail = _AdaptiveChatTrail;
949
+ function scrollRecentPillStyles() {
950
+ return {
951
+ position: "absolute",
952
+ bottom: "8px",
953
+ left: "50%",
954
+ transform: "translateX(-50%)",
955
+ display: "flex",
956
+ alignItems: "center",
957
+ gap: "5px",
958
+ height: "26px",
959
+ padding: "0 10px",
960
+ borderRadius: "9999px",
961
+ border: "var(--sc-content-bubble-border, 1px solid rgba(255, 255, 255, 0.18))",
962
+ background: "var(--sc-content-bubble-bg, rgba(20, 22, 28, 0.85))",
963
+ color: "var(--sc-tile-text-color, rgba(255, 255, 255, 0.92))",
964
+ fontSize: "12px",
965
+ fontWeight: "500",
966
+ lineHeight: "1",
967
+ whiteSpace: "nowrap",
968
+ cursor: "pointer",
969
+ boxShadow: "0 2px 8px rgba(0, 0, 0, 0.25)",
970
+ zIndex: "3"
971
+ };
972
+ }
803
973
  function blurVeilStyles() {
804
974
  const mask = "linear-gradient(to bottom, black 0%, black 40%, transparent 100%)";
805
975
  return {
@@ -1330,4 +1500,4 @@ export {
1330
1500
  AdaptiveChatTrail,
1331
1501
  looksLikeSerializedModelResponse
1332
1502
  };
1333
- //# sourceMappingURL=chunk-LTG5G6WN.js.map
1503
+ //# sourceMappingURL=chunk-LSJFN2BR.js.map