fix(ui): hide transient dirty state while a worktree bootstraps
A freshly created worktree transiently looks dirty until its setup commands and initial git reset finish. The work status panel showed those files as changes on the branch and the draft's worktree dropdown flashed its dirty warning, and both then froze on that state because nothing refetched after bootstrap. Make the bootstrap state (the existing authority on unfinished creation) subscribable, suppress the dirty probe and the changed-files row while it is pending, and force one status fetch when it settles so the lifted gate shows the reset tree.
This commit is contained in:
@@ -51,6 +51,7 @@ const {
|
||||
markWorktreeBootstrapPending,
|
||||
setWorktreeBootstrapState,
|
||||
startWorktreeBootstrapWatcher,
|
||||
subscribeWorktreeBootstrapState,
|
||||
waitForWorktreeBootstrap,
|
||||
waitForWorktreeGitReady,
|
||||
} = await import('./worktreeBootstrap');
|
||||
@@ -246,3 +247,39 @@ describe('worktreeBootstrap.waitForWorktreeBootstrap', () => {
|
||||
clearWorktreeBootstrapState('/repo-wt');
|
||||
});
|
||||
});
|
||||
|
||||
describe('worktreeBootstrap subscription', () => {
|
||||
beforeEach(() => {
|
||||
clearWorktreeBootstrapState('/repo-wt');
|
||||
});
|
||||
|
||||
test('notifies subscribers as a directory enters and leaves bootstrap', () => {
|
||||
const pendingSnapshots: boolean[] = [];
|
||||
const unsubscribe = subscribeWorktreeBootstrapState(() => {
|
||||
pendingSnapshots.push(getWorktreeBootstrapState('/repo-wt')?.status === 'pending');
|
||||
});
|
||||
|
||||
try {
|
||||
markWorktreeBootstrapPending('/repo-wt');
|
||||
setWorktreeBootstrapState('/repo-wt', { status: 'ready', error: null, updatedAt: 2 });
|
||||
clearWorktreeBootstrapState('/repo-wt');
|
||||
} finally {
|
||||
unsubscribe();
|
||||
}
|
||||
|
||||
expect(pendingSnapshots).toEqual([true, false, false]);
|
||||
});
|
||||
|
||||
test('stops notifying after unsubscribe', () => {
|
||||
let notifications = 0;
|
||||
const unsubscribe = subscribeWorktreeBootstrapState(() => {
|
||||
notifications += 1;
|
||||
});
|
||||
|
||||
markWorktreeBootstrapPending('/repo-wt');
|
||||
unsubscribe();
|
||||
clearWorktreeBootstrapState('/repo-wt');
|
||||
|
||||
expect(notifications).toBe(1);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -24,6 +24,23 @@ const watchers = new Map<string, { cancelled: boolean; lifecycleVersion: number
|
||||
const getKey = (directory: string): string => normalizePath(directory);
|
||||
const getWaiterKey = (key: string, target: WorktreeBootstrapTarget): string => `${key}\n${target}`;
|
||||
|
||||
// UI surfaces subscribe to know when a directory enters or leaves bootstrap,
|
||||
// so a half-created worktree's transient files are never shown as changes.
|
||||
const bootstrapListeners = new Set<() => void>();
|
||||
|
||||
const notifyBootstrapListeners = (): void => {
|
||||
for (const listener of bootstrapListeners) {
|
||||
listener();
|
||||
}
|
||||
};
|
||||
|
||||
export const subscribeWorktreeBootstrapState = (listener: () => void): (() => void) => {
|
||||
bootstrapListeners.add(listener);
|
||||
return () => {
|
||||
bootstrapListeners.delete(listener);
|
||||
};
|
||||
};
|
||||
|
||||
const startLifecycle = (key: string): void => {
|
||||
const watcher = watchers.get(key);
|
||||
if (watcher) {
|
||||
@@ -72,6 +89,7 @@ const storePolledState = (
|
||||
}
|
||||
|
||||
state.set(key, next);
|
||||
notifyBootstrapListeners();
|
||||
return next;
|
||||
};
|
||||
|
||||
@@ -98,6 +116,7 @@ export const markWorktreeBootstrapPending = (directory: string): void => {
|
||||
error: null,
|
||||
updatedAt: Date.now(),
|
||||
});
|
||||
notifyBootstrapListeners();
|
||||
};
|
||||
|
||||
export const clearWorktreeBootstrapState = (directory: string): void => {
|
||||
@@ -108,6 +127,7 @@ export const clearWorktreeBootstrapState = (directory: string): void => {
|
||||
startLifecycle(key);
|
||||
state.delete(key);
|
||||
lifecycleVersions.delete(key);
|
||||
notifyBootstrapListeners();
|
||||
};
|
||||
|
||||
export const setWorktreeBootstrapState = (directory: string, next: WorktreeBootstrapState): void => {
|
||||
@@ -117,6 +137,7 @@ export const setWorktreeBootstrapState = (directory: string, next: WorktreeBoots
|
||||
}
|
||||
startLifecycle(key);
|
||||
state.set(key, next);
|
||||
notifyBootstrapListeners();
|
||||
};
|
||||
|
||||
export const getWorktreeBootstrapState = (directory: string): WorktreeBootstrapState | null => {
|
||||
|
||||
Reference in New Issue
Block a user