Files
openchamber/packages/ui/src/stores/useGlobalSessionsStore.test.ts
T
c_w_xiaohei 701173e399 perf(ui): index and batch session updates
Batch global session, status, ordering, and activity timing mutations at the existing directory event boundary so large subagent bursts publish each owner once.

Maintain active session roots, children, and directory buckets in the global store, reuse them in Sidebar projections, and avoid rebuilding live aggregates and structural data for unrelated renders while preserving authoritative ordering reconciliation.
2026-08-26 00:42:37 +08:00

309 lines
13 KiB
TypeScript

import { beforeEach, describe, expect, test } from 'bun:test';
import type { Session } from '@opencode-ai/sdk/v2';
import {
isGlobalSessionRecencyOnlyUpdate,
resolveGlobalSessionDirectory,
mergeLiveSessionWithGlobalSession,
useGlobalSessionsStore,
} from './useGlobalSessionsStore';
type SessionExtra = Partial<Session> & {
directory?: string | null;
project?: { worktree?: string | null } | null;
};
const buildSession = (shareUrl: string, extra: SessionExtra = {}): Session => ({
id: 'ses_1',
title: 'Shared session',
time: { created: 1, updated: 2 },
share: { url: shareUrl },
...extra,
} as Session);
describe('useGlobalSessionsStore', () => {
beforeEach(() => {
useGlobalSessionsStore.setState({
activeSessions: [],
archivedSessions: [],
sessionsByDirectory: new Map(),
entityById: new Map(),
structure: {
activeSessionIds: [],
activeRootIds: [],
activeChildrenByParentId: new Map(),
activeIdsByDirectory: new Map(),
},
hasLoaded: false,
status: 'idle',
});
});
test('updates an existing session when the share URL changes', () => {
useGlobalSessionsStore.getState().upsertSession(buildSession('https://share.example/a'));
useGlobalSessionsStore.getState().upsertSession(buildSession('https://share.example/b'));
expect(useGlobalSessionsStore.getState().activeSessions[0]?.share?.url).toBe('https://share.example/b');
});
test('publishes an updated session when sharing is removed', () => {
useGlobalSessionsStore.getState().upsertSession(buildSession('https://share.example/a'));
const sharedSessions = useGlobalSessionsStore.getState().activeSessions;
useGlobalSessionsStore.getState().upsertSession({
...buildSession('https://share.example/a'),
share: undefined,
time: { created: 1, updated: 3 },
});
const unsharedSessions = useGlobalSessionsStore.getState().activeSessions;
expect(unsharedSessions).not.toBe(sharedSessions);
expect(unsharedSessions[0]?.share).toBe(undefined);
});
test('preserves directory metadata when a live update omits it', () => {
useGlobalSessionsStore.getState().upsertSession(buildSession('https://share.example/a', { directory: '/repo/app' }));
useGlobalSessionsStore.getState().upsertSession(buildSession('https://share.example/b', {
time: { created: 1, updated: 3 },
}));
const session = useGlobalSessionsStore.getState().activeSessions[0];
expect(resolveGlobalSessionDirectory(session)).toBe('/repo/app');
expect(useGlobalSessionsStore.getState().sessionsByDirectory.get('/repo/app')?.[0]?.id).toBe('ses_1');
});
test('preserves raw directory metadata when a live update only has project worktree', () => {
useGlobalSessionsStore.getState().upsertSession(buildSession('https://share.example/a', { directory: '/repo/app' }));
useGlobalSessionsStore.getState().upsertSession(buildSession('https://share.example/b', {
project: { worktree: '/repo/app' },
time: { created: 1, updated: 3 },
}));
const session = useGlobalSessionsStore.getState().activeSessions[0] as Session & { directory?: string | null };
expect(session.directory).toBe('/repo/app');
expect(resolveGlobalSessionDirectory(session)).toBe('/repo/app');
});
test('trusts explicit incoming raw directory metadata', () => {
useGlobalSessionsStore.getState().upsertSession(buildSession('https://share.example/a', { directory: '/repo/app' }));
useGlobalSessionsStore.getState().upsertSession(buildSession('https://share.example/b', {
directory: '/repo/app-worktree',
time: { created: 1, updated: 3 },
}));
expect(resolveGlobalSessionDirectory(useGlobalSessionsStore.getState().activeSessions[0])).toBe('/repo/app-worktree');
expect(useGlobalSessionsStore.getState().sessionsByDirectory.get('/repo/app')).toBe(undefined);
expect(useGlobalSessionsStore.getState().sessionsByDirectory.get('/repo/app-worktree')?.[0]?.id).toBe('ses_1');
});
test('preserves directory metadata when moving a session to archived', () => {
useGlobalSessionsStore.getState().upsertSession(buildSession('https://share.example/a', { directory: '/repo/app' }));
useGlobalSessionsStore.getState().upsertSession(buildSession('https://share.example/b', {
time: { created: 1, updated: 3, archived: 4 },
}));
expect(useGlobalSessionsStore.getState().activeSessions).toEqual([]);
expect(resolveGlobalSessionDirectory(useGlobalSessionsStore.getState().archivedSessions[0])).toBe('/repo/app');
});
test('preserves the opposite session-list reference during an upsert', () => {
const active = buildSession('https://share.example/active');
const archived = buildSession('https://share.example/archived', {
id: 'ses_archived',
time: { created: 1, updated: 2, archived: 3 },
});
useGlobalSessionsStore.getState().applySnapshot([active], [archived]);
const archivedSessions = useGlobalSessionsStore.getState().archivedSessions;
useGlobalSessionsStore.getState().upsertSession(buildSession('https://share.example/active-updated', {
time: { created: 1, updated: 3 },
}));
expect(useGlobalSessionsStore.getState().archivedSessions).toBe(archivedSessions);
const activeSessions = useGlobalSessionsStore.getState().activeSessions;
const structure = useGlobalSessionsStore.getState().structure;
useGlobalSessionsStore.getState().upsertSession({
...archived,
time: { created: 1, updated: 4, archived: 3 },
});
expect(useGlobalSessionsStore.getState().activeSessions).toBe(activeSessions);
expect(useGlobalSessionsStore.getState().structure).toBe(structure);
});
test('applies a batch of session upserts in one store publication', () => {
let publications = 0;
const unsubscribe = useGlobalSessionsStore.subscribe(() => {
publications += 1;
});
useGlobalSessionsStore.getState().upsertSessions([
buildSession('https://share.example/a'),
buildSession('https://share.example/b', { id: 'ses_2' }),
]);
unsubscribe();
expect(useGlobalSessionsStore.getState().activeSessions.map((session) => session.id)).toEqual(['ses_2', 'ses_1']);
expect(publications).toBe(1);
});
test('indexes a large batch of subagents in one store publication', () => {
const parent = buildSession('https://share.example/parent', { id: 'ses_parent' });
const children = Array.from({ length: 1_000 }, (_, index) => buildSession(
`https://share.example/child-${index}`,
{ id: `ses_child_${index}`, parentID: parent.id },
));
let publications = 0;
const unsubscribe = useGlobalSessionsStore.subscribe(() => {
publications += 1;
});
useGlobalSessionsStore.getState().upsertSessions([parent, ...children]);
unsubscribe();
const state = useGlobalSessionsStore.getState();
expect(publications).toBe(1);
expect(state.structure.activeRootIds).toEqual([parent.id]);
expect(state.structure.activeChildrenByParentId.get(parent.id)?.length).toBe(1_000);
});
test('preserves hierarchy references for entity-only updates', () => {
const parent = buildSession('https://share.example/parent', { id: 'ses_parent', directory: '/repo' });
const child = buildSession('https://share.example/child', {
id: 'ses_child',
directory: '/repo',
parentID: parent.id,
});
useGlobalSessionsStore.getState().upsertSessions([parent, child]);
const previous = useGlobalSessionsStore.getState();
const previousChildren = previous.structure.activeChildrenByParentId.get(parent.id);
useGlobalSessionsStore.getState().upsertSession({
...child,
title: 'Renamed child',
time: { ...child.time, updated: 3 },
});
const next = useGlobalSessionsStore.getState();
expect(next.structure).toBe(previous.structure);
expect(next.structure.activeChildrenByParentId.get(parent.id)).toBe(previousChildren);
expect(next.entityById.get(child.id)?.title).toBe('Renamed child');
});
test('updates only affected hierarchy buckets when a session is reparented', () => {
const parentA = buildSession('https://share.example/a', { id: 'ses_parent_a' });
const parentB = buildSession('https://share.example/b', { id: 'ses_parent_b' });
const parentC = buildSession('https://share.example/c', { id: 'ses_parent_c' });
const child = buildSession('https://share.example/child', { id: 'ses_child', parentID: parentA.id });
const unrelatedChild = buildSession('https://share.example/other', { id: 'ses_other', parentID: parentC.id });
useGlobalSessionsStore.getState().upsertSessions([parentA, parentB, parentC, child, unrelatedChild]);
const previous = useGlobalSessionsStore.getState().structure;
const unrelatedBucket = previous.activeChildrenByParentId.get(parentC.id);
useGlobalSessionsStore.getState().upsertSession({ ...child, parentID: parentB.id });
const next = useGlobalSessionsStore.getState().structure;
expect(next).not.toBe(previous);
expect(next.activeChildrenByParentId.get(parentA.id)).toBe(undefined);
expect([...next.activeChildrenByParentId.get(parentB.id) ?? []]).toEqual([child.id]);
expect(next.activeChildrenByParentId.get(parentC.id)).toBe(unrelatedBucket);
});
test('applies ordered mixed mutations in one publication', () => {
const original = buildSession('https://share.example/original', { id: 'ses_original' });
useGlobalSessionsStore.getState().upsertSession(original);
let publications = 0;
const unsubscribe = useGlobalSessionsStore.subscribe(() => {
publications += 1;
});
useGlobalSessionsStore.getState().applySessionMutations([
{ type: 'upsert', session: buildSession('https://share.example/temporary', { id: 'ses_temporary' }) },
{ type: 'remove', sessionId: original.id },
{ type: 'remove', sessionId: 'ses_temporary' },
{ type: 'upsert', session: buildSession('https://share.example/final', { id: 'ses_final' }) },
]);
unsubscribe();
const state = useGlobalSessionsStore.getState();
expect(publications).toBe(1);
expect(state.activeSessions.map((session) => session.id)).toEqual(['ses_final']);
expect(state.structure.activeRootIds).toEqual(['ses_final']);
});
});
describe('mergeLiveSessionWithGlobalSession', () => {
test('preserves global share over live share', () => {
const live = buildSession('https://live.example/s', { time: { created: 1, updated: 5 } });
const global = buildSession('https://global.example/s', { time: { created: 1, updated: 3 } });
const merged = mergeLiveSessionWithGlobalSession(live, global);
expect(merged.share?.url).toBe('https://global.example/s');
expect(merged.time?.updated).toBe(5);
});
test('preserves directory from global when live omits it', () => {
const live = buildSession('https://live.example/s', { time: { created: 1, updated: 5 } });
const global = buildSession('https://global.example/s', { directory: '/repo/app' });
const merged = mergeLiveSessionWithGlobalSession(live, global);
expect(resolveGlobalSessionDirectory(merged)).toBe('/repo/app');
});
test('live directory takes precedence over global when present', () => {
const live = buildSession('https://live.example/s', { directory: '/repo/worktree' });
const global = buildSession('https://global.example/s', { directory: '/repo/app' });
const merged = mergeLiveSessionWithGlobalSession(live, global);
expect(resolveGlobalSessionDirectory(merged)).toBe('/repo/worktree');
});
});
describe('isGlobalSessionRecencyOnlyUpdate', () => {
test('accepts an updated timestamp while preserving omitted directory metadata', () => {
const existing = buildSession('https://share.example/s', {
directory: '/repo/app',
time: { created: 1, updated: 2 },
});
const incoming = buildSession('https://share.example/s', {
time: { created: 1, updated: 3 },
});
expect(isGlobalSessionRecencyOnlyUpdate(existing, incoming)).toBe(true);
});
test('rejects title and archive changes as structural updates', () => {
const existing = buildSession('https://share.example/s', { time: { created: 1, updated: 2 } });
const renamed = buildSession('https://share.example/s', {
title: 'Renamed',
time: { created: 1, updated: 3 },
});
const archived = buildSession('https://share.example/s', {
time: { created: 1, updated: 3, archived: 4 },
});
expect(isGlobalSessionRecencyOnlyUpdate(existing, renamed)).toBe(false);
expect(isGlobalSessionRecencyOnlyUpdate(existing, archived)).toBe(false);
});
test('rejects parent and slug changes as structural updates', () => {
const existing = buildSession('https://share.example/s', {
parentID: 'parent-a',
slug: 'slug-a',
time: { created: 1, updated: 2 },
});
const reparented = buildSession('https://share.example/s', {
parentID: 'parent-b',
slug: 'slug-a',
time: { created: 1, updated: 3 },
});
const reslugged = buildSession('https://share.example/s', {
parentID: 'parent-a',
slug: 'slug-b',
time: { created: 1, updated: 3 },
});
expect(isGlobalSessionRecencyOnlyUpdate(existing, reparented)).toBe(false);
expect(isGlobalSessionRecencyOnlyUpdate(existing, reslugged)).toBe(false);
});
});