Skip to content
Closed
Show file tree
Hide file tree
Changes from 22 commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
3897689
Reduce background Git ref and port polling
Quicksaver Jul 20, 2026
e1bf7a2
Document background Git ref and port polling behavior
Quicksaver Jul 20, 2026
72c361c
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Quicksaver Jul 20, 2026
66bdee8
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Quicksaver Jul 20, 2026
7944bea
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Quicksaver Jul 20, 2026
5b816e5
Serialize preview snapshot notifications
Quicksaver Jul 20, 2026
8f2fdbb
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Quicksaver Jul 21, 2026
d46e13c
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Quicksaver Jul 22, 2026
152b294
test(vcs): align ref polling coverage with interval
Quicksaver Jul 22, 2026
7ef36da
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Quicksaver Jul 22, 2026
bfdd40a
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Quicksaver Jul 23, 2026
a188c8e
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Quicksaver Jul 23, 2026
365fca6
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Quicksaver Jul 23, 2026
97457be
test(web): cover ref menu refreshes
Quicksaver Jul 23, 2026
c58f8ec
docs: clarify ref refresh ownership
Quicksaver Jul 23, 2026
9b39a60
Clarify ref refresh provenance and test scope
Quicksaver Jul 23, 2026
32a480b
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Quicksaver Jul 23, 2026
594bc91
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Quicksaver Jul 23, 2026
3b8c967
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Quicksaver Jul 23, 2026
62fa974
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Quicksaver Jul 23, 2026
50fe890
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Quicksaver Jul 23, 2026
bb1446c
Merge upstream/main into fix/reduce-background-git-ref-port-polling
Quicksaver Jul 24, 2026
fd138b3
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Quicksaver Jul 24, 2026
33063a2
Roll back failed preview listener subscriptions
Quicksaver Jul 24, 2026
1863288
Serialize preview port scans
Quicksaver Jul 24, 2026
3afa927
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Quicksaver Jul 24, 2026
24b63ab
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Quicksaver Jul 25, 2026
8a2719f
Reduce idle port polling and refresh VCS refs after query reset
Quicksaver Jul 26, 2026
6baa89f
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Quicksaver Jul 27, 2026
43bca69
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Quicksaver Jul 27, 2026
a11db7d
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Quicksaver Jul 27, 2026
77e9752
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Quicksaver Jul 27, 2026
f194fc7
Merge commit '32843c2551bd5832d436ef46bff5ab1198cdc39c' into fix/redu…
Quicksaver Jul 27, 2026
71df377
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Jul 28, 2026
326c399
Prevent reentrant port and ref refresh races
Jul 28, 2026
b14ddbc
Queue reentrant scans after active polling
Jul 28, 2026
84c6fd2
Make port listener cleanup interruptible
Jul 28, 2026
e02f789
Merge remote-tracking branch 'refs/remotes/upstream/main' into fix/re…
Jul 28, 2026
07cdda0
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Jul 29, 2026
2731ed7
Guarantee preview listener delivery acknowledgments
Jul 29, 2026
ae08700
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Jul 29, 2026
d88c2bb
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Jul 29, 2026
d3a5586
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Jul 29, 2026
d2b9075
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Jul 29, 2026
f160a45
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Jul 29, 2026
03ca85e
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Jul 30, 2026
86afbea
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Jul 30, 2026
d8c4beb
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Jul 30, 2026
88979c0
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Jul 31, 2026
56b544b
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Jul 31, 2026
db0b7b5
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Jul 31, 2026
d094997
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Jul 31, 2026
6d1ae1b
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Jul 31, 2026
4864bc1
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Aug 2, 2026
d6a94e8
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Aug 3, 2026
3a0dcfc
Rescan once after terminal processes settle
Aug 3, 2026
f5a682e
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Aug 6, 2026
0868321
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Aug 6, 2026
4e3d219
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Aug 7, 2026
89568c2
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Aug 7, 2026
bf142c5
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Aug 7, 2026
69bfbf1
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Aug 8, 2026
1ed7966
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Aug 8, 2026
63bae7d
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Aug 8, 2026
a9251e4
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Aug 9, 2026
557ee6b
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Aug 9, 2026
206649f
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Aug 9, 2026
34d1ce4
Merge commit '2abe66800cceeafea759d4db782c72298cc1e1e1' into fix/redu…
Aug 10, 2026
0d580a5
Merge commit 'f21d5e444e9a6b5876253ca8618a4dc8d4f2146c' into fix/redu…
Aug 10, 2026
1622878
Merge commit '5da45337f7178db711624c3e7c4d69059b2b286a' into fix/redu…
Aug 10, 2026
634491a
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Aug 10, 2026
980f778
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Aug 10, 2026
c2b9a35
merge: integrate latest upstream main
Quicksaver Aug 16, 2026
793eeaa
refactor(preview): unify retention configuration
Quicksaver Aug 16, 2026
f835e8d
docs: update preview polling customization details
Quicksaver Aug 16, 2026
2d3a241
test(preview): cover configured retention cache reuse
Quicksaver Aug 16, 2026
f46ee50
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Quicksaver Aug 17, 2026
9fb07d8
Add preconnect to successful fetch test mock
Quicksaver Aug 17, 2026
e9f361f
fix(server): preserve idle terminal settle scan
Quicksaver Aug 17, 2026
8662947
fix(server): harden preview scan lifecycle
Quicksaver Aug 17, 2026
184e47f
fix(server): close preview polling races
Quicksaver Aug 17, 2026
1175bbe
merge upstream main into preview polling branch
Quicksaver Aug 18, 2026
d19f087
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Quicksaver Aug 18, 2026
28db86b
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Quicksaver Aug 18, 2026
eedca9b
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Quicksaver Aug 19, 2026
c26fcb2
fix(server): preserve preview scan interruptions
Quicksaver Aug 19, 2026
59b92d3
Merge remote-tracking branch 'upstream/main' into fix/reduce-backgrou…
Quicksaver Aug 19, 2026
7a9baca
test(server): await interrupted preview registration exit
Quicksaver Aug 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions BRANCH_DETAILS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# Background Git Ref And Port Polling

