fix(app): working scrollback search + stop prompt duplication on focus

Search fixes:
- TerminalView sets allowProposedApi (the search addon's match decorations
  use registerMarker/registerDecoration); without it findNext threw before
  firing results, so the counter was stuck at 0/0.
- The search bar now renders inside the panel it targets (in the header)
  instead of a global top-right overlay, so it's obvious which panel is
  searched.
- Search is anchored to the panel it was opened on (searchSurfaceId) and no
  longer follows focus — opening it in one panel and switching away no longer
  shows it open elsewhere.

Prompt duplication:
- The focus border was 1px when unfocused, 2px when focused; with border-box
  that resized the content on every focus switch, firing ResizeObserver -> fit
  -> PTY SIGWINCH and making zsh/powerlevel10k reprint its prompt. The border
  is now a constant 2px, color-only on focus.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-14 08:34:43 +07:00
parent a30ec1cc7f
commit 04ac7cdec2
4 changed files with 43 additions and 18 deletions
+10 -6
View File
@@ -3,7 +3,6 @@ import { LayoutEngine } from "./LayoutEngine";
import { Sidebar } from "./Sidebar";
import { TopBar } from "./TopBar";
import { CenterToolbar } from "./CenterToolbar";
import { SearchBar } from "./SearchBar";
import { Wizard } from "./Wizard";
import { EventCenter } from "./EventCenter";
import { maybeNotify } from "./notify";
@@ -35,9 +34,10 @@ export function App() {
const [health, setHealth] = useState<DaemonHealth | null>(null);
const [connected, setConnected] = useState(false);
const [focusedId, setFocusedId] = useState<string | null>(null);
const [searchOpen, setSearchOpen] = useState(false);
const [searchSurfaceId, setSearchSurfaceId] = useState<string | null>(null);
const [searchNonce, setSearchNonce] = useState(0);
const activeRef = useRef<string | null>(null);
const effectiveFocusRef = useRef<string | null>(null);
const wsRef = useRef<WorkspaceView[]>([]);
activeRef.current = activeId;
wsRef.current = workspaces;
@@ -107,7 +107,11 @@ export function App() {
useEffect(() => {
const onKey = (e: KeyboardEvent) => {
if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "f") {
if (activeRef.current) { e.preventDefault(); setSearchOpen(true); setSearchNonce((n) => n + 1); }
if (activeRef.current && effectiveFocusRef.current) {
e.preventDefault();
setSearchSurfaceId(effectiveFocusRef.current); // anchor to the focused panel
setSearchNonce((n) => n + 1);
}
}
};
window.addEventListener("keydown", onKey);
@@ -121,6 +125,7 @@ export function App() {
const active = workspaces.find((w) => w.id === activeId) ?? null;
const leaves = active ? leafIds(active.layout) : [];
const effectiveFocus = focusedId && leaves.includes(focusedId) ? focusedId : leaves[0] ?? null;
effectiveFocusRef.current = effectiveFocus;
function selectWorkspace(id: string) {
setActiveId(id);
@@ -135,13 +140,12 @@ export function App() {
{sidebarOpen && <Sidebar groups={groups} workspaces={workspaces} activeId={activeId} onSelect={selectWorkspace} onNew={() => setWizard(true)} health={health} connected={connected} />}
<div style={{ flex: 1, display: "flex", flexDirection: "column", minWidth: 0 }}>
{active && (
<CenterToolbar selected="" onSelect={(p) => { if (active) void applyPreset(active.id, p, []); }} onOpenSearch={() => setSearchOpen(true)} />
<CenterToolbar selected="" onSelect={(p) => { if (active) void applyPreset(active.id, p, []); }} onOpenSearch={() => { if (effectiveFocus) { setSearchSurfaceId(effectiveFocus); setSearchNonce((n) => n + 1); } }} />
)}
<div style={{ flex: 1, minHeight: 0, position: "relative" }}>
{active
? <LayoutEngine workspaceId={active.id} layout={active.layout} running={running} states={states} surfaces={active.surfaces} focusedId={effectiveFocus} onFocus={setFocusedId} zoomed={active.zoomed} />
? <LayoutEngine workspaceId={active.id} layout={active.layout} running={running} states={states} surfaces={active.surfaces} focusedId={effectiveFocus} onFocus={setFocusedId} zoomed={active.zoomed} searchSurfaceId={searchSurfaceId} searchNonce={searchNonce} onCloseSearch={() => setSearchSurfaceId(null)} />
: <div style={{ color: COLORS.textMuted, padding: 24 }}>No workspace create one to begin.</div>}
{searchOpen && active && <SearchBar surfaceId={effectiveFocus} reopenNonce={searchNonce} onClose={() => setSearchOpen(false)} />}
</div>
</div>
{eventsOpen && (