diff --git a/packages/app/e2e/workspace-agent-title-handoff.spec.ts b/packages/app/e2e/workspace-agent-title-handoff.spec.ts index f4006b134..fa9e922ba 100644 --- a/packages/app/e2e/workspace-agent-title-handoff.spec.ts +++ b/packages/app/e2e/workspace-agent-title-handoff.spec.ts @@ -1,5 +1,6 @@ import { test, expect } from "./fixtures"; import { expectComposerVisible, submitMessage } from "./helpers/composer"; +import { delayCreatedAgentInitialTailResponse } from "./helpers/agent-timeline-gate"; import { delayBrowserAgentCreatedStatus } from "./helpers/new-workspace"; import { seedWorkspace, type SeedDaemonClient } from "./helpers/seed-client"; import { waitForWorkspaceTabsVisible } from "./helpers/workspace-tabs"; @@ -43,6 +44,49 @@ async function fetchActiveAgentTitle( } test.describe("Workspace agent title handoff", () => { + test("does not cover the agent pane while the optimistic create becomes authoritative", async ({ + page, + }) => { + test.setTimeout(120_000); + await page.setViewportSize({ width: 1440, height: 900 }); + + const timelineGate = await delayCreatedAgentInitialTailResponse(page); + const workspace = await seedWorkspace({ repoPrefix: "workspace-create-handoff-flash-" }); + + try { + await page.goto(buildHostWorkspaceRoute(getServerId(), workspace.workspaceId)); + await waitForWorkspaceTabsVisible(page); + await page.getByTestId("workspace-new-agent-tab-inline").click(); + await expectComposerVisible(page); + + const prompt = "Keep the optimistic agent pane visible during handoff"; + await submitMessage(page, prompt); + const agentId = await timelineGate.waitForCreatedAgent(); + await timelineGate.waitForDelayedResponse(); + + await expect(page.getByTestId(`workspace-tab-agent_${agentId}`).first()).toBeVisible({ + timeout: 15_000, + }); + await expect(page.getByText(prompt, { exact: true }).first()).toBeVisible(); + await expect(page.getByTestId("agent-history-overlay")).toHaveCount(0); + + const overlayAppeared = page + .getByTestId("agent-history-overlay") + .waitFor({ state: "attached", timeout: 2_000 }) + .then( + () => true, + () => false, + ); + timelineGate.release(); + await timelineGate.waitForForwardedResponse(); + + expect(await overlayAppeared).toBe(false); + } finally { + timelineGate.release(); + await workspace.cleanup(); + } + }); + test("shows the prompt tab title and replaces it when the daemon title updates", async ({ page, }) => { diff --git a/packages/app/src/hooks/use-agent-screen-state-machine.test.ts b/packages/app/src/hooks/use-agent-screen-state-machine.test.ts index 9b9b3e4f2..5112cce4e 100644 --- a/packages/app/src/hooks/use-agent-screen-state-machine.test.ts +++ b/packages/app/src/hooks/use-agent-screen-state-machine.test.ts @@ -209,7 +209,7 @@ describe("deriveAgentScreenViewState", () => { expect(sync.ui).toBe("silent"); }); - it("covers already-hydrated history while a newly visible agent catches up", () => { + it("keeps already-hydrated history visible while a newly visible agent catches up", () => { const memory = createBaseMemory({ hasRenderedReady: true, lastReadyAgent: createAgent("agent-1"), @@ -225,7 +225,7 @@ describe("deriveAgentScreenViewState", () => { const ready = expectReadyState(result.state); const sync = expectCatchingUpSync(ready); - expect(sync.ui).toBe("overlay"); + expect(sync.ui).toBe("silent"); }); it("keeps hydrated history readable after a visibility catch-up error", () => { diff --git a/packages/app/src/hooks/use-agent-screen-state-machine.ts b/packages/app/src/hooks/use-agent-screen-state-machine.ts index daf8562f0..b87189b39 100644 --- a/packages/app/src/hooks/use-agent-screen-state-machine.ts +++ b/packages/app/src/hooks/use-agent-screen-state-machine.ts @@ -150,13 +150,11 @@ function resolveCatchingUpUi(args: { hasOptimisticCreateContinuity: boolean; isVisibilityCatchUpPending: boolean; hasHydratedHistoryBefore: boolean; - needsAuthoritativeSync: boolean; hadInitialSyncFailure: boolean; }): "overlay" | "silent" { if (args.hasOptimisticCreateContinuity) return "silent"; - if (args.hasHydratedHistoryBefore && args.needsAuthoritativeSync) return "silent"; - if (args.isVisibilityCatchUpPending) return "overlay"; if (args.hasHydratedHistoryBefore) return "silent"; + if (args.isVisibilityCatchUpPending) return "overlay"; if (args.hadInitialSyncFailure) return "silent"; return "overlay"; } @@ -186,7 +184,6 @@ function resolveAgentScreenSync(args: { hasOptimisticCreateContinuity: hasOptimisticCreateContinuity(input), isVisibilityCatchUpPending: input.visibilityCatchUpStatus === "pending", hasHydratedHistoryBefore: input.hasHydratedHistoryBefore, - needsAuthoritativeSync: input.needsAuthoritativeSync, hadInitialSyncFailure, }), };