perf(app): keep warm workspace switching responsive (#3447)

* perf(app): reduce warm workspace switch work

Retained workspaces avoided mounts but pane activity still rerendered the full composer, message input, and agent-control presentation. Keep the activity contract intact while moving focus-sensitive behavior behind small context consumers, and add a Playwright benchmark that measures rapid retained workspace switching on one browser clock.

* perf(app): accelerate retained workspace switching

Avoid repeated retained-stream layout reconciliation and unrelated workspace structure recomputation. Use the lightweight route transition while the host is already focused, while preserving targeted root POP_TO for returns from app-wide routes where it prevents duplicate hidden decks.

* perf(app): defer composer focus during workspace switches

* perf(app): retain recently used workspaces longer

Keep up to ten workspace screens warm so normal multi-workspace navigation does not repeatedly rebuild recently used timelines. Inactive screens expire after ten minutes, while the active screen remains protected and capacity eviction stays least-recently-used.

* fix(app): reconcile retained timeline geometry

* fix(app): refresh retained reading position
This commit is contained in:
Mohamed Boudra
2026-08-16 12:40:28 +02:00
committed by GitHub
parent d60d92e909
commit d4435bd016
20 changed files with 1297 additions and 228 deletions
@@ -27,6 +27,7 @@ import { seedWorkspace } from "../support/helpers/seed-client";
import { waitForWorkspaceTabsVisible } from "../support/helpers/workspace-tabs";
import { getServerId } from "../support/helpers/server-id";
import { buildHostWorkspaceRoute } from "@/utils/host-routes";
import { WORKSPACE_DECK_MAX_MOUNTED_WORKSPACES } from "@/screens/workspace/workspace-deck-retention";
import { delayBrowserAgentCreatedStatus } from "../support/helpers/new-workspace";
import { installDaemonWebSocketGate } from "../support/helpers/daemon-websocket-gate";
import { selectModel } from "../support/helpers/app";
@@ -354,7 +355,7 @@ async function expectHiddenStreamingSubmissionOrderAfterWorkspaceEviction(
model: "ten-second-stream",
});
const evictionAgents = await Promise.all(
Array.from({ length: 3 }, (_unused, index) =>
Array.from({ length: WORKSPACE_DECK_MAX_MOUNTED_WORKSPACES }, (_unused, index) =>
seedMockAgentWorkspace({
repoPrefix: `submission-workspace-eviction-${testInfo.workerIndex}-${index}-`,
title: `Workspace eviction ${index + 1}`,
@@ -379,7 +380,9 @@ async function expectHiddenStreamingSubmissionOrderAfterWorkspaceEviction(
await expectComposerVisible(page);
}
await expect(targetDeckEntry).toHaveCount(0);
await subscriptions.waitForSubscribedAgents([evictionAgents[2]!.agentId]);
await subscriptions.waitForSubscribedAgents([
evictionAgents[WORKSPACE_DECK_MAX_MOUNTED_WORKSPACES - 1]!.agentId,
]);
gate.setAgentStreamSuppressed(false);
await target.client.waitForFinish(target.agentId, 30_000);