Frequently mounted Git-ref and preview-discovery surfaces stay fresh without continuously repeating their most expensive subprocess work.

Expected behavior:

- Git ref lists revalidate their first page every 20 seconds instead of every five seconds. Loaded cursor pages are one-shot snapshots, and inactive ref atoms expire after 30 seconds.
- The composer branch selector's open-triggered ref refresh originated upstream. This branch preserves that behavior while routing it through the same branch-owned open-only refresh helper used by the Diff panel comparison-ref menu, which explicitly refreshes both local and remote refs on open.
- Closing either menu resets its query state without triggering another ref refresh, so user interaction stays fresh without adding close-triggered work.
- Preview port discovery performs one immediate scan when the first subscriber retains it. Subscriptions replay the latest snapshot instead of initiating a duplicate scan.
- Subscription replay and concurrent snapshot broadcasts are serialized so a stale replay cannot arrive after a newer scan result.
- Managed terminal process-set changes trigger an immediate port scan; unchanged registrations and redundant removals do not.
- The broad `lsof` safety-net scan runs every 20 seconds when no server is known and every 10 seconds while a listener is present. This preserves discovery for servers started outside T3-managed terminals without a permanent three-second system-wide process sweep.

Primary files:

- `packages/client-runtime/src/state/vcs.ts`
- `apps/web/src/components/BranchToolbarBranchSelector.tsx`
- `apps/web/src/components/DiffPanel.tsx`
- `apps/web/src/components/vcsRefMenuRefresh.ts`
- `apps/server/src/preview/PortScanner.ts`
- `apps/server/src/ws.ts`

Focused regression coverage:

- `packages/client-runtime/src/state/vcs.test.ts` covers the 20-second first-page revalidation interval.
- `apps/web/src/components/vcsRefMenuRefresh.test.ts` covers the shared helper's one-callback and multiple-callback open paths plus its no-refresh-on-close path; component wiring remains visible in the two primary component files above.
- `apps/server/src/preview/PortScanner.test.ts` covers snapshot replay without rescanning, ordered replay during concurrent broadcasts, and unchanged terminal registrations avoiding redundant probes.

## Development Ports

- Web: `5740`
- Server/WebSocket: `13780`
110 changes: 110 additions & 0 deletions apps/server/src/preview/PortScanner.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import { it as effectIt } from "@effect/vitest";
import { HostProcessPlatform } from "@t3tools/shared/hostProcess";
import * as Net from "@t3tools/shared/Net";
import * as Cause from "effect/Cause";
import * as Deferred from "effect/Deferred";
import * as Effect from "effect/Effect";
import * as Exit from "effect/Exit";
import * as Fiber from "effect/Fiber";
import * as Layer from "effect/Layer";
import * as PlatformError from "effect/PlatformError";
import { expect } from "vite-plus/test";
Expand Down Expand Up @@ -155,3 +157,111 @@ effectIt("does not swallow process probe interruption", () =>
}
}),
);

