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:
@@ -12,6 +12,7 @@ import {
|
||||
HostRuntimeController,
|
||||
HostRuntimeStore,
|
||||
type HostRuntimeControllerDeps,
|
||||
type HostRuntimeSnapshot,
|
||||
} from "./host-runtime";
|
||||
|
||||
class FakeDaemonClient {
|
||||
@@ -264,6 +265,27 @@ function clearProbeBackoff(controller: HostRuntimeController): void {
|
||||
).connectionLastProbedAt.clear();
|
||||
}
|
||||
|
||||
type HostRuntimeSnapshotPatch = Partial<Omit<HostRuntimeSnapshot, "serverId" | "clientGeneration">>;
|
||||
|
||||
function updateControllerSnapshot(
|
||||
controller: HostRuntimeController,
|
||||
patch: HostRuntimeSnapshotPatch,
|
||||
): void {
|
||||
(
|
||||
controller as unknown as {
|
||||
updateSnapshot: (patch: HostRuntimeSnapshotPatch) => void;
|
||||
}
|
||||
).updateSnapshot(patch);
|
||||
}
|
||||
|
||||
function makeProbeMap(
|
||||
entries: Array<
|
||||
[string, HostRuntimeSnapshot["probeByConnectionId"] extends Map<string, infer T> ? T : never]
|
||||
>,
|
||||
): HostRuntimeSnapshot["probeByConnectionId"] {
|
||||
return new Map(entries);
|
||||
}
|
||||
|
||||
describe("HostRuntimeController", () => {
|
||||
it("keeps known hosts in connecting when a created client reports idle during connect", async () => {
|
||||
const host = makeHost({
|
||||
@@ -923,6 +945,134 @@ describe("HostRuntimeController", () => {
|
||||
expect(controller.getSnapshot().clientGeneration).toBe(generationBeforeProbes);
|
||||
expect(createdClients).toHaveLength(0);
|
||||
});
|
||||
|
||||
it("does not notify or replace the snapshot for equal probe maps", () => {
|
||||
const controller = new HostRuntimeController({ host: makeHost() });
|
||||
const firstProbeMap = makeProbeMap([
|
||||
["direct:lan:6767", { status: "available", latencyMs: 12 }],
|
||||
]);
|
||||
const equalProbeMap = makeProbeMap([
|
||||
["direct:lan:6767", { status: "available", latencyMs: 12 }],
|
||||
]);
|
||||
|
||||
updateControllerSnapshot(controller, { probeByConnectionId: firstProbeMap });
|
||||
const snapshotAfterFirstProbe = controller.getSnapshot();
|
||||
let notifyCount = 0;
|
||||
const unsubscribe = controller.subscribe(() => {
|
||||
notifyCount += 1;
|
||||
});
|
||||
|
||||
updateControllerSnapshot(controller, { probeByConnectionId: equalProbeMap });
|
||||
|
||||
expect(notifyCount).toBe(0);
|
||||
expect(controller.getSnapshot()).toBe(snapshotAfterFirstProbe);
|
||||
expect(controller.getSnapshot().probeByConnectionId).toBe(firstProbeMap);
|
||||
unsubscribe();
|
||||
});
|
||||
|
||||
it("does not notify or replace the snapshot when connection status is already equal", () => {
|
||||
const controller = new HostRuntimeController({ host: makeHost() });
|
||||
|
||||
updateControllerSnapshot(controller, { connectionStatus: "online" });
|
||||
const snapshotAfterOnline = controller.getSnapshot();
|
||||
let notifyCount = 0;
|
||||
const unsubscribe = controller.subscribe(() => {
|
||||
notifyCount += 1;
|
||||
});
|
||||
|
||||
updateControllerSnapshot(controller, { connectionStatus: "online" });
|
||||
|
||||
expect(notifyCount).toBe(0);
|
||||
expect(controller.getSnapshot()).toBe(snapshotAfterOnline);
|
||||
unsubscribe();
|
||||
});
|
||||
|
||||
it("does not notify or replace the snapshot when every patched field is equal", () => {
|
||||
const controller = new HostRuntimeController({ host: makeHost() });
|
||||
const firstProbeMap = makeProbeMap([
|
||||
["direct:lan:6767", { status: "available", latencyMs: 12 }],
|
||||
]);
|
||||
const equalProbeMap = makeProbeMap([
|
||||
["direct:lan:6767", { status: "available", latencyMs: 12 }],
|
||||
]);
|
||||
|
||||
updateControllerSnapshot(controller, {
|
||||
connectionStatus: "online",
|
||||
probeByConnectionId: firstProbeMap,
|
||||
});
|
||||
const snapshotAfterSetup = controller.getSnapshot();
|
||||
let notifyCount = 0;
|
||||
const unsubscribe = controller.subscribe(() => {
|
||||
notifyCount += 1;
|
||||
});
|
||||
|
||||
updateControllerSnapshot(controller, {
|
||||
connectionStatus: "online",
|
||||
probeByConnectionId: equalProbeMap,
|
||||
});
|
||||
|
||||
expect(notifyCount).toBe(0);
|
||||
expect(controller.getSnapshot()).toBe(snapshotAfterSetup);
|
||||
expect(controller.getSnapshot().probeByConnectionId).toBe(firstProbeMap);
|
||||
unsubscribe();
|
||||
});
|
||||
|
||||
it("notifies once for a changed field while preserving equal field identity", () => {
|
||||
const controller = new HostRuntimeController({ host: makeHost() });
|
||||
const firstProbeMap = makeProbeMap([
|
||||
["direct:lan:6767", { status: "available", latencyMs: 12 }],
|
||||
]);
|
||||
const equalProbeMap = makeProbeMap([
|
||||
["direct:lan:6767", { status: "available", latencyMs: 12 }],
|
||||
]);
|
||||
|
||||
updateControllerSnapshot(controller, {
|
||||
connectionStatus: "online",
|
||||
probeByConnectionId: firstProbeMap,
|
||||
});
|
||||
const snapshotBeforeChange = controller.getSnapshot();
|
||||
let notifyCount = 0;
|
||||
const unsubscribe = controller.subscribe(() => {
|
||||
notifyCount += 1;
|
||||
});
|
||||
|
||||
updateControllerSnapshot(controller, {
|
||||
connectionStatus: "offline",
|
||||
probeByConnectionId: equalProbeMap,
|
||||
});
|
||||
|
||||
expect(notifyCount).toBe(1);
|
||||
expect(controller.getSnapshot()).not.toBe(snapshotBeforeChange);
|
||||
expect(controller.getSnapshot().connectionStatus).toBe("offline");
|
||||
expect(controller.getSnapshot().probeByConnectionId).toBe(firstProbeMap);
|
||||
unsubscribe();
|
||||
});
|
||||
|
||||
it("notifies once and replaces the probe map when probe contents change", () => {
|
||||
const controller = new HostRuntimeController({ host: makeHost() });
|
||||
const firstProbeMap = makeProbeMap([
|
||||
["direct:lan:6767", { status: "available", latencyMs: 12 }],
|
||||
]);
|
||||
const changedProbeMap = makeProbeMap([
|
||||
["direct:lan:6767", { status: "available", latencyMs: 12 }],
|
||||
["relay:relay.paseo.sh:443", { status: "unavailable", latencyMs: null }],
|
||||
]);
|
||||
|
||||
updateControllerSnapshot(controller, { probeByConnectionId: firstProbeMap });
|
||||
const snapshotBeforeChange = controller.getSnapshot();
|
||||
let notifyCount = 0;
|
||||
const unsubscribe = controller.subscribe(() => {
|
||||
notifyCount += 1;
|
||||
});
|
||||
|
||||
updateControllerSnapshot(controller, { probeByConnectionId: changedProbeMap });
|
||||
|
||||
expect(notifyCount).toBe(1);
|
||||
expect(controller.getSnapshot()).not.toBe(snapshotBeforeChange);
|
||||
expect(controller.getSnapshot().probeByConnectionId).toBe(changedProbeMap);
|
||||
expect(controller.getSnapshot().probeByConnectionId).not.toBe(firstProbeMap);
|
||||
unsubscribe();
|
||||
});
|
||||
});
|
||||
|
||||
describe("HostRuntimeStore", () => {
|
||||
|
||||
Reference in New Issue
Block a user