diff --git a/src/renderer/src/lib/visible-overlay.test.ts b/src/renderer/src/lib/visible-overlay.test.ts
index b6cf072d2d9..4fa99e0be23 100644
--- a/src/renderer/src/lib/visible-overlay.test.ts
+++ b/src/renderer/src/lib/visible-overlay.test.ts
@@ -30,6 +30,16 @@ describe('hasVisibleOverlay', () => {
expect(hasVisibleOverlay()).toBe(false)
})
+ it('ignores the persistent workspace list while preserving its nested popups', () => {
+ mount('
')
+
+ expect(hasVisibleOverlay()).toBe(false)
+
+ mount('')
+
+ expect(hasVisibleOverlay()).toBe(true)
+ })
+
it('ignores a display:none overlay', () => {
mount('')
diff --git a/src/renderer/src/lib/visible-overlay.ts b/src/renderer/src/lib/visible-overlay.ts
index 19a8315cccf..44dc14a514a 100644
--- a/src/renderer/src/lib/visible-overlay.ts
+++ b/src/renderer/src/lib/visible-overlay.ts
@@ -1,4 +1,6 @@
-const OVERLAY_SELECTOR = '[role="dialog"], [role="alertdialog"], [role="listbox"], [role="menu"]'
+// The always-mounted worktree sidebar is page chrome, not an Escape-owning popup.
+const OVERLAY_SELECTOR =
+ '[role="dialog"], [role="alertdialog"], [role="listbox"]:not([data-worktree-sidebar]), [role="menu"]'
type VisibleOverlayOptions = {
/** Overlays inside a match are treated as page content, not as a layer above it. */