effectIt("replays snapshots without rescanning unchanged terminal registrations", () => {
let probeCount = 0;
let replayCount = 0;
const layer = makeProbeFailureLayer(() =>
Effect.sync(() => {
probeCount += 1;
return {
stdout: "",
stderr: "",
code: null,
timedOut: false,
stdoutTruncated: false,
stderrTruncated: false,
};
}),
);

return Effect.gen(function* () {
const scanner = yield* PortScanner.PortDiscovery;
yield* scanner.subscribe(() => Effect.void);
yield* scanner.retain;
yield* scanner.registerTerminalProcesses({
threadId: "thread-1",
terminalId: "terminal-1",
processIds: [42],
});
yield* scanner.registerTerminalProcesses({
threadId: "thread-1",
terminalId: "terminal-1",
processIds: [42],
});
yield* scanner.subscribe(() =>
Effect.sync(() => {
replayCount += 1;
}),
);

expect(probeCount).toBe(2);
expect(replayCount).toBe(1);
}).pipe(Effect.provide(layer));
});

effectIt("serializes snapshot replay with concurrent broadcasts", () =>
Effect.gen(function* () {
const replayStarted = yield* Deferred.make<void>();
const releaseReplay = yield* Deferred.make<void>();
const secondProbeCompleted = yield* Deferred.make<void>();
const secondDeliveryStarted = yield* Deferred.make<void>();
const deliveries: Array<ReadonlyArray<number>> = [];
let probeCount = 0;
let deliveryCount = 0;
const layer = makeProbeFailureLayer(() =>
Effect.gen(function* () {
probeCount += 1;
if (probeCount === 2) {
yield* Deferred.succeed(secondProbeCompleted, undefined).pipe(Effect.ignore);
}
return {
stdout: probeCount === 1 ? "p100\ncnode\nn*:3000\n" : "p101\ncnode\nn*:3001\n",
stderr: "",
code: null,
timedOut: false,
stdoutTruncated: false,
stderrTruncated: false,
};
}),
);

yield* Effect.gen(function* () {
const scanner = yield* PortScanner.PortDiscovery;
yield* scanner.retain;

const subscription = yield* scanner
.subscribe((servers) =>
Effect.gen(function* () {
deliveryCount += 1;
if (deliveryCount === 1) {
yield* Deferred.succeed(replayStarted, undefined).pipe(Effect.ignore);
yield* Deferred.await(releaseReplay);
} else {
yield* Deferred.succeed(secondDeliveryStarted, undefined).pipe(Effect.ignore);
}
deliveries.push(servers.map((server) => server.port));
}),
)
.pipe(Effect.forkScoped);
yield* Deferred.await(replayStarted);

const registration = yield* scanner
.registerTerminalProcesses({
threadId: "thread-1",
terminalId: "terminal-1",
processIds: [101],
})
.pipe(Effect.forkScoped);
yield* Deferred.await(secondProbeCompleted);
yield* Effect.yieldNow;
expect(yield* Deferred.isDone(secondDeliveryStarted)).toBe(false);

yield* Deferred.succeed(releaseReplay, undefined);
yield* Fiber.join(subscription);
yield* Fiber.join(registration);

expect(deliveries).toEqual([[3000], [3001]]);
}).pipe(Effect.provide(layer));
}),
);
93 changes: 68 additions & 25 deletions apps/server/src/preview/PortScanner.ts
Comment thread
Quicksaver marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import * as Duration from "effect/Duration";
import * as Effect from "effect/Effect";
import * as Layer from "effect/Layer";
import * as Ref from "effect/Ref";
import * as Schedule from "effect/Schedule";
import * as Semaphore from "effect/Semaphore";
import * as Scope from "effect/Scope";

import * as ProcessRunner from "../processRunner.ts";
Expand Down Expand Up @@ -50,7 +50,8 @@ export const COMMON_DEV_PORTS: ReadonlyArray<number> = Object.freeze([
3000, 3001, 3333, 4173, 4200, 4321, 5000, 5173, 5174, 5175, 5500, 8000, 8080, 8081, 8888, 9000,
]);

const POLL_INTERVAL = Duration.seconds(3);
const ACTIVE_POLL_INTERVAL = Duration.seconds(10);
const IDLE_POLL_INTERVAL = Duration.seconds(20);
const LSOF_TIMEOUT_MS = 5_000;
const WINDOWS_LISTENER_TIMEOUT_MS = 5_000;

