@v-tilt/browser 1.12.0 → 1.13.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.
@@ -35,6 +35,15 @@ export declare class LazyLoadedChat implements LazyLoadedChatInterface {
35
35
  private _widgetRegistry;
36
36
  /** Tracks whether show() was explicitly called (vs open() temporarily revealing the container). */
37
37
  private _bubbleExplicitShow;
38
+ private _lastConnectedDistinctId;
39
+ private _unsubscribeIdentity;
40
+ private _unsubscribeReset;
41
+ private _identityChangeInFlight;
42
+ private _userScrolledUp;
43
+ private _unreadNewMessagesCount;
44
+ private _messagesScrollListenerBound;
45
+ private _conversationListenersBound;
46
+ private _channelListListenersBound;
38
47
  constructor(instance: VTilt, config?: ChatConfig);
39
48
  get isOpen(): boolean;
40
49
  get isConnected(): boolean;
@@ -58,6 +67,26 @@ export declare class LazyLoadedChat implements LazyLoadedChatInterface {
58
67
  toggle(): void;
59
68
  show(): void;
60
69
  hide(): void;
70
+ /**
71
+ * Build a stub ChatChannel from a ChatChannelSummary so that
72
+ * `selectChannel` can populate state synchronously while the real
73
+ * `GET /channels/:id` response is in flight. The header reads `ai_mode`
74
+ * and `status` immediately; remaining fields are filled by the response.
75
+ */
76
+ private _stubChannelFromSummary;
77
+ /**
78
+ * Handle a vTilt identity change (anon → identified, or user → user).
79
+ *
80
+ * Ably enforces that the connection's `clientId` is immutable for the
81
+ * lifetime of a Realtime connection. Authorizing a token with a different
82
+ * `clientId` puts the connection into the terminal `failed` state and
83
+ * produces a 40102 error. The correct response is to fully close the
84
+ * existing client and create a new one with a token bound to the new id.
85
+ *
86
+ * This also clears per-identity state (channels, messages, unread count)
87
+ * since those belong to the previous user.
88
+ */
89
+ private _handleIdentityChange;
61
90
  getChannels(): Promise<void>;
62
91
  selectChannel(channelId: string): Promise<void>;
63
92
  createChannel(): Promise<void>;
@@ -93,6 +122,13 @@ export declare class LazyLoadedChat implements LazyLoadedChatInterface {
93
122
  private _refreshAblyToken;
94
123
  /**
95
124
  * Subscribe to per-channel messages and typing for a specific conversation.
125
+ *
126
+ * Attach is async; the user may close the widget or switch to a different
127
+ * channel while we are still awaiting `attach()`. In that case
128
+ * `_disconnectChannel()` runs concurrently — it nulls `this._ablyChannel`
129
+ * and calls `detach()`, which makes Ably reject the pending `attach()` with
130
+ * "Attach request superseded by a subsequent detach request". We treat
131
+ * that race as expected: bail out silently instead of logging an error.
96
132
  */
97
133
  private _connectChannel;
98
134
  /**
@@ -155,13 +191,107 @@ export declare class LazyLoadedChat implements LazyLoadedChatInterface {
155
191
  */
156
192
  private _constrainWidgetToViewport;
157
193
  private _updateHeader;
158
- private _getChannelListHTML;
194
+ /**
195
+ * Render the channel-list view with mode-aware diffing.
196
+ *
197
+ * Three modes share a single content container so the click delegation
198
+ * stays bound across them:
199
+ * - empty: no channels and not loading → welcome / CTA screen
200
+ * - skeleton: no channels and loading → shimmer placeholders
201
+ * - populated: 1+ channels → scaffold + diff items by id
202
+ *
203
+ * Only the populated mode preserves item DOM identity; the other two are
204
+ * rare transient states where a full innerHTML write is cheap.
205
+ */
206
+ private _renderChannelList;
207
+ /**
208
+ * Wire a single delegated click handler on the list container so we don't
209
+ * re-attach per-item listeners on every render.
210
+ */
211
+ private _setupChannelListDelegation;
212
+ private _createChannelItemNode;
213
+ private _updateChannelItemNode;
214
+ /**
215
+ * Compact fingerprint used to decide whether a channel-item DOM node needs
216
+ * an update. Only includes fields that the rendered HTML depends on.
217
+ */
218
+ private _channelItemFingerprint;
219
+ private _getEmptyListHTML;
220
+ private _getListScaffoldHTML;
221
+ private _getListSkeletonHTML;
222
+ /**
223
+ * Build a skeleton bubble that mirrors the shape of a real chat message
224
+ * from `_getMessageHTML`: same avatar size (32×32), same bubble padding
225
+ * (12px 16px), same asymmetric border-radius (20/20/20/4 incoming,
226
+ * 20/20/4/20 outgoing), and a meta line underneath sized like the
227
+ * "Sender · 12:34" footer. The shimmer fills the bubble interior — not
228
+ * floating lines — so the placeholder reads as a "loading message"
229
+ * rather than a generic content blob. Width varies per call so the
230
+ * stack of bubbles has visual rhythm instead of looking like a parade
231
+ * of identical rectangles.
232
+ */
233
+ private _getSkeletonBubbleHTML;
234
+ /**
235
+ * Skeleton placeholder shown while `selectChannel` is loading the
236
+ * messages of an existing conversation. Three alternating bubbles
237
+ * (incoming → outgoing → incoming) imply history is on its way.
238
+ * Bubble widths vary to avoid the "fake repeated rectangles" look.
239
+ */
240
+ private _getMessageSkeletonHTML;
241
+ /**
242
+ * New-conversation loader shown while `createChannel` (or
243
+ * `sendMessage({channel: 'new'})`) is in flight. A single incoming
244
+ * bubble using *exactly* the same shape, padding, border-radius, and
245
+ * avatar treatment as a real incoming message from `_getMessageHTML`
246
+ * — so when the real greeting arrives it morphs in place rather than
247
+ * snapping to a new layout. The avatar uses the live AI/Support color
248
+ * + icon (identity is known from `_config.aiMode`), and the bubble
249
+ * carries three animated typing dots instead of text, signalling
250
+ * "the assistant is preparing your greeting."
251
+ */
252
+ private _getNewChannelLoaderHTML;
159
253
  private _getChannelItemHTML;
160
254
  private _getConversationHTML;
161
- private _attachChannelListListeners;
162
255
  private _attachConversationListeners;
163
256
  private _formatRelativeTime;
257
+ /**
258
+ * Render messages with a key-based diff.
259
+ *
260
+ * - Each message node carries `data-msg-key="msg:<id>"` and a fingerprint;
261
+ * if both match what we already have, the node is left untouched.
262
+ * - The "New" divider lives as a sibling node with its own key so it
263
+ * participates in the same diff (no flicker when it moves).
264
+ * - Scroll position is preserved: we only snap to the bottom when the user
265
+ * was already near the bottom or just sent their own message. Otherwise
266
+ * we surface a "↓ N new messages" pill instead of yanking them down.
267
+ * - Newly-inserted message nodes get the `vtilt-msg-enter` class for a
268
+ * subtle fade-in animation. The initial bulk render skips animation.
269
+ */
164
270
  private _renderMessages;
271
+ /**
272
+ * Build the "New" divider node (rendered above the first unread agent/AI
273
+ * message when scrolled into view).
274
+ */
275
+ private _createMessageDividerNode;
276
+ /**
277
+ * Build a single message DOM node from the message model.
278
+ */
279
+ private _createMessageNode;
280
+ /**
281
+ * Compact fingerprint used to decide whether a message node needs an update.
282
+ * Streaming AI responses update the content field many times — re-rendering
283
+ * the same DOM lets the cursor and layout stay stable for the user.
284
+ */
285
+ private _messageFingerprint;
286
+ /**
287
+ * Wire (once per container) a scroll listener that tracks whether the user
288
+ * is reading history above the latest message. Used to suppress auto-scroll
289
+ * and to dismiss the "new messages" pill when the user scrolls back down.
290
+ */
291
+ private _setupMessagesScrollListener;
292
+ private _isNearBottom;
293
+ private _showNewMessagesPill;
294
+ private _hideNewMessagesPill;
165
295
  private _getMessageHTML;
166
296
  private _isMessageReadByAgent;
167
297
  /**