refactor(app): reshape session store reads around narrow hook surface

Eliminate sidebar re-render cascades by fixing both sides of the
subscription contract:

- Session-store writes preserve identity: setWorkspaces, mergeWorkspaces,
  removeWorkspace, and patchWorkspaceScripts return the previous Map/entry
  reference when content is unchanged.
- HostRuntimeController.updateSnapshot only notifies when a patched field
  actually changed; idle probe ticks no longer replace the snapshot.
- New canonical hook surface in stores/session-store-hooks.ts
  (useWorkspace, useWorkspaceFields, useWorkspaceStructure,
  useWorkspaceKeys, useResolveWorkspaceIdByCwd,
  useWorkspaceStatusesForBadges, useWorkspaceExecutionAuthority,
  useRecommendedProjectPaths, useHasWorkspaces). These are the only
  place to subscribe to workspaces.
- 15 greedy subscribers migrated to the hook surface. The sidebar is now
  driven by useWorkspaceStructure for identity/ordering; each row
  hydrates its own descriptor via useWorkspaceFields, so a workspace
  update re-renders only its row. Archive-redirect callbacks switched to
  event-time useSessionStore.getState().
- Deleted dead code: use-session-directory.ts, workspace-fetch-debug.ts,
  and the unused SidebarProjectEntry aggregates (activeCount,
  totalWorkspaces, aggregated statusBucket).

No user-visible behavior change. Targeted tests green (79 tests across
host-runtime, session-store, session-store-hooks, sidebar list, sidebar
row model, sidebar shortcuts, workspace source of truth).
This commit is contained in:
Mohamed Boudra
2026-04-19 20:19:45 +07:00
parent 8991d34f78
commit bd1fb311ea
27 changed files with 1797 additions and 623 deletions
@@ -1,9 +1,14 @@
import { describe, expect, it } from "vitest";
import { describe, expect, it, vi } from "vitest";
vi.hoisted(() => {
(globalThis as unknown as { __DEV__: boolean }).__DEV__ = false;
});
import {
resolveWorkspaceHeader,
shouldRenderMissingWorkspaceDescriptor,
} from "./workspace-header-source";
import { buildSidebarProjectsFromWorkspaces } from "@/hooks/use-sidebar-workspaces-list";
import { createSidebarWorkspaceEntry } from "@/hooks/use-sidebar-workspaces-list";
import type { WorkspaceDescriptor } from "@/stores/session-store";
describe("workspace source of truth consumption", () => {
@@ -23,17 +28,15 @@ describe("workspace source of truth consumption", () => {
};
const header = resolveWorkspaceHeader({ workspace });
const sidebarProjects = buildSidebarProjectsFromWorkspaces({
const sidebarWorkspace = createSidebarWorkspaceEntry({
serverId: "srv",
workspaces: [workspace],
projectOrder: [],
workspaceOrderByScope: {},
workspace,
});
expect(header.title).toBe("feat/workspace-sot");
expect(header.subtitle).toBe("getpaseo/paseo");
expect(sidebarProjects[0]?.workspaces[0]?.name).toBe(header.title);
expect(sidebarProjects[0]?.workspaces[0]?.statusBucket).toBe("running");
expect(sidebarWorkspace.name).toBe(header.title);
expect(sidebarWorkspace.statusBucket).toBe("running");
});
it("renders explicit missing state only after workspace hydration", () => {