diff --git a/packages/ui/src/components/chat/__tests__/issue-2903-subagent-status-line-only.test.tsx b/packages/ui/src/components/chat/__tests__/issue-2903-subagent-status-line-only.test.tsx index cd72ed46..54ed84d2 100644 --- a/packages/ui/src/components/chat/__tests__/issue-2903-subagent-status-line-only.test.tsx +++ b/packages/ui/src/components/chat/__tests__/issue-2903-subagent-status-line-only.test.tsx @@ -226,20 +226,23 @@ describe('issue #2903 busy embedded subagent status-line-only', () => { expect(chatContainerSource).toContain('void ensureSessionRenderable(currentSessionId);'); }); - test('empty+busy branch skips empty state so StatusRowContainer can stand alone', () => { + test('the empty and idle branch leaves the status row to the busy path', () => { + // A busy session with no messages yet must fall through to the viewport so + // StatusRowContainer is the only thing on screen. The idle branch returns + // before it and must not render one of its own. The empty state itself no + // longer lives here: the draft surface owns it since the draft transition + // animation landed. expect(chatContainerSource).toContain('if (sessionMessages.length === 0 && !sessionIsWorking)'); - expect(chatContainerSource).toContain(''); - const emptyBusyGuard = 'if (sessionMessages.length === 0 && !sessionIsWorking)'; - const emptyStateReturn = chatContainerSource.indexOf(emptyBusyGuard); - expect(emptyStateReturn).toBeGreaterThan(-1); - const emptyStateBlock = chatContainerSource.slice( - emptyStateReturn, - emptyStateReturn + 1600, + const emptyIdleGuard = 'if (sessionMessages.length === 0 && !sessionIsWorking)'; + const emptyIdleReturn = chatContainerSource.indexOf(emptyIdleGuard); + expect(emptyIdleReturn).toBeGreaterThan(-1); + const emptyIdleBlock = chatContainerSource.slice( + emptyIdleReturn, + emptyIdleReturn + 1600, ); - expect(emptyStateBlock).toContain(''); + expect(emptyIdleBlock).not.toContain(''); }); test('visibility handshake remains as defense-in-depth for background work', () => { diff --git a/packages/ui/src/sync/__tests__/issue-2039.test.ts b/packages/ui/src/sync/__tests__/issue-2039.test.ts index 70a3048e..572d2094 100644 --- a/packages/ui/src/sync/__tests__/issue-2039.test.ts +++ b/packages/ui/src/sync/__tests__/issue-2039.test.ts @@ -4,6 +4,9 @@ import { togglePermissionAutoAccept } from "../../components/chat/permissionAuto const storage = new Map() const createSessionCalls: Array<{ title?: string; directory: string | null; parentID: string | null; metadata?: unknown }> = [] const permissionAutoAcceptCalls: Array<[string, boolean]> = [] +// Sync's session→directory index. `createSession` writes it, and directory +// resolution reads it as the authoritative source, so the mock has to keep one. +const sessionDirectoryRegistry = new Map() let createdSessionDirectory: string | undefined const getMockCalls = (fn: unknown): unknown[][] => ((fn as { mock?: { calls: unknown[][] } }).mock?.calls ?? []) @@ -241,13 +244,34 @@ mock.module("../sync-refs", () => ({ getSyncMessages: () => [], getSyncParts: () => [], getAllSyncSessions: () => [], - getSyncSessionDirectory: () => null, + getSyncSessionDirectory: (sessionId: string) => sessionDirectoryRegistry.get(sessionId) ?? null, + registerSessionDirectory: (sessionId: string, directory: string) => { + sessionDirectoryRegistry.set(sessionId, directory) + }, })) mock.module("../session-actions", () => ({ - createSession: mock(async (title: string | undefined, directory: string | null, parentID: string | null, metadata?: unknown) => { + // Mirrors the real action's authoritative steps: the created session becomes + // current under the directory the server confirmed, and that directory enters + // the routing index. Everything these tests assert about routing depends on + // those two, so a mock without them tests nothing. + createSession: mock(async ( + title: string | undefined, + directory: string | null, + parentID: string | null, + metadata?: unknown, + selectionTransition?: "submitted-draft", + ) => { createSessionCalls.push({ title, directory, parentID, metadata }) - return { id: "ses_issue_2039", directory: createdSessionDirectory ?? directory } + const session = { id: "ses_issue_2039", directory: createdSessionDirectory ?? directory } + const sessionDirectory = session.directory ?? null + if (sessionDirectory) { + sessionDirectoryRegistry.set(session.id, sessionDirectory) + } + const { useSessionUIStore: store } = await import("../session-ui-store") + store.getState().setCurrentSession(session.id, sessionDirectory, selectionTransition) + store.getState().markSessionAsOpenChamberCreated(session.id) + return session }), deleteSession: mock(async () => true), deleteSessions: mock(async () => ({ deletedIds: [], failedIds: [] })), @@ -322,6 +346,7 @@ describe("issue 2039 draft auto-accept", () => { beforeEach(() => { storage.clear() createSessionCalls.length = 0 + sessionDirectoryRegistry.clear() permissionAutoAcceptCalls.length = 0 createdSessionDirectory = undefined