From 0cba706b01e2e0fef620893d441e272cdac7894e Mon Sep 17 00:00:00 2001
From: Neil <4138956+nwparker@users.noreply.github.com>
Date: Sun, 6 Sep 2026 18:33:19 -0700
Subject: [PATCH] fix(ports): coalesce advertised URL refresh bursts (#19150)
---
.../ports/WorkspacePortScanner.test.tsx | 110 ++++++++++++++++++
.../components/ports/WorkspacePortScanner.tsx | 14 ++-
2 files changed, 122 insertions(+), 2 deletions(-)
diff --git a/src/renderer/src/components/ports/WorkspacePortScanner.test.tsx b/src/renderer/src/components/ports/WorkspacePortScanner.test.tsx
index 0ae53fee931..7741368d025 100644
--- a/src/renderer/src/components/ports/WorkspacePortScanner.test.tsx
+++ b/src/renderer/src/components/ports/WorkspacePortScanner.test.tsx
@@ -602,3 +602,113 @@ describe('WorkspacePortScanner', () => {
expect(getPublishedRemoteWorktreePorts()).toBeUndefined()
})
})
+
+describe('advertised URL refresh bursts', () => {
+ async function mountLocalScanner(): Promise<() => void> {
+ useAppStore.setState({ settings: getDefaultSettings('/tmp/orca-workspaces') })
+ await act(async () => {
+ root?.render()
+ await flushPromises()
+ })
+ localScan.mockClear()
+ return vi.mocked(window.api.workspacePorts.onAdvertisedUrlChanged).mock
+ .calls[0][0] as () => void
+ }
+
+ it('coalesces sequential URL changes into one immediate scan and one settled scan', async () => {
+ const changed = await mountLocalScanner()
+ for (let index = 0; index < 5; index++) {
+ await act(async () => {
+ changed()
+ await flushPromises()
+ await vi.advanceTimersByTimeAsync(100)
+ })
+ }
+ expect(localScan).toHaveBeenCalledTimes(1)
+ await act(async () => {
+ await vi.advanceTimersByTimeAsync(1_000)
+ })
+ expect(localScan).toHaveBeenCalledTimes(2)
+ await act(async () => {
+ changed()
+ await flushPromises()
+ })
+ expect(localScan).toHaveBeenCalledTimes(3)
+ })
+
+ it('cancels the settled scan on unmount', async () => {
+ const changed = await mountLocalScanner()
+ await act(async () => {
+ changed()
+ await flushPromises()
+ })
+ act(() => root?.unmount())
+ root = null
+ await vi.advanceTimersByTimeAsync(2_000)
+ expect(localScan).toHaveBeenCalledTimes(1)
+ })
+
+ it('skips the settled scan while hidden and accepts the next visible URL change', async () => {
+ let visibility: DocumentVisibilityState = 'visible'
+ const restore = overrideDocumentVisibilityState(() => visibility)
+ try {
+ const changed = await mountLocalScanner()
+ await act(async () => {
+ changed()
+ await flushPromises()
+ })
+ visibility = 'hidden'
+ await act(async () => {
+ await vi.advanceTimersByTimeAsync(2_000)
+ })
+ expect(localScan).toHaveBeenCalledTimes(1)
+ visibility = 'visible'
+ await act(async () => {
+ changed()
+ await flushPromises()
+ })
+ expect(localScan).toHaveBeenCalledTimes(2)
+ } finally {
+ restore()
+ }
+ })
+})
+
+it('releases the URL burst when its leading scan finishes while hidden', async () => {
+ let visibility: DocumentVisibilityState = 'visible'
+ const restore = overrideDocumentVisibilityState(() => visibility)
+ try {
+ useAppStore.setState({ settings: getDefaultSettings('/tmp/orca-workspaces') })
+ await act(async () => {
+ root?.render()
+ await flushPromises()
+ })
+ const changed = vi.mocked(window.api.workspacePorts.onAdvertisedUrlChanged).mock
+ .calls[0][0] as () => void
+ let finish!: (scan: WorkspacePortScanResult) => void
+ localScan.mockClear()
+ localScan.mockImplementationOnce(
+ () =>
+ new Promise((resolve) => {
+ finish = resolve
+ })
+ )
+ await act(async () => {
+ changed()
+ await flushPromises()
+ })
+ visibility = 'hidden'
+ await act(async () => {
+ finish(emptyScan)
+ await flushPromises()
+ })
+ visibility = 'visible'
+ await act(async () => {
+ changed()
+ await flushPromises()
+ })
+ expect(localScan).toHaveBeenCalledTimes(2)
+ } finally {
+ restore()
+ }
+})
diff --git a/src/renderer/src/components/ports/WorkspacePortScanner.tsx b/src/renderer/src/components/ports/WorkspacePortScanner.tsx
index 2b12bd86cd8..f8f2d11ee40 100644
--- a/src/renderer/src/components/ports/WorkspacePortScanner.tsx
+++ b/src/renderer/src/components/ports/WorkspacePortScanner.tsx
@@ -280,6 +280,7 @@ export function WorkspacePortScanner({ enabled = true }: { enabled?: boolean }):
return
}
+ let burstRefresh: Promise | null = null
let eventSequence = 0
let disposed = false
let retryTimer: ReturnType | null = null
@@ -296,15 +297,24 @@ export function WorkspacePortScanner({ enabled = true }: { enabled?: boolean }):
const sequence = eventSequence
clearRetryTimer()
if (!isWindowVisible()) {
+ burstRefresh = null
return
}
- void refresh({ force: true, targets: [runtimeTarget] }).finally(() => {
- if (disposed || sequence !== eventSequence || !isWindowVisible()) {
+ // Keep the leading scan through the quiet window so sequential events share it too.
+ burstRefresh ??= refresh({ force: true, targets: [runtimeTarget] })
+ void burstRefresh.finally(() => {
+ if (disposed || sequence !== eventSequence) {
+ return
+ }
+ if (!isWindowVisible()) {
+ burstRefresh = null
return
}
// Why: some dev servers print their URL just before the listener is
// visible to lsof/netstat. One quiet settle scan catches that startup race.
retryTimer = setTimeout(() => {
+ retryTimer = null
+ burstRefresh = null
if (disposed || sequence !== eventSequence || !isWindowVisible()) {
return
}