diff --git a/apps/ade-cli/src/tuiClient/__tests__/multiChatLayout.test.ts b/apps/ade-cli/src/tuiClient/__tests__/multiChatLayout.test.ts index 18be57639..132d66600 100644 --- a/apps/ade-cli/src/tuiClient/__tests__/multiChatLayout.test.ts +++ b/apps/ade-cli/src/tuiClient/__tests__/multiChatLayout.test.ts @@ -1,5 +1,44 @@ import { describe, expect, it } from "vitest"; -import { canRenderMultiChatGrid, computeTileRects, focusedSessionIdForMultiView } from "../multiChatLayout"; +import { + canRenderMultiChatGrid, + computeTileRects, + focusedSessionIdForMultiView, + resolveOpenChatSessionIds, +} from "../multiChatLayout"; + +describe("resolveOpenChatSessionIds", () => { + const multiView = { + tiles: [ + { sessionId: "tile-a", laneId: "lane-1" }, + { sessionId: "tile-b", laneId: "lane-1" }, + ], + focusedIndex: 0, + }; + + it("streams tile sessions when the grid is visible", () => { + expect(resolveOpenChatSessionIds({ + gridViewActive: true, + multiView, + activeSessionId: "other-chat", + })).toEqual(new Set(["tile-a", "tile-b"])); + }); + + it("streams only the active chat when a resumable grid is hidden", () => { + expect(resolveOpenChatSessionIds({ + gridViewActive: false, + multiView, + activeSessionId: "solo-chat", + })).toEqual(new Set(["solo-chat"])); + }); + + it("streams the active chat when no grid exists", () => { + expect(resolveOpenChatSessionIds({ + gridViewActive: false, + multiView: null, + activeSessionId: "solo-chat", + })).toEqual(new Set(["solo-chat"])); + }); +}); describe("multi chat layout", () => { it("computes the locked 1-6 tile patterns inside the available area", () => { diff --git a/apps/ade-cli/src/tuiClient/app.tsx b/apps/ade-cli/src/tuiClient/app.tsx index 9efb90e99..d0a4b3b66 100644 --- a/apps/ade-cli/src/tuiClient/app.tsx +++ b/apps/ade-cli/src/tuiClient/app.tsx @@ -183,6 +183,7 @@ import { } from "./hitTestRegistry"; import { focusedSessionIdForMultiView, + resolveOpenChatSessionIds, type MultiViewState, type MultiViewTile, } from "./multiChatLayout"; @@ -5870,12 +5871,11 @@ export function AdeCodeApp({ project, forceEmbedded, requireSocket, socketPath } useEffect(() => { if (!connection) return; const unsubscribe = connection.onChatEvent((envelope) => { - const currentMultiView = multiViewRef.current; - const openSessionIds = new Set( - currentMultiView - ? currentMultiView.tiles.map((tile) => tile.sessionId) - : [activeSessionIdRef.current].filter((value): value is string => Boolean(value)), - ); + const openSessionIds = resolveOpenChatSessionIds({ + gridViewActive: gridViewActiveRef.current, + multiView: multiViewRef.current, + activeSessionId: activeSessionIdRef.current, + }); if (!openSessionIds.has(envelope.sessionId)) { // Event for a session we're not displaying — refresh summaries (cheap, // dedup-guarded). Only the open-session token stream below is coalesced. diff --git a/apps/ade-cli/src/tuiClient/multiChatLayout.ts b/apps/ade-cli/src/tuiClient/multiChatLayout.ts index bbc775db8..9762120af 100644 --- a/apps/ade-cli/src/tuiClient/multiChatLayout.ts +++ b/apps/ade-cli/src/tuiClient/multiChatLayout.ts @@ -88,3 +88,15 @@ export function focusedSessionIdForMultiView(multiView: MultiViewState | null): if (!multiView) return null; return multiView.tiles[multiView.focusedIndex]?.sessionId ?? null; } + +/** Session ids that should receive live chat event streaming (not summary-only refresh). */ +export function resolveOpenChatSessionIds(args: { + gridViewActive: boolean; + multiView: MultiViewState | null; + activeSessionId: string | null; +}): Set { + if (args.gridViewActive && args.multiView) { + return new Set(args.multiView.tiles.map((tile) => tile.sessionId)); + } + return args.activeSessionId ? new Set([args.activeSessionId]) : new Set(); +}