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:
@@ -3,6 +3,7 @@ import { useStoreWithEqualityFn } from "zustand/traditional";
|
||||
import { useSidebarOrderStore } from "@/stores/sidebar-order-store";
|
||||
import {
|
||||
composeWorkspaceStructure,
|
||||
createWorkspaceStructureProjectsSelector,
|
||||
selectHasHydratedWorkspaces,
|
||||
selectHydratedWorkspaceServerIds,
|
||||
selectWorkspaceDirectoryServerIds,
|
||||
@@ -16,7 +17,6 @@ import {
|
||||
selectWorkspaceKeys,
|
||||
selectWorkspaceOrderByScope,
|
||||
selectWorkspaceStatusesForBadges,
|
||||
selectWorkspaceStructureProjects,
|
||||
workspaceEqualityFns,
|
||||
type WorkspaceStructure,
|
||||
} from "./selectors";
|
||||
@@ -100,9 +100,13 @@ export function useWorkspaceDirectory(
|
||||
}
|
||||
|
||||
export function useWorkspaceStructure(serverIds: string[]): WorkspaceStructure {
|
||||
const selectProjects = useMemo(
|
||||
() => createWorkspaceStructureProjectsSelector(serverIds),
|
||||
[serverIds],
|
||||
);
|
||||
const projects = useStoreWithEqualityFn(
|
||||
useSessionStore,
|
||||
(state) => selectWorkspaceStructureProjects(state, serverIds),
|
||||
selectProjects,
|
||||
workspaceEqualityFns.deep,
|
||||
);
|
||||
const projectOrder = useStoreWithEqualityFn(
|
||||
|
||||
@@ -4,6 +4,7 @@ import { createProjectViewKey } from "@/projects/workspace-structure";
|
||||
import type { DaemonClient } from "@getpaseo/client/internal/daemon-client";
|
||||
import {
|
||||
composeWorkspaceStructure,
|
||||
createWorkspaceStructureProjectsSelector,
|
||||
selectHasWorkspaces,
|
||||
selectHydratedWorkspaceServerIds,
|
||||
selectWorkspaceDirectoryServerIds,
|
||||
@@ -338,6 +339,27 @@ describe("selectWorkspaceFields", () => {
|
||||
});
|
||||
|
||||
describe("workspace structure composition", () => {
|
||||
it("reuses structure when unrelated session state changes", () => {
|
||||
const workspace = createWorkspace({ id: "workspace-a" });
|
||||
const workspaces = new Map([[workspace.id, workspace]]);
|
||||
const projects = new Map([
|
||||
[workspace.projectId, projectDescriptorFromTestWorkspace(workspace)],
|
||||
]);
|
||||
const selectProjects = createWorkspaceStructureProjectsSelector([SERVER_ID]);
|
||||
const before = selectProjects({ sessions: { [SERVER_ID]: { workspaces, projects } } });
|
||||
const after = selectProjects({
|
||||
sessions: {
|
||||
[SERVER_ID]: {
|
||||
workspaces,
|
||||
projects,
|
||||
hasHydratedWorkspaces: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
expect(after).toBe(before);
|
||||
});
|
||||
|
||||
function snapshotStructure(
|
||||
serverId: string,
|
||||
sidebar: SidebarOrderSnapshot,
|
||||
|
||||
@@ -182,6 +182,38 @@ export function selectWorkspaceStructureProjects(
|
||||
return buildWorkspaceStructureProjects({ sessions });
|
||||
}
|
||||
|
||||
export function createWorkspaceStructureProjectsSelector(
|
||||
serverIds: readonly string[],
|
||||
): (state: SessionsSnapshot) => WorkspaceStructureProject[] {
|
||||
let previousInputs: Array<{
|
||||
workspaces: Map<string, WorkspaceDescriptor> | undefined;
|
||||
projects: Map<string, ProjectDescriptor> | undefined;
|
||||
}> | null = null;
|
||||
let previousProjects: WorkspaceStructureProject[] | null = null;
|
||||
|
||||
return (state) => {
|
||||
const inputs = serverIds.map((serverId) => ({
|
||||
workspaces: state.sessions[serverId]?.workspaces,
|
||||
projects: state.sessions[serverId]?.projects,
|
||||
}));
|
||||
const priorInputs = previousInputs;
|
||||
const unchanged =
|
||||
priorInputs !== null &&
|
||||
inputs.every(
|
||||
(input, index) =>
|
||||
input.workspaces === priorInputs[index]?.workspaces &&
|
||||
input.projects === priorInputs[index]?.projects,
|
||||
);
|
||||
if (unchanged && previousProjects) {
|
||||
return previousProjects;
|
||||
}
|
||||
|
||||
previousInputs = inputs;
|
||||
previousProjects = selectWorkspaceStructureProjects(state, serverIds);
|
||||
return previousProjects;
|
||||
};
|
||||
}
|
||||
|
||||
export function selectProject(
|
||||
state: SessionsSnapshot,
|
||||
serverId: string | null,
|
||||
|
||||
Reference in New Issue
Block a user