@v-tilt/browser 1.13.0 → 1.13.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (109) hide show
  1. package/dist/all-external-dependencies.js.map +1 -1
  2. package/dist/array.chat.js +2 -0
  3. package/dist/array.chat.js.map +1 -0
  4. package/dist/array.chat.no-external.js +2 -0
  5. package/dist/array.chat.no-external.js.map +1 -0
  6. package/dist/array.full.chat.js +2 -0
  7. package/dist/array.full.chat.js.map +1 -0
  8. package/dist/array.full.chat.no-external.js +2 -0
  9. package/dist/array.full.chat.no-external.js.map +1 -0
  10. package/dist/array.full.js +1 -1
  11. package/dist/array.full.js.map +1 -1
  12. package/dist/array.full.no-external.js +2 -0
  13. package/dist/array.full.no-external.js.map +1 -0
  14. package/dist/array.js +1 -1
  15. package/dist/array.js.map +1 -1
  16. package/dist/array.no-external.js +1 -1
  17. package/dist/array.no-external.js.map +1 -1
  18. package/dist/chat.js +1 -1
  19. package/dist/chat.js.map +1 -1
  20. package/dist/entrypoints/all-external-dependencies.d.ts +10 -3
  21. package/dist/entrypoints/array.chat.d.ts +10 -0
  22. package/dist/entrypoints/array.chat.no-external.d.ts +6 -0
  23. package/dist/entrypoints/array.full.chat.d.ts +13 -0
  24. package/dist/entrypoints/array.full.chat.no-external.d.ts +7 -0
  25. package/dist/entrypoints/array.full.d.ts +5 -9
  26. package/dist/entrypoints/array.full.no-external.d.ts +12 -0
  27. package/dist/entrypoints/module.chat.es.d.ts +7 -0
  28. package/dist/entrypoints/module.full.chat.es.d.ts +12 -0
  29. package/dist/entrypoints/module.full.es.d.ts +12 -0
  30. package/dist/entrypoints/module.no-external.es.d.ts +1 -0
  31. package/dist/extensions/chat/bubble-drag.d.ts +20 -5
  32. package/dist/extensions/chat/chat-wrapper.d.ts +8 -2
  33. package/dist/extensions/chat/chat.d.ts +21 -351
  34. package/dist/extensions/chat/controller/__tests__/fakes/ably-realtime-fake.d.ts +84 -0
  35. package/dist/extensions/chat/controller/ably-client.d.ts +160 -0
  36. package/dist/extensions/chat/controller/ably-handlers.d.ts +95 -0
  37. package/dist/extensions/chat/controller/ably-token.d.ts +67 -0
  38. package/dist/extensions/chat/controller/chat-controller.d.ts +194 -0
  39. package/dist/extensions/chat/controller/message-delivery-status.d.ts +6 -0
  40. package/dist/extensions/chat/controller/message-order.d.ts +12 -0
  41. package/dist/extensions/chat/controller/message-stream.d.ts +49 -0
  42. package/dist/extensions/chat/lib/bubble-offset.d.ts +18 -0
  43. package/dist/extensions/chat/lib/merge-chat-config.d.ts +3 -0
  44. package/dist/extensions/chat/normalize-send-content.d.ts +2 -0
  45. package/dist/extensions/chat/outbox/message-delivery.d.ts +17 -0
  46. package/dist/extensions/chat/outbox/message-outbox.d.ts +57 -0
  47. package/dist/extensions/chat/store/chat-store.d.ts +122 -0
  48. package/dist/extensions/chat/types.d.ts +1 -19
  49. package/dist/extensions/chat/ui/ChannelItem.d.ts +12 -0
  50. package/dist/extensions/chat/ui/ChannelListView.d.ts +14 -0
  51. package/dist/extensions/chat/ui/ChatBubble.d.ts +14 -0
  52. package/dist/extensions/chat/ui/ChatHeader.d.ts +13 -0
  53. package/dist/extensions/chat/ui/ChatPanel.d.ts +14 -0
  54. package/dist/extensions/chat/ui/ChatRoot.d.ts +31 -0
  55. package/dist/extensions/chat/ui/ClosedBanner.d.ts +7 -0
  56. package/dist/extensions/chat/ui/ConnectionBanner.d.ts +32 -0
  57. package/dist/extensions/chat/ui/ConversationView.d.ts +14 -0
  58. package/dist/extensions/chat/ui/MessageBubble.d.ts +25 -0
  59. package/dist/extensions/chat/ui/MessageInput.d.ts +14 -0
  60. package/dist/extensions/chat/ui/MessageList.d.ts +19 -0
  61. package/dist/extensions/chat/ui/NewMessagesPill.d.ts +13 -0
  62. package/dist/extensions/chat/ui/Skeletons.d.ts +14 -0
  63. package/dist/extensions/chat/ui/TypingBubble.d.ts +23 -0
  64. package/dist/extensions/chat/ui/TypingIndicator.d.ts +12 -0
  65. package/dist/extensions/chat/ui/WidgetSlot.d.ts +20 -0
  66. package/dist/extensions/chat/ui/hooks/use-auto-mark-read.d.ts +18 -0
  67. package/dist/extensions/chat/ui/hooks/use-scroll-preservation.d.ts +33 -0
  68. package/dist/extensions/chat/ui/icons.d.ts +18 -0
  69. package/dist/extensions/chat/ui/message-render.d.ts +18 -0
  70. package/dist/extensions/chat/ui/shadow-styles.d.ts +13 -0
  71. package/dist/extensions/chat/ui/utils/mobile-body-scroll-lock.d.ts +14 -0
  72. package/dist/extensions/chat/widget-registry.d.ts +21 -5
  73. package/dist/extensions/chat/widgets/collect-email.d.ts +4 -2
  74. package/dist/extensions/chat/widgets/escalate-to-human.d.ts +2 -1
  75. package/dist/external-scripts-loader.js +1 -1
  76. package/dist/external-scripts-loader.js.map +1 -1
  77. package/dist/feature.d.ts +1 -0
  78. package/dist/lib/merge-vtilt-config.d.ts +9 -0
  79. package/dist/main.js +1 -1
  80. package/dist/main.js.map +1 -1
  81. package/dist/module.chat.d.ts +1788 -0
  82. package/dist/module.chat.js +2 -0
  83. package/dist/module.chat.js.map +1 -0
  84. package/dist/module.d.ts +48 -3
  85. package/dist/module.full.chat.d.ts +1792 -0
  86. package/dist/module.full.chat.js +2 -0
  87. package/dist/module.full.chat.js.map +1 -0
  88. package/dist/module.full.d.ts +1793 -0
  89. package/dist/module.full.js +2 -0
  90. package/dist/module.full.js.map +1 -0
  91. package/dist/module.js +1 -1
  92. package/dist/module.js.map +1 -1
  93. package/dist/module.no-external.d.ts +48 -3
  94. package/dist/module.no-external.js +1 -1
  95. package/dist/module.no-external.js.map +1 -1
  96. package/dist/recorder.js.map +1 -1
  97. package/dist/snippet-stub-methods.d.ts +14 -0
  98. package/dist/utils/globals.d.ts +53 -27
  99. package/dist/utils/index.d.ts +8 -0
  100. package/dist/vtilt.d.ts +6 -1
  101. package/dist/web-vitals.js.map +1 -1
  102. package/package.json +74 -71
  103. package/dist/extensions/chat/chat-styles.d.ts +0 -27
  104. package/dist/extensions/chat/message-content-styles.d.ts +0 -1
  105. package/dist/extensions/chat/message-html.d.ts +0 -6
  106. package/dist/extensions/chat/message-markdown.d.ts +0 -8
  107. package/dist/extensions/ga4-proxy.d.ts +0 -59
  108. package/dist/utils/type-utils.d.ts +0 -4
  109. package/dist/web-vitals.d.ts +0 -81
