From 4e8e991d61e975620c8b94e049fe2e8f159a21ac Mon Sep 17 00:00:00 2001 From: David Metcalfe <80915+DavidMetcalfe@users.noreply.github.com> Date: Mon, 27 Jul 2026 15:09:22 -0700 Subject: [PATCH 1/6] fix(desktop): find bar positioning, overlay guard, and keybind gate Three fixes for the find-in-page bar (Ctrl+F): 1. Position: replace fixed right-4 with calc() that accounts for the titlebar tool cluster width, preventing visual overlap with the layout/haptics/keybinds/settings icons. Uses existing CSS vars from wiring.tsx (--titlebar-tools-right, --titlebar-tools-width). right-[calc(var(--titlebar-tools-right,0.75rem)+var(--titlebar-tools-width,0px)+0.5rem)] 2. Overlay guard: hide the find bar on full-screen overlay routes (agents, command-center, cron, profiles, settings, starmap, webhooks), matching the titlebar controls pattern. Prevents the bar from rendering behind overlays at z-50 and avoids collision with overlay-specific search surfaces (e.g. Settings search #69025). 3. Keybind gate: suppress the view.findInPage keybind on overlay routes so Ctrl+F doesn't mutate store state with no visible effect. Defense-in-depth alongside the component guard. Adds regression test asserting the find bar does not render on /settings. --- apps/desktop/src/app/hooks/use-keybinds.ts | 12 ++++++++++-- apps/desktop/src/components/find-bar.test.tsx | 13 +++++++++++++ apps/desktop/src/components/find-bar.tsx | 3 ++- 3 files changed, 25 insertions(+), 3 deletions(-) diff --git a/apps/desktop/src/app/hooks/use-keybinds.ts b/apps/desktop/src/app/hooks/use-keybinds.ts index 6d8f5a430e..7ce40d2f42 100644 --- a/apps/desktop/src/app/hooks/use-keybinds.ts +++ b/apps/desktop/src/app/hooks/use-keybinds.ts @@ -1,6 +1,7 @@ import { useEffect, useRef } from 'react' -import { useNavigate } from 'react-router' +import { useLocation, useNavigate } from 'react-router' +import { appViewForPath, isOverlayView } from '@/app/routes' import { closeActiveTab } from '@/app/chat/close-tab' import { hudTargetSessionId } from '@/app/hud/handoff' import { setTerminalTakeover } from '@/app/right-sidebar/store' @@ -98,6 +99,7 @@ type HandlerMap = Record void> // mode is active (edit overlay / panel rebind) — records the pressed combo. export function useKeybinds(deps: KeybindRuntimeDeps): void { const navigate = useNavigate() + const location = useLocation() const { resolvedMode, setMode } = useTheme() // Keep the latest closures without re-subscribing the listener. @@ -256,7 +258,13 @@ export function useKeybinds(deps: KeybindRuntimeDeps): void { // the Win/Linux path where ⌘W reaches the renderer directly. 'view.closeTab': () => void closeActiveTab(id => navigate(sessionRoute(id))), 'view.reopenTab': reopenLastClosedTile, - 'view.findInPage': openFindBar, + 'view.findInPage': () => { + // Suppress on overlay routes so it doesn't collide with overlay-specific + // search surfaces (e.g. Settings search bar). + if (!isOverlayView(appViewForPath(location.pathname))) { + openFindBar() + } + }, // ⌘G / ⌘⇧G are handled by the find bar's own capture-phase listener while // it is open (so they don't collide with `view.toggleReview`). These // registry handlers cover a user-assigned dedicated chord: stepping is a diff --git a/apps/desktop/src/components/find-bar.test.tsx b/apps/desktop/src/components/find-bar.test.tsx index 625aa38d06..3d2f148460 100644 --- a/apps/desktop/src/components/find-bar.test.tsx +++ b/apps/desktop/src/components/find-bar.test.tsx @@ -609,4 +609,17 @@ describe('FindBar', () => { await waitFor(() => expect(screen.queryByRole('search')).toBeNull()) expect(bridge.stopFindInPage).not.toHaveBeenCalled() }) + + it('does not render on overlay routes (settings, command center, …)', () => { + // Match isOverlayView: agents, command-center, cron, profiles, settings, + // starmap, webhooks. Test with the most commonly hit one. + openFindBar() + renderFindBar('/settings') + + expect(screen.queryByRole('search')).toBeNull() + // The store still has active=true but the component guard suppresses + // rendering — a harmless state ghost that the next pathname-change + // cleanup will drain via closeFindBar. + expect($findInPage.get().active).toBe(true) + }) }) diff --git a/apps/desktop/src/components/find-bar.tsx b/apps/desktop/src/components/find-bar.tsx index 40888049e5..75a284ba0e 100644 --- a/apps/desktop/src/components/find-bar.tsx +++ b/apps/desktop/src/components/find-bar.tsx @@ -2,6 +2,7 @@ import { useStore } from '@nanostores/react' import { useEffect, useRef, useState } from 'react' import { useLocation } from 'react-router' +import { appViewForPath, isOverlayView } from '@/app/routes' import { Tip } from '@/components/ui/tooltip' import { useI18n } from '@/i18n' import { findBarKeyAction, formatMatchLabel } from '@/lib/find-in-page' @@ -121,7 +122,7 @@ export function FindBar() { return () => window.removeEventListener('keydown', onKeyDown, { capture: true }) }, [active]) - if (!active) { + if (!active || isOverlayView(appViewForPath(pathname))) { return null } From a1b24c52f7311c822bd19b14930dbc2bcee07b1c Mon Sep 17 00:00:00 2001 From: David Metcalfe <80915+DavidMetcalfe@users.noreply.github.com> Date: Tue, 4 Aug 2026 23:02:56 -0700 Subject: [PATCH 2/6] fix(desktop): count the right-sidebar toggle in the titlebar tools width --- apps/desktop/src/app/contrib/wiring.tsx | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/app/contrib/wiring.tsx b/apps/desktop/src/app/contrib/wiring.tsx index ab5ecaafa4..d2a6ead87a 100644 --- a/apps/desktop/src/app/contrib/wiring.tsx +++ b/apps/desktop/src/app/contrib/wiring.tsx @@ -999,7 +999,12 @@ export function ContribWiring({ children }: { children: ReactNode }) { // Pane-registered tools (preview's monitor/devtools cluster) anchor flush // against the static system cluster — in the tree layout the titlebar band // sits ABOVE the grid, so AppShell's pane-width anchoring doesn't apply. - const SYSTEM_TOOL_COUNT = 4 + // Count every button the static cluster actually renders: four systemTools + // (layout, haptics, keybinds, settings) PLUS the always-present + // right-sidebar toggle (see titlebar-controls.tsx). A shared width that + // under-counts leaves the find bar, the titlebar header padding, and the + // pane-cluster anchor overlapping the fifth button. + const SYSTEM_TOOL_COUNT = 5 const paneToolCount = rightTitlebarTools.filter(tool => !tool.hidden).length const systemToolsWidth = titlebarToolsWidthCss(SYSTEM_TOOL_COUNT) From d0ba405ab77c5adbc421753c46b5d716a468ca01 Mon Sep 17 00:00:00 2001 From: David Metcalfe <80915+DavidMetcalfe@users.noreply.github.com> Date: Tue, 4 Aug 2026 23:02:56 -0700 Subject: [PATCH 3/6] test(desktop): cover the view.findInPage overlay keybind gate --- apps/desktop/src/components/find-bar.test.tsx | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/apps/desktop/src/components/find-bar.test.tsx b/apps/desktop/src/components/find-bar.test.tsx index 3d2f148460..a5930284d4 100644 --- a/apps/desktop/src/components/find-bar.test.tsx +++ b/apps/desktop/src/components/find-bar.test.tsx @@ -6,6 +6,7 @@ import { FindBar } from '@/components/find-bar' import { I18nProvider } from '@/i18n' import { en } from '@/i18n/en' import { zh } from '@/i18n/zh' +import { useKeybinds, type KeybindRuntimeDeps } from '@/app/hooks/use-keybinds' import { findBarClaimsCombo, findBarKeyAction, formatMatchLabel } from '@/lib/find-in-page' import { KEYBIND_ACTIONS } from '@/lib/keybinds/actions' import { actionAllowedInInput } from '@/lib/keybinds/combo' @@ -22,6 +23,13 @@ import { updateFindResults } from '@/store/find-in-page' +// useKeybinds only needs the theme context for resolvedMode/setMode; the real +// provider persists themes and subscribes to backend sync, which is unrelated +// to the find-in-page gate under test. +vi.mock('@/themes/context', () => ({ + useTheme: () => ({ resolvedMode: 'dark', setMode: vi.fn() }) +})) + // ── Bridge double ─────────────────────────────────────────────────────────── // Stands in for the preload `hermesDesktop` surface. `onFoundInPage` records // its subscribers so the tests can assert the listener refcount and drive @@ -623,3 +631,52 @@ describe('FindBar', () => { expect($findInPage.get().active).toBe(true) }) }) + +// ── Keybind gate: view.findInPage on overlay routes ───────────────────────── +// The component guard above proves hidden RENDERING; this proves the keybind +// itself never OPENS the bar on an overlay route. Mounted against the real +// useKeybinds listener + combo index so a regression in either the handler +// wiring or the route classification fails the test. + +function KeybindHarness({ deps }: { deps: KeybindRuntimeDeps }) { + useKeybinds(deps) + + return null +} + +describe('view.findInPage keybind gate', () => { + function renderKeybinds(pathname: string) { + const deps: KeybindRuntimeDeps = { + toggleCommandCenter: vi.fn(), + startFreshSession: vi.fn(), + openNewSessionTab: vi.fn(), + toggleSelectedPin: vi.fn() + } + + render( + + + + + + ) + + return deps + } + + it('does not open the find bar for mod+f while an overlay route is showing', () => { + renderKeybinds('/settings') + + fireEvent.keyDown(window, { key: 'f', metaKey: true }) + + expect($findInPage.get().active).toBe(false) + }) + + it('opens the find bar for mod+f on a normal chat route', () => { + renderKeybinds('/session/a') + + fireEvent.keyDown(window, { key: 'f', metaKey: true }) + + expect($findInPage.get().active).toBe(true) + }) +}) From 16a1ad6ef483c51754131e7f50148e5e813a10cf Mon Sep 17 00:00:00 2001 From: David Metcalfe <80915+DavidMetcalfe@users.noreply.github.com> Date: Tue, 4 Aug 2026 23:06:21 -0700 Subject: [PATCH 4/6] fix(desktop): count the right-sidebar toggle in the controller's titlebar slot too --- apps/desktop/src/app/contrib/controller.tsx | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/app/contrib/controller.tsx b/apps/desktop/src/app/contrib/controller.tsx index a3be11e558..88b8ae7513 100644 --- a/apps/desktop/src/app/contrib/controller.tsx +++ b/apps/desktop/src/app/contrib/controller.tsx @@ -776,7 +776,10 @@ export function ContribController() { className="pointer-events-auto absolute z-10 flex w-max items-center gap-2 [-webkit-app-region:no-drag]" style={{ right: - 'max(calc(var(--workspace-right, 0px) + 0.5rem), calc(var(--titlebar-tools-right, 0.75rem) + 4 * var(--titlebar-control-size, 24px) + 0.5rem))' + // Five static cluster buttons: four systemTools plus the + // always-present right-sidebar toggle (titlebar-controls.tsx). + // Keep in sync with wiring.tsx's SYSTEM_TOOL_COUNT. + 'max(calc(var(--workspace-right, 0px) + 0.5rem), calc(var(--titlebar-tools-right, 0.75rem) + 5 * var(--titlebar-control-size, 24px) + 0.5rem))' }} /> From 903250772fc5c0f5abf52599c1a09137019b0cb7 Mon Sep 17 00:00:00 2001 From: David Metcalfe <80915+DavidMetcalfe@users.noreply.github.com> Date: Thu, 6 Aug 2026 11:32:05 -0700 Subject: [PATCH 5/6] style(desktop): satisfy perfectionist import ordering in find-bar PR files - use-keybinds.ts: sort @/app/routes after @/app/chat/close-tab and the right-sidebar imports (natural-asc) - find-bar.test.tsx: sort @/app/hooks/use-keybinds before @/components and @/i18n imports; KeybindRuntimeDeps before useKeybinds in named imports --- apps/desktop/src/app/hooks/use-keybinds.ts | 2 +- apps/desktop/src/components/find-bar.test.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/apps/desktop/src/app/hooks/use-keybinds.ts b/apps/desktop/src/app/hooks/use-keybinds.ts index 7ce40d2f42..919da805ec 100644 --- a/apps/desktop/src/app/hooks/use-keybinds.ts +++ b/apps/desktop/src/app/hooks/use-keybinds.ts @@ -1,11 +1,11 @@ import { useEffect, useRef } from 'react' import { useLocation, useNavigate } from 'react-router' -import { appViewForPath, isOverlayView } from '@/app/routes' import { closeActiveTab } from '@/app/chat/close-tab' import { hudTargetSessionId } from '@/app/hud/handoff' import { setTerminalTakeover } from '@/app/right-sidebar/store' import { closeActiveTerminal, createTerminal, cycleTerminal } from '@/app/right-sidebar/terminal/terminals' +import { appViewForPath, isOverlayView } from '@/app/routes' import { activateTreeTabSlot, cycleTreeTabInFocusedZone, diff --git a/apps/desktop/src/components/find-bar.test.tsx b/apps/desktop/src/components/find-bar.test.tsx index a5930284d4..e127ee98d1 100644 --- a/apps/desktop/src/components/find-bar.test.tsx +++ b/apps/desktop/src/components/find-bar.test.tsx @@ -2,11 +2,11 @@ import { act, cleanup, fireEvent, render, screen, waitFor } from '@testing-libra import { MemoryRouter, useNavigate } from 'react-router' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +import { type KeybindRuntimeDeps, useKeybinds } from '@/app/hooks/use-keybinds' import { FindBar } from '@/components/find-bar' import { I18nProvider } from '@/i18n' import { en } from '@/i18n/en' import { zh } from '@/i18n/zh' -import { useKeybinds, type KeybindRuntimeDeps } from '@/app/hooks/use-keybinds' import { findBarClaimsCombo, findBarKeyAction, formatMatchLabel } from '@/lib/find-in-page' import { KEYBIND_ACTIONS } from '@/lib/keybinds/actions' import { actionAllowedInInput } from '@/lib/keybinds/combo' From 001e2b0b85c7f1f07ca2f50be3d7b1900a0897a6 Mon Sep 17 00:00:00 2001 From: Teknium <127238744+teknium1@users.noreply.github.com> Date: Sat, 15 Aug 2026 01:56:20 -0700 Subject: [PATCH 6/6] test(desktop): add archiveSelectedSession to keybind harness deps KeybindRuntimeDeps grew archiveSelectedSession on main after #72959 was opened; the salvaged keybind-gate harness needs the new field to typecheck. --- apps/desktop/src/components/find-bar.test.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/apps/desktop/src/components/find-bar.test.tsx b/apps/desktop/src/components/find-bar.test.tsx index e127ee98d1..02448cac78 100644 --- a/apps/desktop/src/components/find-bar.test.tsx +++ b/apps/desktop/src/components/find-bar.test.tsx @@ -650,7 +650,8 @@ describe('view.findInPage keybind gate', () => { toggleCommandCenter: vi.fn(), startFreshSession: vi.fn(), openNewSessionTab: vi.fn(), - toggleSelectedPin: vi.fn() + toggleSelectedPin: vi.fn(), + archiveSelectedSession: vi.fn() } render(