@trigger.dev/sdk 0.0.0-chat-prerelease-20260415164455 → 0.0.0-chat-prerelease-20260418083610

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 (45) hide show
  1. package/dist/commonjs/v3/chat-react.d.ts +20 -0
  2. package/dist/commonjs/v3/chat-react.js +81 -0
  3. package/dist/commonjs/v3/chat-react.js.map +1 -1
  4. package/dist/commonjs/v3/chat-tab-coordinator.d.ts +65 -0
  5. package/dist/commonjs/v3/chat-tab-coordinator.js +235 -0
  6. package/dist/commonjs/v3/chat-tab-coordinator.js.map +1 -0
  7. package/dist/commonjs/v3/chat-tab-coordinator.test.d.ts +1 -0
  8. package/dist/commonjs/v3/chat-tab-coordinator.test.js +140 -0
  9. package/dist/commonjs/v3/chat-tab-coordinator.test.js.map +1 -0
  10. package/dist/commonjs/v3/chat.d.ts +42 -0
  11. package/dist/commonjs/v3/chat.js +90 -0
  12. package/dist/commonjs/v3/chat.js.map +1 -1
  13. package/dist/commonjs/v3/test/index.d.ts +3 -0
  14. package/dist/commonjs/v3/test/index.js +18 -0
  15. package/dist/commonjs/v3/test/index.js.map +1 -0
  16. package/dist/commonjs/v3/test/mock-chat-agent.d.ts +121 -0
  17. package/dist/commonjs/v3/test/mock-chat-agent.js +233 -0
  18. package/dist/commonjs/v3/test/mock-chat-agent.js.map +1 -0
  19. package/dist/commonjs/v3/test/setup-catalog.d.ts +1 -0
  20. package/dist/commonjs/v3/test/setup-catalog.js +18 -0
  21. package/dist/commonjs/v3/test/setup-catalog.js.map +1 -0
  22. package/dist/commonjs/version.js +1 -1
  23. package/dist/esm/v3/chat-react.d.ts +20 -0
  24. package/dist/esm/v3/chat-react.js +80 -0
  25. package/dist/esm/v3/chat-react.js.map +1 -1
  26. package/dist/esm/v3/chat-tab-coordinator.d.ts +65 -0
  27. package/dist/esm/v3/chat-tab-coordinator.js +231 -0
  28. package/dist/esm/v3/chat-tab-coordinator.js.map +1 -0
  29. package/dist/esm/v3/chat-tab-coordinator.test.d.ts +1 -0
  30. package/dist/esm/v3/chat-tab-coordinator.test.js +138 -0
  31. package/dist/esm/v3/chat-tab-coordinator.test.js.map +1 -0
  32. package/dist/esm/v3/chat.d.ts +42 -0
  33. package/dist/esm/v3/chat.js +90 -0
  34. package/dist/esm/v3/chat.js.map +1 -1
  35. package/dist/esm/v3/test/index.d.ts +3 -0
  36. package/dist/esm/v3/test/index.js +13 -0
  37. package/dist/esm/v3/test/index.js.map +1 -0
  38. package/dist/esm/v3/test/mock-chat-agent.d.ts +121 -0
  39. package/dist/esm/v3/test/mock-chat-agent.js +230 -0
  40. package/dist/esm/v3/test/mock-chat-agent.js.map +1 -0
  41. package/dist/esm/v3/test/setup-catalog.d.ts +1 -0
  42. package/dist/esm/v3/test/setup-catalog.js +16 -0
  43. package/dist/esm/v3/test/setup-catalog.js.map +1 -0
  44. package/dist/esm/version.js +1 -1
  45. package/package.json +18 -2