@@ -0,0 +1,18 @@
1
+ import type { BubbleOffset } from "../../../utils/globals";
2
+ export declare const DEFAULT_BUBBLE_OFFSET: {
3
+ readonly bottom: 20;
4
+ readonly right: 20;
5
+ readonly left: 20;
6
+ };
7
+ export interface ResolvedBubbleOffset {
8
+ bottom: number;
9
+ right: number;
10
+ left: number;
11
+ }
12
+ /** Merge partial offset with SDK defaults (20px on each edge). */
13
+ export declare function resolveBubbleOffset(partial?: BubbleOffset | null): ResolvedBubbleOffset;
14
+ /** Set CSS custom properties consumed by shadow-styles on the chat host. */
15
+ export declare function applyBubbleOffsetStyles(host: HTMLElement, offset: ResolvedBubbleOffset): void;
16
+ export type WidgetPosition = "bottom-right" | "bottom-left";
17
+ /** Apply fixed positioning to the pre-load lightweight bubble element. */
18
+ export declare function applyLightweightBubblePosition(el: HTMLElement, position: WidgetPosition, offset: ResolvedBubbleOffset): void;
@@ -0,0 +1,3 @@
1
+ import type { ChatConfig } from "../../../utils/globals";
2
+ /** Deep-merge a partial chat config patch into the current code config. */
3
+ export declare function mergeChatConfig(current: ChatConfig, patch: Partial<ChatConfig>): ChatConfig;
@@ -3,6 +3,8 @@ import type { ContentType } from "./types";
3
3
  /** Metadata key for markdown stored as `content_type: text`. Must match dashboard `message-rendering.ts`. */
4
4
  export declare const RICH_FORMAT_METADATA_KEY = "rich_format";
5
5
  export declare const RICH_FORMAT_MARKDOWN = "markdown";
