Skip to content

Commit 690830b

Browse files
Yuruzuuclaude
andcommitted
fix(web): a branch picked right after opening the picker no longer waits for its status read
Co-Authored-By: Claude Opus 5.5 <[email protected]>
1 parent ac5e48c commit 690830b

3 files changed

Lines changed: 73 additions & 2 deletions

File tree

‎apps/web/src/components/BranchToolbarBranchSelector.tsx‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ export function BranchToolbarBranchSelector({
125125
const switchRef = useAtomCommand(vcsEnvironment.switchRef, {
126126
reportFailure: false,
127127
});
128-
const refreshStatus = useAtomCommand(vcsEnvironment.refreshStatus, {
128+
const refreshLocalStatusForPicker = useAtomCommand(vcsEnvironment.refreshLocalStatusForPicker, {
129129
reportFailure: false,
130130
});
131131
const createRefMutation = useAtomCommand(vcsEnvironment.createRef, {
@@ -512,7 +512,10 @@ export function BranchToolbarBranchSelector({
512512
const handleOpenChange = (open: boolean) => {
513513
setIsBranchMenuOpen(open);
514514
if (open && !isBranchMenuOpen && branchCwd !== null) {
515-
void refreshStatus({ environmentId, input: { cwd: branchCwd, localOnly: true } });
515+
void refreshLocalStatusForPicker({
516+
environmentId,
517+
input: { cwd: branchCwd, localOnly: true },
518+
});
516519
}
517520
if (!open) {
518521
setBranchQuery("");

‎packages/client-runtime/src/state/vcs.test.ts‎

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -467,6 +467,67 @@ describe("cached VCS refs", () => {
467467
),
468468
);
469469

470+
it.effect("a checkout does not wait behind a picker's local status refresh", () =>
471+
Effect.scoped(
472+
Effect.gen(function* () {
473+
const refreshStarted = yield* Deferred.make<void>();
474+
const releaseRefresh = yield* Deferred.make<void>();
475+
const client = {
476+
[WS_METHODS.vcsRefreshStatus]: () =>
477+
Deferred.succeed(refreshStarted, undefined).pipe(
478+
Effect.andThen(Deferred.await(releaseRefresh)),
479+
),
480+
[WS_METHODS.vcsSwitchRef]: () => Effect.succeed({ refName: "feature" }),
481+
} as unknown as WsRpcProtocolClient;
482+
const supervisor = EnvironmentSupervisor.EnvironmentSupervisor.of({
483+
target: TARGET,
484+
state: yield* SubscriptionRef.make(CONNECTED_CONNECTION_STATE),
485+
session: yield* SubscriptionRef.make(Option.some(session(client))),
486+
prepared: yield* SubscriptionRef.make(Option.none<PreparedConnection>()),
487+
connect: Effect.void,
488+
disconnect: Effect.void,
489+
retryNow: Effect.void,
490+
} satisfies EnvironmentSupervisor.EnvironmentSupervisor["Service"]);
491+
const run: EnvironmentRegistry.EnvironmentRegistry["Service"]["run"] = (
492+
_environmentId,
493+
effect,
494+
) => Effect.provideService(effect, EnvironmentSupervisor.EnvironmentSupervisor, supervisor);
495+
const runtime = Atom.runtime(
496+
Layer.merge(
497+
Layer.succeed(
498+
EnvironmentRegistry.EnvironmentRegistry,
499+
EnvironmentRegistry.EnvironmentRegistry.of({
500+
run,
501+
} as unknown as EnvironmentRegistry.EnvironmentRegistry["Service"]),
502+
),
503+
Layer.succeed(Persistence.EnvironmentCacheStore, cacheWithRefs(Option.none())),
504+
),
505+
);
506+
const atoms = createVcsEnvironmentAtoms(runtime);
507+
const registry = yield* Effect.acquireRelease(Effect.sync(AtomRegistry.make), (registry) =>
508+
Effect.sync(() => registry.dispose()),
509+
);
510+
const target = { environmentId: TARGET.environmentId, input: { cwd: "/repo" } };
511+
512+
const refresh = atoms.refreshLocalStatusForPicker.run(registry, {
513+
...target,
514+
input: { cwd: "/repo", localOnly: true },
515+
});
516+
yield* Deferred.await(refreshStarted);
517+
const checkout = yield* Effect.promise(() =>
518+
atoms.switchRef.run(registry, {
519+
...target,
520+
input: { cwd: "/repo", refName: "feature" },
521+
}),
522+
);
523+
524+
expect(AsyncResult.isSuccess(checkout)).toBe(true);
525+
yield* Deferred.succeed(releaseRefresh, undefined);
526+
expect(AsyncResult.isSuccess(yield* Effect.promise(() => refresh))).toBe(true);
527+
}),
528+
),
529+
);
530+
470531
it.effect("suppresses persisted snapshots after an environment-wide clear fails", () =>
471532
Effect.scoped(
472533
Effect.gen(function* () {

‎packages/client-runtime/src/state/vcs.ts‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -341,6 +341,13 @@ export function createVcsEnvironmentAtoms<R, E>(
341341
concurrency: vcsCommandConcurrency,
342342
onSettled: invalidateRefs,
343343
}),
344+
// Outside the serial queue: it only reads status, and a checkout picked right after opening
345+
// the picker must not wait behind that read.
346+
refreshLocalStatusForPicker: createEnvironmentRpcCommand(runtime, {
347+
label: "environment-data:vcs:refresh-local-status-for-picker",
348+
tag: WS_METHODS.vcsRefreshStatus,
349+
onSettled: invalidateRefs,
350+
}),
344351
createWorktree: createEnvironmentRpcCommand(runtime, {
345352
label: "environment-data:vcs:create-worktree",
346353
tag: WS_METHODS.vcsCreateWorktree,

0 commit comments

Comments
 (0)