@@ -46,6 +46,26 @@ export type { InferChatUIMessage };
46
46
  * ```
47
47
  */
48
48
  export declare function useTriggerChatTransport<TTask extends AnyTask = AnyTask>(options: UseTriggerChatTransportOptions<TTask>): TriggerChatTransport;
49
+ /**
50
+ * Sync chat messages across browser tabs.
51
+ *
52
+ * Requires `multiTab: true` on the transport. Handles:
53
+ * - Tracking read-only state (`isReadOnly`) when another tab is active
54
+ * - Broadcasting messages from the active tab to other tabs
55
+ * - Receiving messages from other tabs and updating local state via `setMessages`
56
+ *
57
+ * @example
58
+ * ```tsx
59
+ * const transport = useTriggerChatTransport({ task: "my-chat", multiTab: true, accessToken });
60
+ * const { messages, setMessages } = useChat({ id: chatId, transport });
61
+ * const { isReadOnly } = useMultiTabChat(transport, chatId, messages, setMessages);
62
+ *
63
+ * <input disabled={isReadOnly} placeholder={isReadOnly ? "Active in another tab" : "Type a message..."} />
64
+ * ```
65
+ */
66
+ export declare function useMultiTabChat<T = unknown>(transport: TriggerChatTransport, chatId: string, messages: T[], setMessages: (messages: T[]) => void): {
67
+ isReadOnly: boolean;
68
+ };
49
69
  /** A pending message tracked by `usePendingMessages`. */
50
70
  export type PendingMessage = {
51
71
  id: string;
@@ -2,6 +2,7 @@
2
2
  "use strict";
3
3
  Object.defineProperty(exports, "__esModule", { value: true });
4
4
  exports.useTriggerChatTransport = useTriggerChatTransport;
5
+ exports.useMultiTabChat = useMultiTabChat;
5
6
  exports.usePendingMessages = usePendingMessages;
6
7
  /**
7
8
  * @module @trigger.dev/sdk/chat/react
@@ -74,8 +75,88 @@ function useTriggerChatTransport(options) {
74
75
  (0, react_1.useEffect)(() => {
75
76
  ref.current?.setTriggerTask(triggerTask);
76
77
  }, [triggerTask]);
78
+ // Note: dispose() is NOT called in effect cleanup because React strict mode
79
+ // runs cleanup+re-setup, but the transport lives in a ref and isn't recreated.
80
+ // Calling dispose() would permanently close the BroadcastChannel.
81
+ // The coordinator's beforeunload handler handles tab close cleanup instead.
77
82
  return ref.current;
78
83
  }
84
+ /**
85
+ * Sync chat messages across browser tabs.
86
+ *
87
+ * Requires `multiTab: true` on the transport. Handles:
88
+ * - Tracking read-only state (`isReadOnly`) when another tab is active
89
+ * - Broadcasting messages from the active tab to other tabs
90
+ * - Receiving messages from other tabs and updating local state via `setMessages`
91
+ *
92
+ * @example
93
+ * ```tsx
94
+ * const transport = useTriggerChatTransport({ task: "my-chat", multiTab: true, accessToken });
95
+ * const { messages, setMessages } = useChat({ id: chatId, transport });
96
+ * const { isReadOnly } = useMultiTabChat(transport, chatId, messages, setMessages);
97
+ *
98
+ * <input disabled={isReadOnly} placeholder={isReadOnly ? "Active in another tab" : "Type a message..."} />
99
+ * ```
100
+ */
101
+ function useMultiTabChat(transport, chatId, messages, setMessages) {
102
+ const [isReadOnly, setIsReadOnly] = (0, react_1.useState)(() => transport.isReadOnly(chatId));
103
+ // Track read-only state
104
+ (0, react_1.useEffect)(() => {
105
+ const listener = (id, readOnly) => {
106
+ if (id === chatId)
107
+ setIsReadOnly(readOnly);
108
+ };
109
+ transport.addReadOnlyListener(listener);
110
+ setIsReadOnly(transport.isReadOnly(chatId));
111
+ return () => transport.removeReadOnlyListener(listener);
112
+ }, [transport, chatId]);
113
+ // Active tab: broadcast messages to other tabs on change.
114
+ // Only broadcast when THIS tab holds the claim (is the current sender).
115
+ // Deferred via requestIdleCallback so the structured clone in
116
+ // BroadcastChannel.postMessage never blocks rendering during streaming.
117
+ const idleRef = (0, react_1.useRef)(null);
118
+ const latestMessagesRef = (0, react_1.useRef)(messages);
119
+ latestMessagesRef.current = messages;
120
+ (0, react_1.useEffect)(() => {
121
+ if (!transport.hasClaim(chatId) || messages.length === 0)
122
+ return;
123
+ if (idleRef.current !== null)
124
+ return; // Already scheduled
125
+ const schedule = typeof requestIdleCallback === "function"
126
+ ? requestIdleCallback
127
+ : (fn) => setTimeout(fn, 50);
128
+ idleRef.current = schedule(() => {
129
+ idleRef.current = null;
130
+ if (transport.hasClaim(chatId)) {
131
+ transport.broadcastMessages(chatId, latestMessagesRef.current);
132
+ }
133
+ });
134
+ }, [transport, chatId, messages]);
135
+ // Flush final state when claim is released (turn complete)
136
+ (0, react_1.useEffect)(() => {
137
+ if (!transport.hasClaim(chatId) && latestMessagesRef.current.length > 0) {
138
+ if (idleRef.current !== null) {
139
+ const cancel = typeof cancelIdleCallback === "function"
140
+ ? cancelIdleCallback
141
+ : clearTimeout;
142
+ cancel(idleRef.current);
143
+ idleRef.current = null;
144
+ }
145
+ transport.broadcastMessages(chatId, latestMessagesRef.current);
146
+ }
147
+ }, [transport, chatId, isReadOnly]);
148
+ // Read-only tab: receive messages from the active tab
149
+ (0, react_1.useEffect)(() => {
150
+ const listener = (id, msgs) => {
151
+ if (id === chatId) {
152
+ setMessages(msgs);
153
+ }
154
+ };
155
+ transport.addMessagesListener(listener);
156
+ return () => transport.removeMessagesListener(listener);
157
+ }, [transport, chatId, setMessages]);
158
+ return { isReadOnly };
159
+ }
79
160
  /**
80
161
  * React hook for managing pending messages (steering) during streaming.
81
162
  *
@@ -1 +1 @@
1
- {"version":3,"file":"chat-react.js","sourceRoot":"","sources":["../../../src/v3/chat-react.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"chat-react.js","sourceRoot":"","sources":["../../../src/v3/chat-react.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Coordinates multi-tab access to chat sessions via BroadcastChannel.
3
+ *
4
+ * When multiple browser tabs open the same chat, only one can be the active
5
+ * sender. Others enter read-only mode. The coordinator uses a simple
6
+ * claim/release/heartbeat protocol to track ownership per chatId.
7
+ *
8
+ * Gracefully degrades to a no-op when BroadcastChannel is unavailable
9
+ * (SSR, Node.js, old browsers).
10
+ *
11
+ * @internal
12
+ */
13
+ type ReadOnlyListener = (chatId: string, isReadOnly: boolean) => void;
14
+ type MessagesListener = (chatId: string, messages: unknown[]) => void;
15
+ type SessionListener = (chatId: string, session: {
16
+ lastEventId?: string;
17
+ }) => void;
18
+ export declare class ChatTabCoordinator {
19
+ private tabId;
20
+ private channel;
21
+ /** Claims held by OTHER tabs: chatId -> { tabId, lastSeen } */
22
+ private claims;
23
+ /** chatIds that THIS tab has claimed */
24
+ private myClaims;
25
+ private listeners;
26
+ private messagesListeners;
27
+ private sessionListeners;
28
+ private heartbeatTimer;
29
+ private beforeUnloadHandler;
30
+ constructor();
31
+ /**
32
+ * Attempt to claim a chatId for sending.
33
+ * Returns false if another tab already holds it.
34
+ */
35
+ claim(chatId: string): boolean;
36
+ /** Release a chatId so other tabs can claim it. */
37
+ release(chatId: string): void;
38
+ /** Check if THIS tab currently holds a claim for the chatId. */
39
+ hasClaim(chatId: string): boolean;
40
+ /** Check if another tab holds this chatId. */
41
+ isReadOnly(chatId: string): boolean;
42
+ addListener(fn: ReadOnlyListener): void;
43
+ removeListener(fn: ReadOnlyListener): void;
44
+ /** Broadcast the current messages to other tabs (for real-time sync). */
45
+ broadcastMessages(chatId: string, messages: unknown[]): void;
46
+ addMessagesListener(fn: MessagesListener): void;
47
+ removeMessagesListener(fn: MessagesListener): void;
48
+ /** Broadcast session state (lastEventId) to other tabs. */
49
+ broadcastSession(chatId: string, session: {
50
+ lastEventId?: string;
51
+ }): void;
52
+ addSessionListener(fn: SessionListener): void;
53
+ removeSessionListener(fn: SessionListener): void;
54
+ /** Clean up channel, timers, and event listeners. */
55
+ dispose(): void;
56
+ private handleMessage;
57
+ private sendHeartbeats;
58
+ private expireStaleClaimsFromOtherTabs;
59
+ private releaseAll;
60
+ private broadcast;
61
+ private notify;
62
+ private notifyMessages;
63
+ private notifySession;
64
+ }
65
+ export {};
@@ -0,0 +1,235 @@
1
+ "use strict";
2
+ /**
3
+ * Coordinates multi-tab access to chat sessions via BroadcastChannel.
4
+ *
5
+ * When multiple browser tabs open the same chat, only one can be the active
6
+ * sender. Others enter read-only mode. The coordinator uses a simple
7
+ * claim/release/heartbeat protocol to track ownership per chatId.
8
+ *
9
+ * Gracefully degrades to a no-op when BroadcastChannel is unavailable
10
+ * (SSR, Node.js, old browsers).
11
+ *
12
+ * @internal
13
+ */
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.ChatTabCoordinator = void 0;
16
+ const CHANNEL_NAME = "trigger-chat-tab-coord";
17
+ const HEARTBEAT_INTERVAL_MS = 5_000;
18
+ const HEARTBEAT_TIMEOUT_MS = 10_000;
19
+ class ChatTabCoordinator {
20
+ tabId;
21
+ channel = null;
22
+ /** Claims held by OTHER tabs: chatId -> { tabId, lastSeen } */
23
+ claims = new Map();
24
+ /** chatIds that THIS tab has claimed */
25
+ myClaims = new Set();
26
+ listeners = new Set();
27
+ messagesListeners = new Set();
28
+ sessionListeners = new Set();
29
+ heartbeatTimer = null;
30
+ beforeUnloadHandler = null;
31
+ constructor() {
32
+ this.tabId =
33
+ typeof crypto !== "undefined" && crypto.randomUUID
34
+ ? crypto.randomUUID()
35
+ : `tab-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
36
+ if (typeof BroadcastChannel === "undefined") {
37
+ return; // No-op mode
38
+ }
39
+ this.channel = new BroadcastChannel(CHANNEL_NAME);
40
+ this.channel.onmessage = (event) => {
41
+ this.handleMessage(event.data);
42
+ };
43
+ // Heartbeat: send for our claims + check for stale claims from other tabs
44
+ this.heartbeatTimer = setInterval(() => {
45
+ this.sendHeartbeats();
46
+ this.expireStaleClaimsFromOtherTabs();
47
+ }, HEARTBEAT_INTERVAL_MS);
48
+ // Best-effort release on tab close
49
+ this.beforeUnloadHandler = () => this.releaseAll();
50
+ if (typeof window !== "undefined") {
51
+ window.addEventListener("beforeunload", this.beforeUnloadHandler);
52
+ }
53
+ }
54
+ /**
55
+ * Attempt to claim a chatId for sending.
56
+ * Returns false if another tab already holds it.
57
+ */
58
+ claim(chatId) {
59
+ if (!this.channel)
60
+ return true; // No-op mode
61
+ const existing = this.claims.get(chatId);
62
+ if (existing && existing.tabId !== this.tabId) {
63
+ return false; // Another tab holds this chat
64
+ }
65
+ this.myClaims.add(chatId);
66
+ this.broadcast({ type: "claim", chatId, tabId: this.tabId });
67
+ return true;
68
+ }
69
+ /** Release a chatId so other tabs can claim it. */
70
+ release(chatId) {
71
+ if (!this.channel)
72
+ return;
73
+ if (!this.myClaims.has(chatId))
74
+ return;
75
+ this.myClaims.delete(chatId);
76
+ this.broadcast({ type: "release", chatId, tabId: this.tabId });
77
+ }
78
+ /** Check if THIS tab currently holds a claim for the chatId. */
79
+ hasClaim(chatId) {
80
+ return this.myClaims.has(chatId);
81
+ }
82
+ /** Check if another tab holds this chatId. */
83
+ isReadOnly(chatId) {
84
+ if (!this.channel)
85
+ return false;
86
+ const claim = this.claims.get(chatId);
87
+ return claim != null && claim.tabId !== this.tabId;
88
+ }
89
+ addListener(fn) {
90
+ this.listeners.add(fn);
91
+ }
92
+ removeListener(fn) {
93
+ this.listeners.delete(fn);
94
+ }
95
+ /** Broadcast the current messages to other tabs (for real-time sync). */
96
+ broadcastMessages(chatId, messages) {
97
+ if (!this.channel)
98
+ return;
99
+ this.broadcast({ type: "messages", chatId, tabId: this.tabId, messages });
100
+ }
101
+ addMessagesListener(fn) {
102
+ this.messagesListeners.add(fn);
103
+ }
104
+ removeMessagesListener(fn) {
105
+ this.messagesListeners.delete(fn);
106
+ }
107
+ /** Broadcast session state (lastEventId) to other tabs. */
108
+ broadcastSession(chatId, session) {
109
+ if (!this.channel)
110
+ return;
111
+ this.broadcast({ type: "session", chatId, tabId: this.tabId, session });
112
+ }
113
+ addSessionListener(fn) {
114
+ this.sessionListeners.add(fn);
115
+ }
116
+ removeSessionListener(fn) {
117
+ this.sessionListeners.delete(fn);
118
+ }
119
+ /** Clean up channel, timers, and event listeners. */
120
+ dispose() {
121
+ this.releaseAll();
122
+ if (this.heartbeatTimer) {
123
+ clearInterval(this.heartbeatTimer);
124
+ this.heartbeatTimer = null;
125
+ }
126
+ if (this.beforeUnloadHandler && typeof window !== "undefined") {
127
+ window.removeEventListener("beforeunload", this.beforeUnloadHandler);
128
+ this.beforeUnloadHandler = null;
129
+ }
130
+ if (this.channel) {
131
+ this.channel.close();
132
+ this.channel = null;
133
+ }
134
+ this.listeners.clear();
135
+ this.messagesListeners.clear();
136
+ this.sessionListeners.clear();
137
+ }
138
+ // --- Private ---
139
+ handleMessage(msg) {
140
+ if (msg.tabId === this.tabId)
141
+ return; // Ignore own messages
142
+ switch (msg.type) {
143
+ case "claim": {
144
+ const wasReadOnly = this.isReadOnly(msg.chatId);
145
+ this.claims.set(msg.chatId, { tabId: msg.tabId, lastSeen: Date.now() });
146
+ if (!wasReadOnly) {
147
+ this.notify(msg.chatId, true);
148
+ }
149
+ break;
150
+ }
151
+ case "release": {
152
+ const claim = this.claims.get(msg.chatId);
153
+ if (claim && claim.tabId === msg.tabId) {
154
+ this.claims.delete(msg.chatId);
155
+ this.notify(msg.chatId, false);
156
+ }
157
+ break;
158
+ }
159
+ case "heartbeat": {
160
+ const claim = this.claims.get(msg.chatId);
161
+ if (claim && claim.tabId === msg.tabId) {
162
+ claim.lastSeen = Date.now();
163
+ }
164
+ break;
165
+ }
166
+ case "messages": {
167
+ this.notifyMessages(msg.chatId, msg.messages);
168
+ break;
169
+ }
170
+ case "session": {
171
+ this.notifySession(msg.chatId, msg.session);
172
+ break;
173
+ }
174
+ }
175
+ }
176
+ sendHeartbeats() {
177
+ for (const chatId of this.myClaims) {
178
+ this.broadcast({ type: "heartbeat", chatId, tabId: this.tabId });
179
+ }
180
+ }
181
+ expireStaleClaimsFromOtherTabs() {
182
+ const now = Date.now();
183
+ for (const [chatId, claim] of this.claims) {
184
+ if (claim.tabId !== this.tabId && now - claim.lastSeen > HEARTBEAT_TIMEOUT_MS) {
185
+ this.claims.delete(chatId);
186
+ this.notify(chatId, false);
187
+ }
188
+ }
189
+ }
190
+ releaseAll() {
191
+ for (const chatId of [...this.myClaims]) {
192
+ this.release(chatId);
193
+ }
194
+ }
195
+ broadcast(msg) {
196
+ try {
197
+ this.channel?.postMessage(msg);
198
+ }
199
+ catch {
200
+ // Channel may be closed
201
+ }
202
+ }
203
+ notify(chatId, isReadOnly) {
204
+ for (const fn of this.listeners) {
205
+ try {
206
+ fn(chatId, isReadOnly);
207
+ }
208
+ catch {
209
+ // Non-fatal
210
+ }
211
+ }
212
+ }
213
+ notifyMessages(chatId, messages) {
214
+ for (const fn of this.messagesListeners) {
215
+ try {
216
+ fn(chatId, messages);
217
+ }
218
+ catch {
219
+ // Non-fatal
220
+ }
221
+ }
222
+ }
223
+ notifySession(chatId, session) {
224
+ for (const fn of this.sessionListeners) {
225
+ try {
226
+ fn(chatId, session);
227
+ }
228
+ catch {
229
+ // Non-fatal
230
+ }
231
+ }
232
+ }
233
+ }
234
+ exports.ChatTabCoordinator = ChatTabCoordinator;
235
+ //# sourceMappingURL=chat-tab-coordinator.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-tab-coordinator.js","sourceRoot":"","sources":["../../../src/v3/chat-tab-coordinator.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,140 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const vitest_1 = require("vitest");
4
+ const chat_tab_coordinator_js_1 = require("./chat-tab-coordinator.js");
5
+ // Mock BroadcastChannel for testing
6
+ class MockBroadcastChannel {
7
+ name;
8
+ static instances = [];
9
+ onmessage = null;
10
+ closed = false;
11
+ constructor(name) {
12
+ this.name = name;
13
+ MockBroadcastChannel.instances.push(this);
14
+ }
15
+ postMessage(data) {
16
+ if (this.closed)
17
+ return;
18
+ // Deliver to all OTHER instances on the same channel
19
+ for (const instance of MockBroadcastChannel.instances) {
20
+ if (instance !== this && instance.name === this.name && !instance.closed) {
21
+ instance.onmessage?.({ data });
22
+ }
23
+ }
24
+ }
25
+ close() {
26
+ this.closed = true;
27
+ MockBroadcastChannel.instances = MockBroadcastChannel.instances.filter((i) => i !== this);
28
+ }
29
+ }
30
+ (0, vitest_1.describe)("ChatTabCoordinator", () => {
31
+ (0, vitest_1.beforeEach)(() => {
32
+ MockBroadcastChannel.instances = [];
33
+ vitest_1.vi.stubGlobal("BroadcastChannel", MockBroadcastChannel);
34
+ vitest_1.vi.useFakeTimers();
35
+ });
36
+ (0, vitest_1.afterEach)(() => {
37
+ vitest_1.vi.restoreAllMocks();
38
+ vitest_1.vi.useRealTimers();
39
+ });
40
+ (0, vitest_1.it)("tab A claims, tab B sees isReadOnly", () => {
41
+ const a = new chat_tab_coordinator_js_1.ChatTabCoordinator();
42
+ const b = new chat_tab_coordinator_js_1.ChatTabCoordinator();
43
+ (0, vitest_1.expect)(b.isReadOnly("chat-1")).toBe(false);
44
+ a.claim("chat-1");
45
+ (0, vitest_1.expect)(b.isReadOnly("chat-1")).toBe(true);
46
+ (0, vitest_1.expect)(a.isReadOnly("chat-1")).toBe(false); // Owner is not read-only
47
+ a.dispose();
48
+ b.dispose();
49
+ });
50
+ (0, vitest_1.it)("tab A releases, tab B sees isReadOnly = false", () => {
51
+ const a = new chat_tab_coordinator_js_1.ChatTabCoordinator();
52
+ const b = new chat_tab_coordinator_js_1.ChatTabCoordinator();
53
+ a.claim("chat-1");
54
+ (0, vitest_1.expect)(b.isReadOnly("chat-1")).toBe(true);
55
+ a.release("chat-1");
56
+ (0, vitest_1.expect)(b.isReadOnly("chat-1")).toBe(false);
57
+ a.dispose();
58
+ b.dispose();
59
+ });
60
+ (0, vitest_1.it)("fires listener on claim and release", () => {
61
+ const a = new chat_tab_coordinator_js_1.ChatTabCoordinator();
62
+ const b = new chat_tab_coordinator_js_1.ChatTabCoordinator();
63
+ const listener = vitest_1.vi.fn();
64
+ b.addListener(listener);
65
+ a.claim("chat-1");
66
+ (0, vitest_1.expect)(listener).toHaveBeenCalledWith("chat-1", true);
67
+ a.release("chat-1");
68
+ (0, vitest_1.expect)(listener).toHaveBeenCalledWith("chat-1", false);
69
+ a.dispose();
70
+ b.dispose();
71
+ });
72
+ (0, vitest_1.it)("removeListener stops notifications", () => {
73
+ const a = new chat_tab_coordinator_js_1.ChatTabCoordinator();
74
+ const b = new chat_tab_coordinator_js_1.ChatTabCoordinator();
75
+ const listener = vitest_1.vi.fn();
76
+ b.addListener(listener);
77
+ b.removeListener(listener);
78
+ a.claim("chat-1");
79
+ (0, vitest_1.expect)(listener).not.toHaveBeenCalled();
80
+ a.dispose();
81
+ b.dispose();
82
+ });
83
+ (0, vitest_1.it)("claim returns false when another tab holds the chatId", () => {
84
+ const a = new chat_tab_coordinator_js_1.ChatTabCoordinator();
85
+ const b = new chat_tab_coordinator_js_1.ChatTabCoordinator();
86
+ (0, vitest_1.expect)(a.claim("chat-1")).toBe(true);
87
+ (0, vitest_1.expect)(b.claim("chat-1")).toBe(false);
88
+ a.dispose();
89
+ b.dispose();
90
+ });
91
+ (0, vitest_1.it)("supports multiple independent chatIds", () => {
92
+ const a = new chat_tab_coordinator_js_1.ChatTabCoordinator();
93
+ const b = new chat_tab_coordinator_js_1.ChatTabCoordinator();
94
+ a.claim("chat-1");
95
+ b.claim("chat-2");
96
+ (0, vitest_1.expect)(a.isReadOnly("chat-1")).toBe(false);
97
+ (0, vitest_1.expect)(a.isReadOnly("chat-2")).toBe(true);
98
+ (0, vitest_1.expect)(b.isReadOnly("chat-1")).toBe(true);
99
+ (0, vitest_1.expect)(b.isReadOnly("chat-2")).toBe(false);
100
+ a.dispose();
101
+ b.dispose();
102
+ });
103
+ (0, vitest_1.it)("heartbeat timeout clears stale claim from crashed tab", () => {
104
+ const a = new chat_tab_coordinator_js_1.ChatTabCoordinator();
105
+ const b = new chat_tab_coordinator_js_1.ChatTabCoordinator();
106
+ const listener = vitest_1.vi.fn();
107
+ b.addListener(listener);
108
+ a.claim("chat-1");
109
+ (0, vitest_1.expect)(b.isReadOnly("chat-1")).toBe(true);
110
+ // Simulate tab A crashing (close its channel, stop heartbeats)
111
+ a.dispose();
112
+ // Advance past heartbeat timeout (10s)
113
+ vitest_1.vi.advanceTimersByTime(11_000);
114
+ (0, vitest_1.expect)(b.isReadOnly("chat-1")).toBe(false);
115
+ (0, vitest_1.expect)(listener).toHaveBeenCalledWith("chat-1", false);
116
+ b.dispose();
117
+ });
118
+ (0, vitest_1.it)("dispose releases all claims", () => {
119
+ const a = new chat_tab_coordinator_js_1.ChatTabCoordinator();
120
+ const b = new chat_tab_coordinator_js_1.ChatTabCoordinator();
121
+ a.claim("chat-1");
122
+ a.claim("chat-2");
123
+ (0, vitest_1.expect)(b.isReadOnly("chat-1")).toBe(true);
124
+ (0, vitest_1.expect)(b.isReadOnly("chat-2")).toBe(true);
125
+ a.dispose();
126
+ (0, vitest_1.expect)(b.isReadOnly("chat-1")).toBe(false);
127
+ (0, vitest_1.expect)(b.isReadOnly("chat-2")).toBe(false);
128
+ b.dispose();
129
+ });
130
+ (0, vitest_1.it)("gracefully degrades when BroadcastChannel is unavailable", () => {
131
+ vitest_1.vi.stubGlobal("BroadcastChannel", undefined);
132
+ const coord = new chat_tab_coordinator_js_1.ChatTabCoordinator();
133
+ // All operations are no-ops
134
+ (0, vitest_1.expect)(coord.claim("chat-1")).toBe(true);
135
+ (0, vitest_1.expect)(coord.isReadOnly("chat-1")).toBe(false);
136
+ coord.release("chat-1"); // No error
137
+ coord.dispose(); // No error
138
+ });
139
+ });
140
+ //# sourceMappingURL=chat-tab-coordinator.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"chat-tab-coordinator.test.js","sourceRoot":"","sources":["../../../src/v3/chat-tab-coordinator.test.ts"],"names":[],"mappings":"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"}