6
+ /** Must match `MESSAGE_SOURCE_METADATA_KEY` in app `message-source.ts`. */
7
+ export declare const MESSAGE_SOURCE_METADATA_KEY = "message_source";
6
8
  export interface NormalizedOutgoingMessage {
7
9
  content: string;
8
10
  content_type: ContentType;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Client-side detection of user-message persistence from widget POST responses.
3
+ */
4
+ /** Set on streaming responses after the user row INSERT (before AI bytes). */
5
+ export declare const USER_MESSAGE_ID_HEADER = "X-Vtilt-User-Message-Id";
6
+ /** ISO timestamp for the persisted user row (streaming responses). */
7
+ export declare const USER_MESSAGE_CREATED_AT_HEADER = "X-Vtilt-User-Message-Created-At";
8
+ /**
9
+ * Returns true when the server has acknowledged the client-minted user row.
10
+ * For streaming responses, reads response headers without consuming the body.
11
+ * For JSON responses, clones and parses `{ message }`.
12
+ *
13
+ * vTilt persists the user row before streaming AI bytes. When the user ack
14
+ * header is absent (older builds), `text/plain` + `response.ok` is treated as
15
+ * persisted so retries do not spin forever.
16
+ */
17
+ export declare function isUserMessagePersisted(response: Response, expectedMessageId: string): Promise<boolean>;
@@ -0,0 +1,57 @@
1
+ /**
2
+ * Session-scoped outbound message queue for reliable widget delivery.
3
+ */
4
+ import type { ChatMessage } from "../../../utils/globals";
5
+ /** Requeue `sending` rows stuck longer than this (crash / abort mid-flight). */
6
+ export declare const STALE_SENDING_MS = 30000;
7
+ /** Reuse a recently created channel for `newChannel` drain instead of POSTing again. */
8
+ export declare const LAST_CREATED_CHANNEL_TTL_MS: number;
9
+ export type OutboxEntryState = "pending" | "sending" | "failed";
10
+ export interface OutboxEntry {
11
+ messageId: string;
12
+ channelId: string;
13
+ distinctId: string;
14
+ body: string;
15
+ content_type: ChatMessage["content_type"];
16
+ metadata: Record<string, unknown>;
17
+ created_at: string;
18
+ state: OutboxEntryState;
19
+ attempts: number;
20
+ /** Epoch ms — do not drain before this instant (exponential backoff). */
21
+ nextRetryAt: number;
22
+ /** Epoch ms — when the row entered `sending` (stale recovery). */
23
+ sendingSince?: number;
24
+ /** When set, drain creates a channel before POSTing the message. */
25
+ newChannel?: {
26
+ skipGreeting: boolean;
27
+ };
28
+ }
29
+ export interface LastCreatedChannelHint {
30
+ channelId: string;
31
+ at: number;
32
+ }
33
+ export declare function outboxStorageKey(projectToken: string, distinctId: string): string;
34
+ export declare function lastCreatedChannelStorageKey(storageKey: string): string;
35
+ export declare function readLastCreatedChannelHint(storageKey: string, now?: number): LastCreatedChannelHint | null;
36
+ export declare function writeLastCreatedChannelHint(storageKey: string, channelId: string): void;
37
+ export declare class MessageOutbox {
38
+ private readonly _storageKey;
39
+ /** Fallback when sessionStorage is unavailable or writes fail. */
40
+ private _memory;
41
+ constructor(_storageKey: string);
42
+ list(): OutboxEntry[];
43
+ get(messageId: string): OutboxEntry | undefined;
44
+ upsert(entry: OutboxEntry): void;
45
+ patch(messageId: string, partial: Partial<OutboxEntry>): void;
46
+ remove(messageId: string): void;
47
+ /** Entries eligible for drain (not in backoff, not actively sending). */
48
+ dueEntries(now?: number): OutboxEntry[];
49
+ /**
50
+ * Recover rows left in `sending` after a crash, tab kill, or aborted fetch.
51
+ */
52
+ requeueStaleSending(maxAgeMs?: number, now?: number): void;
53
+ private _readStorage;
54
+ private _write;
55
+ }
56
+ /** Backoff: 1s, 2s, 4s, … capped at 60s. */
57
+ export declare function nextOutboxRetryAt(attempts: number, now?: number): number;
@@ -0,0 +1,122 @@
1
+ /**
2
+ * ChatStore — signal-based state container for the chat widget.
3
+ *
4
+ * All UI state (view, messages, channels, typing) and all realtime state
5
+ * (connection state, channel ids) live here as @preact/signals signals.
6
+ * Components read `.value` inside JSX; Preact's signal integration re-renders
7
+ * only the affected subtree.
8
+ *
9
+ * Mutations are direct assignments (`store.isOpen.value = true`) from the
10
+ * controller or the Ably handler layer. The store itself has no I/O and no
11
+ * DOM dependency, which makes the rest of the widget trivially testable.
12
+ *
13
+ * See: docs/modules/chat/widget-architecture.md
14
+ */
15
+ import type { ChatChannel, ChatChannelSummary, ChatMessage, ChatTheme, ChatWidgetView } from "../../../utils/globals";
16
+ /** Realtime connection state vocabulary the store exposes to UI. */
17
+ export type RealtimeConnectionState = "disconnected" | "connecting" | "connected" | "suspended" | "failed";
18
+ /** Banner state derived from connection state. */
19
+ export type ConnectionBanner = "none" | "reconnecting" | "failed";
20
+ /** Default theme — Inter is vTilt's brand font; system stack as fallback. */
21
+ export declare const DEFAULT_THEME: Required<Pick<ChatTheme, "primaryColor" | "fontFamily" | "borderRadius">>;
22
+ export declare class ChatStore {
23
+ isOpen: import("@preact/signals").Signal<boolean>;
24
+ isVisible: import("@preact/signals").Signal<boolean>;
25
+ isLoading: import("@preact/signals").Signal<boolean>;
26
+ currentView: import("@preact/signals").Signal<ChatWidgetView>;
27
+ channels: import("@preact/signals").Signal<ChatChannelSummary[]>;
28
+ channel: import("@preact/signals").Signal<ChatChannel | null>;
29
+ messages: import("@preact/signals").Signal<ChatMessage[]>;
30
+ isTyping: import("@preact/signals").Signal<boolean>;
31
+ typingSender: import("@preact/signals").Signal<string | null>;
32
+ typingSenderType: import("@preact/signals").Signal<"user" | "agent" | "ai" | null>;
33
+ agentLastReadAt: import("@preact/signals").Signal<string | null>;
34
+ initialUserReadAt: import("@preact/signals").Signal<string | null>;
35
+ userScrolledUp: import("@preact/signals").Signal<boolean>;
36
+ unreadNewMessagesCount: import("@preact/signals").Signal<number>;
37
+ theme: import("@preact/signals").Signal<Required<Pick<ChatTheme, "borderRadius" | "fontFamily" | "primaryColor">>>;
38
+ /** Live connection state from Ably's connection events. */
39
+ connectionState: import("@preact/signals").Signal<RealtimeConnectionState>;
40
+ /** Numeric Ably project id (set after first successful token request). */
41
+ ablyProjectId: import("@preact/signals").Signal<number | null>;
42
+ /** Channel id the realtime layer has attached to. `null` on list view. */
43
+ realtimeChannelId: import("@preact/signals").Signal<string | null>;
44
+ /**
45
+ * True once both the per-conversation main + typing Ably channels have
46
+ * successfully attached for `realtimeChannelId`. Flips back to `false`
47
+ * on detach, attach failure, or identity change. Drives the "Connecting…"
48
+ * indicator in the conversation view.
49
+ */
50
+ realtimeAttached: import("@preact/signals").Signal<boolean>;
51
+ /**
52
+ * Channel id whose details we are currently fetching. Guards against rapid
53
+ * navigation: a slow GET resolver checks `pendingRealtimeChannelId === id`
54
+ * before writing into the store.
55
+ */
56
+ pendingRealtimeChannelId: import("@preact/signals").Signal<string | null>;
57
+ /** True while `handleIdentityChange()` is tearing down and rebuilding. */
58
+ identityChangeInFlight: import("@preact/signals").Signal<boolean>;
59
+ /**
60
+ * Distinct id the live Ably connection was authorised for. Used by the
61
+ * `connected` event handler to detect drift after `vt.identify()`.
62
+ */
63
+ lastConnectedDistinctId: import("@preact/signals").Signal<string | null>;
64
+ /** Max distinct channels to keep in the cache. LRU-trimmed on write. */
65
+ private static readonly MESSAGES_CACHE_LIMIT;
66
+ private _messagesCache;
67
+ isConnected: import("@preact/signals").ReadonlySignal<boolean>;
68
+ /**
69
+ * Tier A aggregate from `GET /api/chat/widget/unread` while the panel is
70
+ * closed. Cleared on `open()` so channel rows own the badge again.
71
+ */
72
+ polledUnreadTotal: import("@preact/signals").Signal<number | null>;
73
+ /** Sum of unread across all channels — drives the bubble badge. */
74
+ unreadCount: import("@preact/signals").ReadonlySignal<number>;
75
+ /** Banner shown above the input when realtime is degraded. */
76
+ connectionBanner: import("@preact/signals").ReadonlySignal<ConnectionBanner>;
77
+ /**
78
+ * True when the realtime transport is fully ready for the *currently
79
+ * displayed* conversation: connection is `connected`, the channel object
80
+ * exists, and the realtime layer has attached the per-conversation
81
+ * channels for it. Used by the UI to show a subtle "Connecting…" pill
82
+ * while realtime is still spinning up after navigation or identify.
83
+ */
84
+ realtimeReady: import("@preact/signals").ReadonlySignal<boolean>;
85
+ /**
86
+ * Replace `messages` only if `next` differs by identity from current array.
87
+ * Signals trigger re-renders only on identity change of the value, so we
88
+ * always pass a new array to ensure subscribers re-evaluate.
89
+ */
90
+ setMessages(next: ChatMessage[]): void;
91
+ /** Patch a single channel summary in place, then re-sort by last_message_at. */
92
+ patchChannelSummary(id: string, patch: Partial<ChatChannelSummary>): void;
93
+ /** Insert a channel summary at the top of the list (skip if id exists). */
94
+ prependChannelSummary(summary: ChatChannelSummary): void;
95
+ /** Reset all realtime / channel-scoped state. Used on goToChannelList()
96
+ * and `_handleIdentityChange()` — places where we leave the conversation
97
+ * entirely. For *between-channel* transitions, prefer
98
+ * `prepareChannelSwitch()` which leaves the realtime signals tracking
99
+ * the last good attach so `realtimeReady` doesn't blink false. */
100
+ clearActiveChannel(): void;
101
+ /** Soft reset between channels — clears conversation-scoped state but
102
+ * leaves `realtimeChannelId` / `realtimeAttached` tracking the previous
103
+ * successful attach. `AblyClient.attachConversation()` atomically swaps
104
+ * those signals once the new channel is fully attached, so
105
+ * `realtimeReady` (and therefore the "Connecting…" banner) doesn't
106
+ * flicker during normal channel switches.
107
+ *
108
+ * Inbound stale frames from the previous channel are already filtered
109
+ * by AblyClient's channel-ref guard, so leaving these signals true is
110
+ * safe from a correctness standpoint. */
111
+ prepareChannelSwitch(): void;
112
+ /**
113
+ * Cache the message list for a channel so a later `selectChannel(id)`
114
+ * can paint instantly. Drops `temp-*` rows (they don't belong to a
115
+ * persisted state) and LRU-trims to `MESSAGES_CACHE_LIMIT`.
116
+ */
117
+ cacheMessages(channelId: string, messages: ChatMessage[]): void;
118
+ /** Returns the cached message list for a channel, or null if absent. */
119
+ getCachedMessages(channelId: string): ChatMessage[] | null;
120
+ /** Drop a channel from the cache. Used on full identity reset. */
121
+ clearMessagesCache(): void;
122
+ }
@@ -4,7 +4,7 @@
4
4
  * Type definitions for the chat widget extension.
5
5
  * Core types are re-exported from globals.ts for consistency.
6
6
  */
7
- export type { ChatMessage, ChatChannel, ChatChannelSummary, ChatConfig, ChatTheme, ChatWidgetView, LazyLoadedChatInterface, SendChatMessageContent, SendChatMessageOptions, } from "../../utils/globals";
7
+ export type { BubbleOffset, ChatMessage, ChatChannel, ChatChannelSummary, ChatConfig, ChatTheme, ChatWidgetView, LazyLoadedChatInterface, SendChatMessageContent, SendChatMessageOptions, } from "../../utils/globals";
8
8
  export { messageUsesRichRendering, normalizeSendChatMessageContent, } from "./normalize-send-content";
9
9
  export type { NormalizedOutgoingMessage } from "./normalize-send-content";
10
10
  /**
@@ -77,24 +77,6 @@ export interface ChannelMessageEventProperties {
77
77
  $content_preview: string;
78
78
  $ai_mode?: boolean;
79
79
  }
80
- /**
81
- * Internal widget state
82
- */
83
- export interface ChatWidgetState {
84
- isOpen: boolean;
85
- isVisible: boolean;
86
- isConnected: boolean;
87
- isLoading: boolean;
88
- unreadCount: number;
89
- currentView: import("../../utils/globals").ChatWidgetView;
90
- channels: import("../../utils/globals").ChatChannelSummary[];
91
- channel: import("../../utils/globals").ChatChannel | null;
92
- messages: import("../../utils/globals").ChatMessage[];
93
- isTyping: boolean;
94
- typingSender: string | null;
95
- typingSenderType: "user" | "agent" | "ai" | null;
96
- agentLastReadAt: string | null;
97
- }
98
80
  /**
99
81
  * Message callback type
100
82
  */
@@ -0,0 +1,12 @@
1
+ /**
2
+ * ChannelItem — single row in the channel list. Renders avatar, name,
3
+ * relative time, preview, and unread badge.
4
+ */
5
+ import type { JSX } from "preact";
6
+ import type { ChatChannelSummary } from "../../../utils/globals";
7
+ interface ChannelItemProps {
8
+ channel: ChatChannelSummary;
9
+ onSelect: (id: string) => void;
10
+ }
11
+ export declare function ChannelItem({ channel, onSelect, }: ChannelItemProps): JSX.Element;
12
+ export {};
@@ -0,0 +1,14 @@
1
+ /**
2
+ * ChannelListView — three modes: empty welcome (no channels, not loading),
3
+ * skeleton (no channels, loading), populated list. The controller manages
4
+ * selection / creation flow; this component only renders + emits intent.
5
+ */
6
+ import type { JSX } from "preact";
7
+ import type { ChatController } from "../controller/chat-controller";
8
+ import type { ChatStore } from "../store/chat-store";
9
+ interface ChannelListViewProps {
10
+ store: ChatStore;
11
+ controller: ChatController;
12
+ }
13
+ export declare function ChannelListView({ store, controller, }: ChannelListViewProps): JSX.Element;
14
+ export {};
@@ -0,0 +1,14 @@
1
+ /**
2
+ * ChatBubble — circular floating button rendered when the panel is closed.
3
+ * Shows the unread badge derived from the channels-summary computed signal.
4
+ */
5
+ import type { JSX, Ref } from "preact";
6
+ import type { ChatController } from "../controller/chat-controller";
7
+ import type { ChatStore } from "../store/chat-store";
8
+ interface ChatBubbleProps {
9
+ store: ChatStore;
10
+ controller: ChatController;
11
+ bubbleRef: Ref<HTMLButtonElement>;
12
+ }
13
+ export declare function ChatBubble({ store, controller, bubbleRef, }: ChatBubbleProps): JSX.Element;
14
+ export {};
@@ -0,0 +1,13 @@
1
+ /**
2
+ * ChatHeader — top bar that shows either the "Messages" title (list view) or
3
+ * back button + avatar + agent label + online indicator (conversation view).
4
+ */
5
+ import type { JSX } from "preact";
6
+ import type { ChatController } from "../controller/chat-controller";
7
+ import type { ChatStore } from "../store/chat-store";
8
+ interface ChatHeaderProps {
9
+ store: ChatStore;
10
+ controller: ChatController;
11
+ }
12
+ export declare function ChatHeader({ store, controller, }: ChatHeaderProps): JSX.Element;
13
+ export {};
@@ -0,0 +1,14 @@
1
+ /**
2
+ * ChatPanel — the open widget panel: header + content (list or conversation).
3
+ * Display is driven via the parent container's `data-open` attribute so the
4
+ * close/open animation can hook in via CSS.
5
+ */
6
+ import type { JSX } from "preact";
7
+ import type { ChatController } from "../controller/chat-controller";
8
+ import type { ChatStore } from "../store/chat-store";
9
+ interface ChatPanelProps {
10
+ store: ChatStore;
11
+ controller: ChatController;
12
+ }
13
+ export declare function ChatPanel({ store, controller }: ChatPanelProps): JSX.Element;
14
+ export {};
@@ -0,0 +1,31 @@
1
+ /**
2
+ * ChatRoot — top-level Preact component + Shadow DOM bootstrap.
3
+ *
4
+ * `mountChatUI()` creates a host `<div>` on `<body>`, attaches an open
5
+ * shadow root, injects the shared style string, and renders `<ChatRoot/>`.
6
+ *
7
+ * The host element receives a CSS transform when `bubbleDraggable` is
8
+ * enabled (via `bubble-drag.ts`); the shadow content is purely structural.
9
+ */
10
+ import { type JSX } from "preact";
11
+ import type { ChatController } from "../controller/chat-controller";
12
+ import type { ChatStore } from "../store/chat-store";
13
+ export interface MountChatUIArgs {
14
+ store: ChatStore;
15
+ controller: ChatController;
16
+ position: "bottom-right" | "bottom-left";
17
+ bubbleDraggable: boolean;
18
+ }
19
+ export interface MountedChatUI {
20
+ host: HTMLElement;
21
+ destroy(): void;
22
+ }
23
+ export declare function mountChatUI(args: MountChatUIArgs): MountedChatUI;
24
+ interface ChatRootProps {
25
+ store: ChatStore;
26
+ controller: ChatController;
27
+ position: "bottom-right" | "bottom-left";
28
+ bubbleDraggable: boolean;
29
+ }
30
+ export declare function ChatRoot({ store, controller, position, bubbleDraggable, }: ChatRootProps): JSX.Element;
31
+ export {};
@@ -0,0 +1,7 @@
1
+ /**
2
+ * ClosedBanner — flat banner shown above the input area when the active
3
+ * channel's status is "closed". Replaces the imperative DOM banner manipulation
4
+ * the old chat.ts performed in `_updateUI`.
5
+ */
6
+ import type { JSX } from "preact";
7
+ export declare function ClosedBanner(): JSX.Element;
@@ -0,0 +1,32 @@
1
+ /**
2
+ * ConnectionBanner — minimal, action-only surface for degraded realtime.
3
+ *
4
+ * Industry alignment (Intercom / Slack / iMessage):
5
+ *
6
+ * - The **initial attach** ("connecting") is invisible. The user can
7
+ * already see their history (painted from the REST hydrate) and the
8
+ * SDK queues anything they type until realtime attaches, so there is
9
+ * nothing actionable to surface. The old "Connecting…" banner was
10
+ * visual noise that flashed on every channel switch and `vt.identify()`.
11
+ *
12
+ * - **Reconnecting** (Ably is `suspended` / `disconnected` mid-session
13
+ * and retrying) only surfaces after a 2.5 s grace window so the brief
14
+ * blips that happen on a healthy network never reach the UI. When it
15
+ * does appear it's a compact inline pill with a pulsing dot — not a
16
+ * full-width amber banner — so it doesn't dominate the conversation.
17
+ *
18
+ * - **Failed** (Ably terminal failure) is the only state that asks the
19
+ * user to do something, so we render an actionable card with a Retry
20
+ * button instead of a passive "refresh to retry" hint.
21
+ *
22
+ * Anything that needs to know the SDK is mid-attach (e.g. typing
23
+ * indicators) reads `store.realtimeReady` directly — this banner is
24
+ * purely a user-facing surface.
25
+ */
26
+ import type { JSX } from "preact";
27
+ import type { ChatStore } from "../store/chat-store";
28
+ interface ConnectionBannerProps {
29
+ store: ChatStore;
30
+ }
31
+ export declare function ConnectionBanner({ store, }: ConnectionBannerProps): JSX.Element | null;
32
+ export {};
@@ -0,0 +1,14 @@
1
+ /**
2
+ * ConversationView — message list (which now owns the trailing typing
3
+ * bubble) + connection banner + closed banner + message input. Driven
4
+ * entirely by store signals.
5
+ */
6
+ import type { JSX } from "preact";
7
+ import type { ChatController } from "../controller/chat-controller";
8
+ import type { ChatStore } from "../store/chat-store";
9
+ interface ConversationViewProps {
10
+ store: ChatStore;
11
+ controller: ChatController;
12
+ }
13
+ export declare function ConversationView({ store, controller, }: ConversationViewProps): JSX.Element;
14
+ export {};
@@ -0,0 +1,25 @@
1
+ /**
2
+ * MessageBubble — renders a single chat message (avatar, bubble, meta).
3
+ *
4
+ * Markdown / HTML rendering for AI/agent messages goes through
5
+ * `formatMessageBodyHtml` which sanitizes inside a detached `<div>` then
6
+ * we inject the sanitized result via `dangerouslySetInnerHTML`. User
7
+ * messages render as escaped text unless they opted into a rich format
8
+ * (markdown / html) at send time.
9
+ *
10
+ * Memoised so unrelated store updates (typing, scroll, channel list)
11
+ * don't re-render every bubble. Reference equality on `message` is the
12
+ * fast path — `upsertMessage` deliberately returns the same object when
13
+ * nothing actually changed, so duplicate Ably echoes don't churn either.
14
+ */
15
+ import type { JSX } from "preact";
16
+ import type { ChatMessage } from "../../../utils/globals";
17
+ import type { ChatController } from "../controller/chat-controller";
18
+ interface MessageBubbleProps {
19
+ message: ChatMessage;
20
+ controller: ChatController;
21
+ isReadByAgent: boolean;
22
+ }
23
+ declare function MessageBubbleImpl({ message, controller, isReadByAgent, }: MessageBubbleProps): JSX.Element;
24
+ export declare const MessageBubble: typeof MessageBubbleImpl;
25
+ export {};
@@ -0,0 +1,14 @@
1
+ /**
2
+ * MessageInput — auto-resizing textarea + send button. Owns its own value
3
+ * (not in the store) because keystroke-level reactivity through the store
4
+ * would re-render the whole conversation tree on every character.
5
+ */
6
+ import type { JSX } from "preact";
7
+ import type { ChatController } from "../controller/chat-controller";
8
+ import type { ChatStore } from "../store/chat-store";
9
+ interface MessageInputProps {
10
+ store: ChatStore;
11
+ controller: ChatController;
12
+ }
13
+ export declare function MessageInput({ store, controller, }: MessageInputProps): JSX.Element;
14
+ export {};
@@ -0,0 +1,19 @@
1
+ /**
2
+ * MessageList — renders the message stream with scroll preservation and a
3
+ * "new messages" pill. Uses keyed children so Preact reuses existing
4
+ * MessageBubble nodes across renders.
5
+ *
6
+ * The typing indicator lives **inside** this list as a trailing
7
+ * `TypingBubble`, not as a separate element below the list, so the dots
8
+ * occupy the exact slot the real message will land in and the bubble
9
+ * shape stays put when content arrives.
10
+ */
11
+ import type { JSX } from "preact";
12
+ import type { ChatController } from "../controller/chat-controller";
13
+ import type { ChatStore } from "../store/chat-store";
14
+ interface MessageListProps {
15
+ store: ChatStore;
16
+ controller: ChatController;
17
+ }
18
+ export declare function MessageList({ store, controller, }: MessageListProps): JSX.Element;
19
+ export {};
@@ -0,0 +1,13 @@
1
+ /**
2
+ * NewMessagesPill — floating "↓ N new messages" affordance shown when the
3
+ * user has scrolled up while new agent/AI messages arrived. Clicking jumps
4
+ * back to the bottom and clears the counter.
5
+ */
6
+ import type { JSX } from "preact";
7
+ interface NewMessagesPillProps {
8
+ count: number;
9
+ visible: boolean;
10
+ onJump: () => void;
11
+ }
12
+ export declare function NewMessagesPill({ count, visible, onJump, }: NewMessagesPillProps): JSX.Element;
13
+ export {};
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Skeleton placeholders for the channel list (first-load) and conversation
3
+ * (mid-`selectChannel`). Sized to match the live row dimensions so layout
4
+ * doesn't jump when real content arrives.
5
+ *
6
+ * The new-channel pending placeholder used to live here as
7
+ * `NewChannelLoader`; it has been unified with the live typing indicator
8
+ * — both flow through `TypingBubble` rendered inside `MessageList`.
9
+ */
10
+ import type { JSX } from "preact";
11
+ /** Skeleton stack for the channel list while channels are loading. */
12
+ export declare function ListSkeleton(): JSX.Element;
13
+ /** Three alternating skeleton bubbles to imply message history is incoming. */
14
+ export declare function MessageSkeleton(): JSX.Element;
@@ -0,0 +1,23 @@
1
+ /**
2
+ * TypingBubble — incoming-style chat bubble with animated typing dots and
3
+ * the matching avatar. Rendered as the last child of the message list so
4
+ * the dots occupy the exact slot the real message will land in; when the
5
+ * message arrives the bubble shape stays put and the content morphs from
6
+ * dots to text. Used for two cases:
7
+ *
8
+ * 1. Live typing event (agent / AI publishes `is_typing: true`) —
9
+ * rendered while `store.isTyping` is true.
10
+ * 2. New-channel pending state — rendered while we wait for the server
11
+ * response to `POST /channels` (replaces the old `NewChannelLoader`
12
+ * so the empty-conversation placeholder and the live indicator share
13
+ * one component).
14
+ */
15
+ import type { JSX } from "preact";
16
+ interface TypingBubbleProps {
17
+ /** Who's composing — drives avatar variant and label. */
18
+ senderType: "ai" | "agent";
19
+ /** Agent's display name. Ignored for AI (always "AI Assistant"). */
20
+ senderName?: string | null;
21
+ }
22
+ export declare function TypingBubble({ senderType, senderName, }: TypingBubbleProps): JSX.Element;
23
+ export {};
@@ -0,0 +1,12 @@
1
+ /**
2
+ * TypingIndicator — animated three-dot affordance shown above the input
3
+ * when an agent or the AI is composing a reply. Driven entirely by store
4
+ * signals; no manual show/hide DOM toggling.
5
+ */
6
+ import type { JSX } from "preact";
7
+ import type { ChatStore } from "../store/chat-store";
8
+ interface TypingIndicatorProps {
9
+ store: ChatStore;
10
+ }
11
+ export declare function TypingIndicator({ store }: TypingIndicatorProps): JSX.Element;
12
+ export {};
@@ -0,0 +1,20 @@
1
+ /**
2
+ * WidgetSlot — renders inline widgets (e.g. collect-email, escalate-to-human)
3
+ * for a message via the ChatWidgetRegistry. Reads the same metadata sources
4
+ * the previous string-builder did, but the registry now returns Preact
5
+ * VNodes so widgets compose naturally inside the message list.
6
+ *
7
+ * Submission UI is metadata-driven: once a widget is `submitted: true`
8
+ * (set by the controller after a successful `onAction`), this component
9
+ * renders a type-specific confirmation instead of the input form.
10
+ */
11
+ import type { JSX } from "preact";
12
+ import type { ChatMessage } from "../../../utils/globals";
13
+ import type { ChatController } from "../controller/chat-controller";
14
+ interface WidgetSlotProps {
15
+ message: ChatMessage;
16
+ controller: ChatController;
17
+ }
18
+ declare function WidgetSlotImpl({ message, controller, }: WidgetSlotProps): JSX.Element | null;
19
+ export declare const WidgetSlot: typeof WidgetSlotImpl;
20
+ export {};
@@ -0,0 +1,18 @@
1
+ /**
2
+ * useAutoMarkRead — fire `markAsRead` shortly after the user reaches the
3
+ * conversation view with unread messages, and again when new messages
4
+ * arrive while the widget is open.
5
+ *
6
+ * The actual server call is debounced inside the controller via the
7
+ * `_isMarkingRead` guard.
8
+ */
9
+ import type { ChatMessage } from "../../../../utils/globals";
10
+ import type { ChatController } from "../../controller/chat-controller";
11
+ import type { ChatStore } from "../../store/chat-store";
12
+ interface UseAutoMarkReadArgs {
13
+ store: ChatStore;
14
+ controller: ChatController;
15
+ messages: ChatMessage[];
16
+ }
17
+ export declare function useAutoMarkRead({ store, controller, messages, }: UseAutoMarkReadArgs): void;
18
+ export {};
@@ -0,0 +1,33 @@
1
+ /**
2
+ * useScrollPreservation — manage the message list's scroll behaviour:
3
+ *
4
+ * 1. Auto-scroll to bottom when:
5
+ * - The user was already near the bottom AND a new message arrived
6
+ * - The user just sent a message (`stickToBottom()` is called by sendMessage path)
7
+ * - The conversation first mounts
8
+ * 2. Preserve scroll position when the user has scrolled up (reading history)
9
+ * and new agent/AI messages arrive — instead, surface the
10
+ * "↓ N new messages" pill.
11
+ * 3. Detect when the user manually scrolls back to the bottom and reset the
12
+ * "scrolled up" flag + clear the pill counter.
13
+ */
14
+ import type { RefObject } from "preact";
15
+ import type { ChatMessage } from "../../../../utils/globals";
16
+ import type { ChatStore } from "../../store/chat-store";
17
+ interface UseScrollPreservationArgs {
18
+ store: ChatStore;
19
+ scrollRef: RefObject<HTMLElement | null>;
20
+ messages: ChatMessage[];
21
+ /**
22
+ * Whether the trailing typing bubble is currently rendered. When it
23
+ * flips from `false` → `true` the bubble grows the list by ~one
24
+ * message-height; if the user was at the bottom we snap them to keep
25
+ * the dots in view, matching the behaviour for newly-arrived
26
+ * messages.
27
+ */
28
+ typingActive?: boolean;
29
+ }
30
+ export declare function useScrollPreservation({ store, scrollRef, messages, typingActive, }: UseScrollPreservationArgs): {
31
+ jumpToBottom: () => void;
32
+ };
33
+ export {};