Expand Down Expand Up @@ -79,6 +80,9 @@ const terminalOwnerKey = (owner: {
readonly terminalId: string;
}): string => `${owner.threadId}\u0000${owner.terminalId}`;

const processIdsEqual = (left: ReadonlySet<number>, right: ReadonlySet<number>): boolean =>
left.size === right.size && [...left].every((processId) => right.has(processId));

const parseLsofOutput = (
raw: string,
terminalByProcessId: ReadonlyMap<number, TerminalProcessOwner> = new Map(),
Expand Down Expand Up @@ -190,6 +194,7 @@ export const make = Effect.gen(function* PortDiscoveryMake() {
const net = yield* Net.NetService;
const processRunner = yield* ProcessRunner.ProcessRunner;
const hostPlatform = yield* HostProcessPlatform;
const notificationLock = yield* Semaphore.make(1);
const stateRef = yield* Ref.make<ScannerState>({
lastSnapshot: [],
listeners: new Set(),
Expand Down Expand Up @@ -292,25 +297,49 @@ export const make = Effect.gen(function* PortDiscoveryMake() {
yield* Effect.forEach(listeners, (listener) => listener(servers), { discard: true });
});

const publishSnapshot = Effect.fn("PortDiscovery.publishSnapshot")(function* (
next: ReadonlyArray<DiscoveredLocalServer>,
) {
yield* notificationLock.withPermit(
Effect.gen(function* () {
const changed = yield* Ref.modify(stateRef, (state) =>
serversEqual(state.lastSnapshot, next)
? [false, state]
: [true, { ...state, lastSnapshot: next }],
);
if (changed) yield* broadcast(next);
}),
);
});

const pollTick = Effect.fn("PortDiscovery.pollTick")(
function* () {
if ((yield* Ref.get(stateRef)).retainCount <= 0) return;
const next = yield* scanOnce();
const changed = yield* Ref.modify(stateRef, (state) =>
serversEqual(state.lastSnapshot, next)
? [false, state]
: [true, { ...state, lastSnapshot: next }],
);
if (changed) yield* broadcast(next);
yield* publishSnapshot(next);
Comment thread
cursor[bot] marked this conversation as resolved.
Outdated
},
Effect.catchCause((cause: Cause.Cause<never>) =>
Effect.logWarning("preview port scan failed", Cause.pretty(cause)),
),
);

// Single layer-scoped polling fiber. Ticks are no-ops when no client is
// currently retained, so the cost is one Ref.get every POLL_INTERVAL.
yield* Effect.forkScoped(pollTick().pipe(Effect.repeat(Schedule.spaced(POLL_INTERVAL))));
// Keep broad listener discovery as a fallback, but avoid a system-wide lsof
// process every three seconds while the app is otherwise idle. Terminal PID
// changes trigger immediate scans below; the periodic loop is only the
// safety net for listeners started outside a managed terminal.
yield* Effect.forkScoped(
Effect.gen(function* () {
while (true) {
const state = yield* Ref.get(stateRef);
yield* Effect.sleep(
state.retainCount > 0 && state.lastSnapshot.length > 0
? ACTIVE_POLL_INTERVAL
: IDLE_POLL_INTERVAL,
);
yield* pollTick();
}
}),
);

const acquireRetention = Effect.fn("PortDiscovery.retain")(function* () {
const wasIdle = yield* Ref.modify(stateRef, (state) => [
Expand All @@ -334,16 +363,23 @@ export const make = Effect.gen(function* PortDiscoveryMake() {
const subscribe: PortDiscovery["Service"]["subscribe"] = Effect.fn("PortDiscovery.subscribe")(
Comment thread
macroscopeapp[bot] marked this conversation as resolved.
Outdated
(listener) =>
Effect.acquireRelease(
Ref.update(stateRef, (state) => ({
...state,
listeners: new Set([...state.listeners, listener]),
})),
notificationLock.withPermit(
Ref.modify(stateRef, (state) => [
state.lastSnapshot,
{
...state,
listeners: new Set([...state.listeners, listener]),
},
]).pipe(Effect.tap(listener)),
),
Comment thread
Quicksaver marked this conversation as resolved.
Outdated
() =>
Ref.update(stateRef, (state) => {
const listeners = new Set(state.listeners);
listeners.delete(listener);
return { ...state, listeners };
}),
notificationLock.withPermit(
Ref.update(stateRef, (state) => {
const listeners = new Set(state.listeners);
listeners.delete(listener);
return { ...state, listeners };
}),
),
),
);

Expand All @@ -356,26 +392,33 @@ export const make = Effect.gen(function* PortDiscoveryMake() {
const processIds = new Set(
input.processIds.filter((processId) => Number.isInteger(processId) && processId > 0),
);
yield* Ref.update(stateRef, (state) => {
const changed = yield* Ref.modify(stateRef, (state) => {
const terminalProcesses = new Map(state.terminalProcesses);
const key = terminalOwnerKey(owner);
const existing = terminalProcesses.get(key);
if (existing && processIdsEqual(existing.processIds, processIds)) {
return [false, state] as const;
}
if (processIds.size === 0) {
if (!existing) return [false, state] as const;
terminalProcesses.delete(key);
} else {
terminalProcesses.set(key, { owner, processIds });
}
return { ...state, terminalProcesses };
return [true, { ...state, terminalProcesses }] as const;
});
if (changed) yield* pollTick();
});

const unregisterTerminal: PortDiscovery["Service"]["unregisterTerminal"] = Effect.fn(
"PortDiscovery.unregisterTerminal",
)(function* (input) {
yield* Ref.update(stateRef, (state) => {
const changed = yield* Ref.modify(stateRef, (state) => {
const terminalProcesses = new Map(state.terminalProcesses);
terminalProcesses.delete(terminalOwnerKey(input));
return { ...state, terminalProcesses };
const removed = terminalProcesses.delete(terminalOwnerKey(input));
return [removed, removed ? { ...state, terminalProcesses } : state] as const;
});
if (changed) yield* pollTick();
});

return PortDiscovery.of({
Expand Down
9 changes: 3 additions & 6 deletions apps/server/src/ws.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1964,13 +1964,10 @@ const makeWsRpcLayer = (
WS_METHODS.subscribeDiscoveredLocalServers,
Stream.callback<DiscoveredLocalServerList>((queue) =>
Effect.gen(function* () {
// Retention performs one immediate scan when discovery was
// idle. Subscribe replays that snapshot to every connection,
// including connections that join an already-retained scanner.
yield* portDiscovery.retain;
const initial = yield* portDiscovery.scan();
const initialScannedAt = DateTime.formatIso(yield* DateTime.now);
yield* Queue.offer(queue, {
servers: initial,
scannedAt: initialScannedAt,
});
yield* portDiscovery.subscribe((servers) =>
Effect.gen(function* () {
const scannedAt = DateTime.formatIso(yield* DateTime.now);
Expand Down
4 changes: 2 additions & 2 deletions apps/web/src/components/BranchToolbarBranchSelector.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ import {
} from "./ui/combobox";
import { stackedThreadToast, toastManager } from "./ui/toast";
import { Tooltip, TooltipPopup, TooltipTrigger } from "./ui/tooltip";
import { refreshVcsRefsOnMenuOpen } from "./vcsRefMenuRefresh";

interface BranchToolbarBranchSelectorProps {
className?: string;
Expand Down Expand Up @@ -500,9 +501,8 @@ export function BranchToolbarBranchSelector({
setIsBranchMenuOpen(open);
if (!open) {
setBranchQuery("");
return;
}
branchRefState.refresh();
refreshVcsRefsOnMenuOpen(open, branchRefState.refresh);
},
[branchRefState.refresh],
);
Expand Down
6 changes: 5 additions & 1 deletion apps/web/src/components/DiffPanel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,7 @@ import { serverEnvironment } from "../state/server";
import { reviewEnvironment } from "../state/review";
import { vcsEnvironment } from "../state/vcs";
import { buildBaseRefChoices, filterBaseRefChoices } from "../lib/baseRefChoices";
import { refreshVcsRefsOnMenuOpen } from "./vcsRefMenuRefresh";

type DiffRenderMode = "stacked" | "split";
type DiffThemeType = "light" | "dark";
Expand Down Expand Up @@ -593,7 +594,10 @@ export default function DiffPanel({
filteredItems={filteredBaseRefItems}
value={selectedBaseRef ?? AUTOMATIC_BASE_REF}
onOpenChange={(open) => {
if (!open) setBaseRefQuery("");
if (!open) {
setBaseRefQuery("");
}
refreshVcsRefsOnMenuOpen(open, localBranchRefs.refresh, remoteBranchRefs.refresh);
}}
onValueChange={(value) => {
if (!value) return;
Expand Down
Loading
Loading