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
@@ -64,7 +64,11 @@ import { readMeasuredWidth } from "@/hooks/use-container-width";
import { useToast } from "@/contexts/toast-context";
import { toErrorMessage } from "@/utils/error-messages";
import { showProviderNoticeToast } from "@/utils/provider-notice-toast";
import { useAgentControlCommandCenterActions } from "@/command-center/agent-control-registration";
import {
useAgentControlCommandCenterActions,
type AgentControlCommandCenterSource,
} from "@/command-center/agent-control-registration";
import { useComposerKeyboardScope } from "@/composer/keyboard-scope";
import { isNative } from "@/constants/platform";
import {
resolveComposerControlDensity,
@@ -154,11 +158,28 @@ export interface DraftAgentControlsProps {
interface AgentControlsProps {
agentId: string;
serverId: string;
isPaneFocused: boolean;
onDropdownClose?: () => void;
isCompactLayout?: boolean;
}
function AgentControlCommandCenterRegistration({
sourceId,
enabled,
controls,
}: {
sourceId: string;
enabled: boolean;
controls: AgentControlCommandCenterSource;
}) {
const { isActiveComposer } = useComposerKeyboardScope();
useAgentControlCommandCenterActions({
sourceId,
enabled: enabled && isActiveComposer,
controls,
});
return null;
}
function findOptionLabel(
options: AgentControlOption[] | undefined,
selectedId: string | undefined,
@@ -1477,7 +1498,6 @@ function ThinkingComboboxOption({
export const AgentControls = memo(function AgentControls({
agentId,
serverId,
isPaneFocused,
onDropdownClose,
isCompactLayout,
}: AgentControlsProps) {
@@ -1649,10 +1669,8 @@ export const AgentControls = memo(function AgentControls({
[agentId, agentProvider, client, toast, updatePreferences],
);
useAgentControlCommandCenterActions({
sourceId: `agent:${serverId}:${agentId}`,
enabled: isPaneFocused && Boolean(client),
controls: {
const commandCenterControls = useMemo<AgentControlCommandCenterSource>(
() => ({
serverId,
ownerKey: agentId,
provider: agentProvider,
@@ -1673,8 +1691,31 @@ export const AgentControls = memo(function AgentControls({
list: agent?.features,
set: handleSetFeature,
},
},
});
}),
[
activeModelId,
agent?.features,
agentId,
agentModelSelectorProviders,
agentProvider,
commandCenterModes,
handleSelectCommandCenterModel,
handleSelectThinkingOption,
handleSetFeature,
modeProviderDefinitions,
modelSelection.selectedThinkingId,
modelSelection.thinkingOptions,
serverId,
],
);
const commandCenterRegistration = (
<AgentControlCommandCenterRegistration
sourceId={`agent:${serverId}:${agentId}`}
enabled={Boolean(client)}
controls={commandCenterControls}
/>
);
const handleModelSelectorOpen = useCallback(() => {
refetchSnapshotIfStale(agentProvider);
@@ -1692,30 +1733,33 @@ export const AgentControls = memo(function AgentControls({
}
return (
<ControlledAgentControls
provider={agent.provider}
modelSelectorProviders={agentModelSelectorProviders}
modelOptions={modelOptions}
selectedModelId={modelSelection.activeModelId ?? undefined}
onSelectModel={handleSelectModel}
agentProfiles={agentProfiles}
onApplyAgentProfile={agentProfiles?.applyProfile}
onEditAgentProfiles={handleEditAgentProfiles}
thinkingOptions={thinkingOptions.length > 1 ? thinkingOptions : undefined}
selectedThinkingOptionId={modelSelection.selectedThinkingId ?? undefined}
onSelectThinkingOption={handleSelectThinkingOption}
features={agent.features}
onSetFeature={handleSetFeature}
isModelLoading={snapshotIsLoading || selectedProviderIsLoading}
onModelSelectorOpen={handleModelSelectorOpen}
onRetryModelProvider={handleRetryModelProvider}
isRetryingModelProvider={snapshotIsRefreshing}
onDropdownClose={onDropdownClose}
disabled={!client}
modeControl={modeControl}
modelSelectorServerId={serverId}
isCompactLayout={isCompactLayout}
/>
<>
{commandCenterRegistration}
<ControlledAgentControls
provider={agent.provider}
modelSelectorProviders={agentModelSelectorProviders}
modelOptions={modelOptions}
selectedModelId={modelSelection.activeModelId ?? undefined}
onSelectModel={handleSelectModel}
agentProfiles={agentProfiles}
onApplyAgentProfile={agentProfiles?.applyProfile}
onEditAgentProfiles={handleEditAgentProfiles}
thinkingOptions={thinkingOptions.length > 1 ? thinkingOptions : undefined}
selectedThinkingOptionId={modelSelection.selectedThinkingId ?? undefined}
onSelectThinkingOption={handleSelectThinkingOption}
features={agent.features}
onSetFeature={handleSetFeature}
isModelLoading={snapshotIsLoading || selectedProviderIsLoading}
onModelSelectorOpen={handleModelSelectorOpen}
onRetryModelProvider={handleRetryModelProvider}
isRetryingModelProvider={snapshotIsRefreshing}
onDropdownClose={onDropdownClose}
disabled={!client}
modeControl={modeControl}
modelSelectorServerId={serverId}
isCompactLayout={isCompactLayout}
/>
</>
);
});