-
Notifications
You must be signed in to change notification settings - Fork 24
Expand file tree
/
Copy pathstore.ts
More file actions
99 lines (88 loc) · 2.69 KB
/
store.ts
File metadata and controls
99 lines (88 loc) · 2.69 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
/**
* Store Module
*
* Manages project state using a simple pub/sub pattern.
* Designed to work with React's useSyncExternalStore.
*
* Batches rapid state updates (e.g., from session streaming changes)
* to prevent excessive React re-renders. Updates are batched until the next animation
* frame, coalescing all changes that happen before the browser paints.
*/
import type { ProjectState, ProjectStore, StateListener } from './types';
/**
* Creates an initial project state.
* Messages and streaming are now per-session — the project store only
* holds project-wide state and the sessions array.
*/
export function createInitialState(
deploymentId: string | null,
modelId: string | null,
gitRepoFullName: string | null
): ProjectState {
return {
isStreaming: false,
isInterrupting: false,
previewUrl: null,
previewStatus: 'idle',
deploymentId,
model: modelId ?? 'anthropic/claude-sonnet-4',
currentIframeUrl: null,
gitRepoFullName,
sessions: [],
pendingNewSession: false,
};
}
/**
* Creates a project store for managing state.
*
* Implements frame-based batched notifications: multiple setState calls are batched
* until the next animation frame, then a single notification is fired. This works
* for both:
* - Multiple calls in the same event loop tick (synchronous)
* - Multiple calls across event loop ticks (e.g., rapid session state changes)
*
* The batching window ends when the browser is ready to paint, ensuring all updates
* that arrive before a frame are combined into a single React re-render.
*
* In test/SSR environments without requestAnimationFrame, falls back to setTimeout(0).
*/
export function createProjectStore(initialState: ProjectState): ProjectStore {
let state = initialState;
const listeners = new Set<StateListener>();
let notificationPending = false;
// Use requestAnimationFrame in browser, setTimeout in tests/SSR
const hasRAF = typeof requestAnimationFrame === 'function';
function scheduleNotification(): void {
if (notificationPending) {
return;
}
notificationPending = true;
const notify = () => {
notificationPending = false;
listeners.forEach(listener => listener());
};
if (hasRAF) {
requestAnimationFrame(notify);
} else {
setTimeout(notify, 0);
}
}
function getState(): ProjectState {
return state;
}
function setState(partial: Partial<ProjectState>): void {
state = { ...state, ...partial };
scheduleNotification();
}
function subscribe(listener: StateListener): () => void {
listeners.add(listener);
return () => {
listeners.delete(listener);
};
}
return {
getState,
setState,
subscribe,
};
}