batch3 T7 review fix: serialize open_file_in_browser to avoid double pane
When no file_browser pane exists, two rapid open_file_in_browser events could both trigger create() since the ref check happens before the first create resolves. Add a creating flag/promise so the second event waits for the first create then updates the newly-created pane's state. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -72,12 +72,36 @@ export function Workspace({ sessionId, projectId }: Props) {
|
||||
}
|
||||
}, [panes, activeId]);
|
||||
|
||||
// Tracks an in-flight create() call so rapid open_file_in_browser events
|
||||
// don't race to each spawn a new file_browser pane. While a create is in
|
||||
// progress the subsequent events wait for it and update the same pane.
|
||||
const creatingRef = useRef<{ id: string; promise: Promise<string> } | null>(
|
||||
null
|
||||
);
|
||||
|
||||
// Subscribe to open_file_in_browser events: focus an existing file_browser
|
||||
// pane (updating its open_file) or spawn one if room is available.
|
||||
useEffect(() => {
|
||||
return sessionEvents.subscribe((event) => {
|
||||
if (event.type !== 'open_file_in_browser') return;
|
||||
void (async () => {
|
||||
// If a create is already in flight, wait for it to finish then update
|
||||
// the newly-created pane rather than spawning a second one.
|
||||
if (creatingRef.current) {
|
||||
const { id: pendingId, promise } = creatingRef.current;
|
||||
const resolvedId = await promise;
|
||||
const targetId = resolvedId || pendingId;
|
||||
const current = panesRef.current;
|
||||
const fb = current?.find((p) => p.id === targetId);
|
||||
const nextState: FileBrowserPaneState = {
|
||||
...(fb?.kind === 'file_browser' ? fb.state : {}),
|
||||
open_file: event.path,
|
||||
};
|
||||
await update(targetId, { state: nextState });
|
||||
setActiveId(targetId);
|
||||
return;
|
||||
}
|
||||
|
||||
const current = panesRef.current;
|
||||
if (!current) return;
|
||||
const fb = current.find(
|
||||
@@ -92,14 +116,32 @@ export function Workspace({ sessionId, projectId }: Props) {
|
||||
await update(fb.id, { state: nextState });
|
||||
setActiveId(fb.id);
|
||||
} else if (current.length < MAX_PANES) {
|
||||
const newPane = await create({ kind: 'file_browser' });
|
||||
const nextState: FileBrowserPaneState = {
|
||||
open_file: event.path,
|
||||
filter: '',
|
||||
expanded_dirs: [],
|
||||
// Reserve the slot immediately so concurrent events see the flag.
|
||||
const createPromise = (async (): Promise<string> => {
|
||||
const newPane = await create({ kind: 'file_browser' });
|
||||
return newPane.id;
|
||||
})();
|
||||
// Use a stable object; id is filled in once resolved.
|
||||
const entry: { id: string; promise: Promise<string> } = {
|
||||
id: '',
|
||||
promise: createPromise,
|
||||
};
|
||||
await update(newPane.id, { state: nextState });
|
||||
setActiveId(newPane.id);
|
||||
creatingRef.current = entry;
|
||||
try {
|
||||
const newId = await createPromise;
|
||||
entry.id = newId;
|
||||
const nextState: FileBrowserPaneState = {
|
||||
open_file: event.path,
|
||||
filter: '',
|
||||
expanded_dirs: [],
|
||||
};
|
||||
await update(newId, { state: nextState });
|
||||
setActiveId(newId);
|
||||
} finally {
|
||||
if (creatingRef.current === entry) {
|
||||
creatingRef.current = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
})();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user