perf: streamline provider and agent startup loading #185

Avoids loading the full provider catalog on startup
Prewarms project config in the background
Prevents duplicate worktree-scoped config requests
This commit is contained in:
Bohdan Triapitsyn
2026-06-16 19:23:38 +03:00
parent fe99c455ae
commit a2a1cedf8d
10 changed files with 307 additions and 52 deletions
+1 -1
View File
@@ -451,7 +451,7 @@ A single store with N properties means every subscriber re-evaluates on every st
## Validation expectations
- Run `bun run type-check` and `bun run lint` before finalizing source-code changes that can affect TypeScript, runtime behavior, builds, lint rules, package resolution, or generated assets. Use a sufficiently long tool timeout for these workspace-wide checks (for example 240000ms) so successful package-level results are not lost to a tool timeout. For docs-only or isolated config-only changes, run the narrowest relevant validation instead (for example JSON/schema validation) and do not run the full checks unless the change can affect code execution.
- Run type-check/lint validation before finalizing source-code changes that can affect TypeScript, runtime behavior, builds, lint rules, package resolution, or generated assets, but keep validation scoped to the edited workspace by default. Prefer the package-level command for the package you changed (for example the relevant workspace's `type-check`/`lint`) instead of workspace-wide `bun run type-check` / `bun run lint`. Use workspace-wide checks only when the change spans multiple workspaces, shared package contracts, root tooling/config, dependency resolution, generated assets used across packages, or when a narrower command cannot cover the risk. Use a sufficiently long tool timeout for any broad checks (for example 240000ms) so successful package-level results are not lost to a tool timeout. For docs-only or isolated config-only changes, run the narrowest relevant validation instead (for example JSON/schema validation) and do not run full checks unless the change can affect code execution.
- For hot-path changes, verify behavior under streaming or repeated events, not just static render.
- For sync or startup changes, verify fresh load, retry/failure, and restart behavior.
- For session changes, verify create, stream, abort, permission, archive/delete, and revisit flows when relevant.
@@ -0,0 +1,9 @@
import { describe, expect, test } from 'bun:test';
import { shouldLoadAvailableProviders } from './providerAvailability';
describe('ProvidersPage available provider loading', () => {
test('loads available providers only in add-provider mode', () => {
expect(shouldLoadAvailableProviders(false)).toBe(false);
expect(shouldLoadAvailableProviders(true)).toBe(true);
});
});
@@ -23,6 +23,7 @@ import type { ModelMetadata } from '@/types';
import { getCurrentIntlLocale, useI18n } from '@/lib/i18n';
import { runtimeFetch } from '@/lib/runtime-fetch';
import { opencodeClient } from '@/lib/opencode/client';
import { shouldLoadAvailableProviders } from './providerAvailability';
const formatCompactNumber = (value: number) => new Intl.NumberFormat(getCurrentIntlLocale(), {
notation: 'compact',
@@ -169,6 +170,7 @@ export const ProvidersPage: React.FC = () => {
const [providerDropdownOpen, setProviderDropdownOpen] = React.useState(false);
const [providerSources, setProviderSources] = React.useState<Record<string, ProviderSources>>({});
const [showAuthPanel, setShowAuthPanel] = React.useState(false);
const isAddMode = selectedProviderId === ADD_PROVIDER_ID;
React.useEffect(() => {
if (!selectedProviderId && providers.length > 0) {
@@ -177,6 +179,10 @@ export const ProvidersPage: React.FC = () => {
}, [providers, selectedProviderId, setSelectedProvider]);
React.useEffect(() => {
if (!isAddMode) {
return;
}
let isMounted = true;
const loadAuthMethods = async () => {
@@ -204,9 +210,13 @@ export const ProvidersPage: React.FC = () => {
return () => {
isMounted = false;
};
}, [t]);
}, [isAddMode, t]);
React.useEffect(() => {
if (!shouldLoadAvailableProviders(isAddMode)) {
return;
}
let isMounted = true;
const loadAvailableProviders = async () => {
@@ -235,7 +245,7 @@ export const ProvidersPage: React.FC = () => {
return () => {
isMounted = false;
};
}, [t]);
}, [isAddMode, t]);
const connectedProviderIds = React.useMemo(
() => new Set(providers.map((provider) => provider.id)),
@@ -482,8 +492,6 @@ export const ProvidersPage: React.FC = () => {
}
};
const isAddMode = selectedProviderId === ADD_PROVIDER_ID;
if (!isAddMode && providers.length === 0) {
return (
<div className="flex h-full items-center justify-center">
@@ -0,0 +1 @@
export const shouldLoadAvailableProviders = (isAddMode: boolean): boolean => isAddMode;
+53 -11
View File
@@ -229,6 +229,7 @@ class OpencodeService {
private currentDirectory: string | undefined = undefined;
private directoryContextQueue: Promise<void> = Promise.resolve();
private listDirectoryInFlight: Map<string, Promise<FilesystemEntry[]>> = new Map();
private configProvidersInFlight: Map<string, Promise<{ providers: Provider[]; default: { [key: string]: string } }>> = new Map();
private listAgentsInFlight: Map<string, Promise<Agent[]>> = new Map();
private listDirectoryCache: Map<string, { entries: FilesystemEntry[]; expiresAt: number }> = new Map();
@@ -253,6 +254,8 @@ class OpencodeService {
this.client = createRuntimeOpencodeClient({ baseUrl: this.baseUrl });
this.scopedClients.clear();
this.listDirectoryInFlight.clear();
this.configProvidersInFlight.clear();
this.listAgentsInFlight.clear();
this.listDirectoryCache.clear();
}
@@ -1263,11 +1266,34 @@ class OpencodeService {
providers: Provider[];
default: { [key: string]: string };
}> {
const response = await this.client.config.providers(
this.currentDirectory ? { directory: this.currentDirectory } : undefined
);
if (!response.data) throw new Error('Failed to get providers');
return response.data;
return this.getProvidersForConfig(this.currentDirectory);
}
async getProvidersForConfig(directory?: string | null): Promise<{
providers: Provider[];
default: { [key: string]: string };
}> {
const effectiveDirectory = this.normalizeCandidatePath(directory) ?? directory ?? this.currentDirectory ?? undefined;
const key = effectiveDirectory ?? '';
const existing = this.configProvidersInFlight.get(key);
if (existing) {
return existing;
}
const request = (async () => {
const response = await this.client.config.providers(
effectiveDirectory ? { directory: effectiveDirectory } : undefined,
);
return unwrapSdkData(response, 'config.providers');
})();
this.configProvidersInFlight.set(key, request);
try {
return await request;
} finally {
this.configProvidersInFlight.delete(key);
}
}
// App Management - using config endpoint since /app doesn't exist in this version
@@ -1308,13 +1334,29 @@ class OpencodeService {
}
const request = (async () => {
const response = await this.client.app.agents(
effectiveDirectory ? { directory: effectiveDirectory } : undefined
);
if (response.error) {
throw new Error(`app.agents failed: ${formatSdkError(response.error)}`);
const params = effectiveDirectory ? { directory: effectiveDirectory } : undefined;
const response = await this.client.app.agents(params);
if (!response.error && Array.isArray(response.data) && response.data.length > 0) {
return response.data;
}
return response.data || [];
// SDK gap / endpoint drift: current OpenCode exposes the authoritative
// agent list at /agent, while app.agents can be empty on some runtimes.
const fallbackResponse = await runtimeFetch('/api/agent', {
...(effectiveDirectory ? { query: { directory: effectiveDirectory } } : {}),
});
if (!fallbackResponse.ok) {
if (response.error) {
throw new Error(`app.agents failed${response.response?.status ? ` (${response.response.status})` : ''}: ${formatSdkError(response.error)}`);
}
throw new Error(`agent.list failed (${fallbackResponse.status})`);
}
const fallbackData = await fallbackResponse.json().catch(() => null) as unknown;
if (!Array.isArray(fallbackData)) {
throw new Error('agent.list failed: invalid response');
}
return fallbackData as Agent[];
})();
this.listAgentsInFlight.set(key, request);
+17 -1
View File
@@ -106,6 +106,18 @@ mock.module('@/stores/utils/safeStorage', () => ({
getSafeStorage: () => makeStorage(),
}));
mock.module('@/stores/useProjectsStore', () => ({
useProjectsStore: {
getState: () => ({
activeProjectId: 'project',
projects: [
{ id: 'project', path: DIRECTORY, label: 'Project' },
{ id: 'other', path: OTHER_DIRECTORY, label: 'Other' },
],
}),
},
}));
mock.module('@/lib/opencode/client', () => ({
opencodeClient: {
setDirectory: mock(() => undefined),
@@ -126,6 +138,11 @@ mock.module('@/lib/opencode/client', () => ({
const id = liveProviderIdsByDirectory.get(currentFetchDirectory ?? '') ?? liveProviderId;
return { providers: [providerResponse(id, `${id}-model`, liveProviderVariants)], default: { default: id } };
}),
getProvidersForConfig: mock(async (directory?: string | null) => {
getProvidersCalls += 1;
const id = liveProviderIdsByDirectory.get(directory ?? '') ?? liveProviderId;
return { providers: [providerResponse(id, `${id}-model`, liveProviderVariants)], default: { default: id } };
}),
listAgents: mock(async () => []),
},
}));
@@ -286,7 +303,6 @@ describe('useConfigStore provider persistence', () => {
const state = useConfigStore.getState();
expect(getProvidersCalls).toBe(2);
expect(new Set(withDirectoryCalls)).toEqual(new Set([DIRECTORY, OTHER_DIRECTORY]));
expect(state.directoryScoped[DIRECTORY]?.providers.map((entry) => entry.id)).toEqual(['active-live']);
expect(state.directoryScoped[OTHER_DIRECTORY]?.providers.map((entry) => entry.id)).toEqual(['inactive-live']);
expect(state.directoryScoped[OTHER_DIRECTORY]?.defaultProviders).toEqual({ default: 'inactive-live' });
+124 -16
View File
@@ -734,6 +734,34 @@ const rememberWorktreeProject = (worktree: string, project: string): void => {
}
};
const normalizeConfigPath = (value: string | null | undefined): string | null => {
const trimmed = typeof value === 'string' ? value.trim() : '';
if (!trimmed) return null;
return trimmed.replace(/\\/g, '/').replace(/\/+$/, '') || '/';
};
const getKnownProjectDirectories = (): string[] => {
try {
return useProjectsStore.getState().projects
.map((project) => normalizeConfigPath(project.path))
.filter((path): path is string => Boolean(path));
} catch {
return [];
}
};
const getFallbackProjectDirectory = (): string | null => {
try {
const { projects, activeProjectId } = useProjectsStore.getState();
const active = activeProjectId
? projects.find((project) => project.id === activeProjectId)
: null;
return normalizeConfigPath(active?.path ?? projects[0]?.path ?? null);
} catch {
return null;
}
};
/**
* Map a directory to its CONFIG scope. Providers/agents/defaults are defined at
* the PROJECT level (opencode.json), so a worktree must inherit its parent
@@ -742,11 +770,14 @@ const rememberWorktreeProject = (worktree: string, project: string): void => {
* a known worktree, else the directory unchanged.
*/
const resolveConfigDirectory = (directory: string | null | undefined): string | null => {
const dir = typeof directory === 'string' && directory.trim().length > 0 ? directory : null;
if (!dir) return dir;
// 1. Persisted mapping — resolves synchronously at startup, before the async
// git worktree discovery has populated the runtime map.
const cached = getWorktreeProjectMap()[dir];
const dir = normalizeConfigPath(directory);
const projects = getKnownProjectDirectories();
if (!dir) return null;
if (projects.includes(dir)) return dir;
// 1. Persisted mapping — resolves synchronously when the async worktree
// discovery has not populated the runtime map yet.
const cached = normalizeConfigPath(getWorktreeProjectMap()[dir]);
if (cached) return cached;
// 2. Live resolution via projects + discovered worktree map; cache the hit.
try {
@@ -755,14 +786,15 @@ const resolveConfigDirectory = (directory: string | null | undefined): string |
useSessionUIStore.getState().availableWorktreesByProject,
dir,
);
if (project?.path && project.path !== dir) {
rememberWorktreeProject(dir, project.path);
return project.path;
const projectPath = normalizeConfigPath(project?.path ?? null);
if (projectPath && projectPath !== dir) {
rememberWorktreeProject(dir, projectPath);
return projectPath;
}
} catch {
return dir;
return null;
}
return dir;
return null;
};
const toConfigDirectoryKey = (directory: string | null | undefined): string =>
@@ -775,6 +807,7 @@ const toConfigDirectoryKey = (directory: string | null | undefined): string =>
const _providersLoadedAt = new Map<string, number>();
const _agentsLoadedAt = new Map<string, number>();
const CONFIG_REFRESH_TTL_MS = 30_000;
const PROJECT_CONFIG_PREWARM_DELAY_MS = 1_000;
const isConfigFresh = (loadedAt: Map<string, number>, key: string): boolean => {
const at = loadedAt.get(key);
return typeof at === 'number' && Date.now() - at < CONFIG_REFRESH_TTL_MS;
@@ -945,6 +978,7 @@ interface ConfigStore {
probeConnection: (options?: { timeoutMs?: number }) => Promise<boolean>;
checkConnection: () => Promise<boolean>;
initializeApp: () => Promise<void>;
prewarmProjectConfigs: (initialDirectory?: string | null) => Promise<void>;
getCurrentProvider: () => ProviderWithModelList | undefined;
getCurrentModel: () => ProviderModel | undefined;
getCurrentAgent: () => Agent | undefined;
@@ -1237,7 +1271,12 @@ export const useConfigStore = create<ConfigStore>()(
// active key + snapshot key always match and stay project-scoped.
// Everything below operates on this key unchanged; the OpenCode
// working directory (opencodeClient.getDirectory()) is separate.
const directoryKey = toConfigDirectoryKey(directory);
const configDirectory = resolveConfigDirectory(directory);
if (!configDirectory) {
markStartupTrace('activateDirectory:skippedUnknownDirectory', { directory });
return;
}
const directoryKey = toDirectoryKey(configDirectory);
let snapshotHadProviders = false;
let snapshotHadAgents = false;
@@ -1360,6 +1399,10 @@ export const useConfigStore = create<ConfigStore>()(
// Providers are project-scoped: resolve a worktree to its project
// so it reuses one shared snapshot instead of its own.
const configDirectory = resolveConfigDirectory(requestedDirectory);
if (!configDirectory) {
markStartupTrace('loadProviders:skippedUnknownDirectory', { requestedDirectory, source: options?.source ?? 'unknown' });
return;
}
const effectiveDirectory = configDirectory ?? opencodeClient.getDirectory() ?? null;
const directoryKey = toDirectoryKey(configDirectory);
const source = options?.source ?? 'unknown';
@@ -1388,10 +1431,7 @@ export const useConfigStore = create<ConfigStore>()(
);
const apiResult = await measureStartupTrace(
'loadProviders:api',
() => opencodeClient.withDirectory(
fromDirectoryKey(directoryKey),
() => opencodeClient.getProviders()
),
() => opencodeClient.getProvidersForConfig(fromDirectoryKey(directoryKey)),
{ directoryKey, source, requestedDirectory, effectiveDirectory, attempt: attempt + 1 },
);
const providers = Array.isArray(apiResult?.providers) ? apiResult.providers : [];
@@ -1779,6 +1819,10 @@ export const useConfigStore = create<ConfigStore>()(
// Agents are project-scoped: resolve a worktree to its project
// so it reuses one shared snapshot instead of its own.
const configDirectory = resolveConfigDirectory(requestedDirectory);
if (!configDirectory) {
markStartupTrace('loadAgents:skippedUnknownDirectory', { requestedDirectory, source: options?.source ?? 'unknown' });
return false;
}
const effectiveDirectory = configDirectory ?? opencodeClient.getDirectory() ?? null;
const directoryKey = toDirectoryKey(configDirectory);
const source = options?.source ?? 'unknown';
@@ -2748,7 +2792,21 @@ export const useConfigStore = create<ConfigStore>()(
useSessionUIStore.getState().availableWorktreesByProject,
initialDirectory ?? null,
);
const configDirectory = resolvedProject?.path ?? initialDirectory ?? null;
const resolvedInitialDirectory = resolveConfigDirectory(resolvedProject?.path ?? initialDirectory ?? null);
const configDirectory = resolvedInitialDirectory ?? getFallbackProjectDirectory();
if (!configDirectory) {
markStartupTrace('initializeApp:noProjectConfigDirectory');
set({ isInitialized: true, isConnected: true, hasEverConnected: true, connectionPhase: "connected" });
return;
}
if (!resolvedInitialDirectory && initialDirectory !== configDirectory) {
markStartupTrace('initializeApp:normalizedUnknownDirectoryToProject', {
initialDirectory,
configDirectory,
});
opencodeClient.setDirectory(configDirectory);
useDirectoryStore.getState().setDirectory(configDirectory, { showOverlay: false });
}
const configDirectoryKey = toDirectoryKey(configDirectory);
if (get().activeDirectoryKey !== configDirectoryKey) {
set({ activeDirectoryKey: configDirectoryKey });
@@ -2761,6 +2819,7 @@ export const useConfigStore = create<ConfigStore>()(
]);
set({ isInitialized: true, isConnected: true, hasEverConnected: true, connectionPhase: "connected" });
void get().prewarmProjectConfigs(configDirectory);
const initEnded = typeof performance !== 'undefined' ? performance.now() : Date.now();
markStartupTrace('initializeApp:end', {
durationMs: Math.round(initEnded - initStarted),
@@ -2786,6 +2845,55 @@ export const useConfigStore = create<ConfigStore>()(
return run;
},
prewarmProjectConfigs: async (initialDirectory?: string | null) => {
if (!get().isConnected) {
return;
}
const initialKey = toConfigDirectoryKey(initialDirectory ?? fromDirectoryKey(get().activeDirectoryKey));
const projectDirectories = useProjectsStore.getState().projects
.map((project) => project.path)
.filter((path): path is string => typeof path === 'string' && path.trim().length > 0);
const seen = new Set<string>([initialKey]);
const queuedDirectories: string[] = [];
for (const directory of projectDirectories) {
const directoryKey = toConfigDirectoryKey(directory);
if (seen.has(directoryKey)) {
continue;
}
seen.add(directoryKey);
const snapshot = get().directoryScoped[directoryKey];
if (snapshot?.providers.length && snapshot.agents.length) {
continue;
}
const scopedDirectory = fromDirectoryKey(directoryKey);
if (scopedDirectory) {
queuedDirectories.push(scopedDirectory);
}
}
for (const directory of queuedDirectories) {
await sleep(PROJECT_CONFIG_PREWARM_DELAY_MS);
if (!get().isConnected) {
return;
}
const directoryKey = toConfigDirectoryKey(directory);
const snapshot = get().directoryScoped[directoryKey];
const tasks: Promise<unknown>[] = [];
if (!snapshot?.providers.length) {
tasks.push(get().loadProviders({ directory, source: 'projectConfigPrewarm' }));
}
if (!snapshot?.agents.length) {
tasks.push(get().loadAgents({ directory, source: 'projectConfigPrewarm' }));
}
if (tasks.length > 0) {
await Promise.allSettled(tasks);
}
}
},
getCurrentProvider: () => {
const { providers, currentProviderId } = get();
return providers.find((p) => p.id === currentProviderId);
@@ -0,0 +1,79 @@
import { beforeEach, describe, expect, mock, test } from 'bun:test';
const storage = new Map<string, string>();
let storageSetCount = 0;
const safeStorage = {
getItem: (key: string) => storage.get(key) ?? null,
setItem: (key: string, value: string) => {
storageSetCount += 1;
storage.set(key, value);
},
removeItem: (key: string) => {
storage.delete(key);
},
clear: () => {
storage.clear();
},
key: (index: number) => Array.from(storage.keys())[index] ?? null,
get length() {
return storage.size;
},
} as Storage;
mock.module('./utils/safeStorage', () => ({
getSafeStorage: () => safeStorage,
}));
mock.module('@/lib/desktop', () => ({
isVSCodeRuntime: () => false,
}));
mock.module('@/lib/runtime-fetch', () => ({
runtimeFetch: mock(async () => new Response('{}', { headers: { 'Content-Type': 'application/json' } })),
}));
const { useSessionFoldersStore } = await import('./useSessionFoldersStore');
const waitForPersist = () => new Promise((resolve) => setTimeout(resolve, 350));
describe('useSessionFoldersStore folder assignments', () => {
beforeEach(() => {
storage.clear();
storageSetCount = 0;
useSessionFoldersStore.setState({
foldersMap: {},
collapsedFolderIds: new Set<string>(),
});
});
test('repeated addSessionToFolder to the same folder preserves foldersMap reference', async () => {
const store = useSessionFoldersStore.getState();
const folder = store.createFolder('/workspace/project', 'Work');
store.addSessionToFolder('/workspace/project', folder.id, 'ses_1');
await waitForPersist();
storageSetCount = 0;
const before = useSessionFoldersStore.getState().foldersMap;
useSessionFoldersStore.getState().addSessionToFolder('/workspace/project', folder.id, 'ses_1');
await waitForPersist();
expect(useSessionFoldersStore.getState().foldersMap).toBe(before);
expect(storageSetCount).toBe(0);
});
test('repeated addSessionsToFolder to the same folder preserves foldersMap reference', async () => {
const store = useSessionFoldersStore.getState();
const folder = store.createFolder('/workspace/project', 'Batch');
store.addSessionsToFolder('/workspace/project', folder.id, ['ses_1', 'ses_2']);
await waitForPersist();
storageSetCount = 0;
const before = useSessionFoldersStore.getState().foldersMap;
useSessionFoldersStore.getState().addSessionsToFolder('/workspace/project', folder.id, ['ses_1', 'ses_2']);
await waitForPersist();
expect(useSessionFoldersStore.getState().foldersMap).toBe(before);
expect(storageSetCount).toBe(0);
});
});
+1 -8
View File
@@ -80,7 +80,6 @@ export async function bootstrapGlobal(
set({ projects })
}),
),
retry(() => sdk.provider.list().then((x) => set({ providers: unwrap(x, "provider.list") }))),
])
const errors = results
@@ -125,7 +124,6 @@ export async function bootstrapDirectory(input: {
global: {
config: Record<string, unknown>
projects: Project[]
providers: { all: unknown[]; connected: unknown[]; default: Record<string, unknown> }
}
loadSessions: (directory: string) => Promise<void> | void
}) {
@@ -136,9 +134,6 @@ export async function bootstrapDirectory(input: {
// Seed from global state while we fetch directory-specific data
const seededProject = projectID(directory, g.projects)
if (seededProject) set({ project: seededProject })
if (state.provider.all.length === 0 && g.providers.all.length > 0) {
set({ provider: g.providers as State["provider"] })
}
if (Object.keys(state.config ?? {}).length === 0 && Object.keys(g.config ?? {}).length > 0) {
set({ config: g.config as State["config"] })
}
@@ -152,7 +147,6 @@ export async function bootstrapDirectory(input: {
seededProject
? Promise.resolve()
: retry(() => sdk.project.current().then((x) => set({ project: unwrap(x, "project.current").id }))),
retry(() => sdk.provider.list().then((x) => set({ provider: unwrap(x, "provider.list") }))),
retry(() => sdk.config.get().then((x) => set({ config: unwrap(x, "config.get") }))),
retry(() =>
sdk.path.get().then((x) => {
@@ -177,7 +171,7 @@ export async function bootstrapDirectory(input: {
// - path.get feeds project resolution, but if we already resolved a project
// (from global projects) its failure is tolerable; the worktree path is
// refreshed by later events.
const [, , , pathResult] = phase1Results
const [, , pathResult] = phase1Results
const pathFailedWithoutProject =
pathResult.status === "rejected" && !getState().project
@@ -194,7 +188,6 @@ export async function bootstrapDirectory(input: {
// These enrich the UI but aren't required for basic functionality.
// ---------------------------------------------------------------------------
void Promise.allSettled([
retry(() => sdk.app.agents().then((x) => set({ agent: unwrap(x, "app.agents") }))),
retry(() => sdk.command.list().then((x) => set({ command: unwrap(x, "command.list") }))),
retry(() => sdk.mcp.status().then((x) => set({ mcp: unwrap(x, "mcp.status") }))),
retry(() => sdk.lsp.status().then((x) => set({ lsp: unwrap(x, "lsp.status") }))),
+10 -11
View File
@@ -1657,11 +1657,10 @@ export function SyncProvider(props: {
ingestDirectoryStateIntoRoutingIndex(routingIndex, directory, store.getState())
}
},
global: {
config: globalState.config,
projects: globalState.projects,
providers: globalState.providers,
},
global: {
config: globalState.config,
projects: globalState.projects,
},
loadSessions: (dir) => retry(async () => {
const rootSessions = (await listGlobalSessionPages(props.sdk, {
directory: dir,
@@ -1752,11 +1751,11 @@ export function SyncProvider(props: {
const generation = ++globalBootstrapGeneration
bootingRoot = true
const globalActions = useGlobalSyncStore.getState().actions
bootstrapGlobal(props.sdk, (patch) => {
if (globalBootstrapGeneration === generation) {
globalActions.set(patch)
}
})
bootstrapGlobal(props.sdk, (patch) => {
if (globalBootstrapGeneration === generation) {
globalActions.set(patch)
}
})
.then(() => {
if (globalBootstrapGeneration === generation) {
bootedAt = Date.now()
@@ -1772,7 +1771,7 @@ export function SyncProvider(props: {
bootingRoot = false
}
}
}, [props.sdk])
}, [props.sdk])
// Event pipeline — created once per mount. No class, no start/stop.
// Abort controller owned by the pipeline closure. Cleanup aborts + flushes.