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:
@@ -1,6 +1,7 @@
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { Maximize2, Minimize2, RotateCw, GripVertical } from "lucide-react";
|
||||
import { TerminalView } from "./TerminalView";
|
||||
import { SearchBar } from "./SearchBar";
|
||||
import { StatusRing } from "./StatusRing";
|
||||
import { COLORS, FONT, STATE_COLOR } from "./theme";
|
||||
import type { LayoutNode, SurfaceState, SurfaceView } from "./layoutTypes";
|
||||
@@ -16,6 +17,11 @@ interface Props {
|
||||
focusedId: string | null;
|
||||
onFocus: (id: string) => void;
|
||||
zoomed: string | null;
|
||||
/** The surface whose scrollback search bar is open, or null. Anchored to the
|
||||
* panel it was opened on — it does NOT follow focus. */
|
||||
searchSurfaceId: string | null;
|
||||
searchNonce: number;
|
||||
onCloseSearch: () => void;
|
||||
}
|
||||
|
||||
type Edge = "left" | "right" | "top" | "bottom";
|
||||
@@ -34,7 +40,7 @@ function shortPath(cwd: string): string {
|
||||
return leaf ? `~/${leaf}` : cwd;
|
||||
}
|
||||
|
||||
export function LayoutEngine({ workspaceId, layout, running, states, surfaces, focusedId, onFocus, zoomed }: Props) {
|
||||
export function LayoutEngine({ workspaceId, layout, running, states, surfaces, focusedId, onFocus, zoomed, searchSurfaceId, searchNonce, onCloseSearch }: Props) {
|
||||
// Panel drag-to-reorder. Implemented with raw pointer events rather than the
|
||||
// HTML5 drag API, which is unreliable in the macOS WKWebView Tauri uses.
|
||||
const [drop, setDrop] = useState<DropTarget | null>(null);
|
||||
@@ -72,7 +78,7 @@ export function LayoutEngine({ workspaceId, layout, running, states, surfaces, f
|
||||
if (!layout) {
|
||||
return <div style={{ color: COLORS.textMuted, padding: 24 }}>Empty workspace — apply a preset to add panels.</div>;
|
||||
}
|
||||
const shared = { workspaceId, running, states, surfaces, focusedId, onFocus, zoomed, drop, onStartPanelDrag: startPanelDrag };
|
||||
const shared = { workspaceId, running, states, surfaces, focusedId, onFocus, zoomed, drop, onStartPanelDrag: startPanelDrag, searchSurfaceId, searchNonce, onCloseSearch };
|
||||
if (zoomed) {
|
||||
return (
|
||||
<div style={{ width: "100%", height: "100%", padding: 12, boxSizing: "border-box" }}>
|
||||
@@ -94,6 +100,9 @@ interface NodeProps {
|
||||
zoomed: string | null;
|
||||
drop: DropTarget | null;
|
||||
onStartPanelDrag: (srcId: string, e: React.MouseEvent) => void;
|
||||
searchSurfaceId: string | null;
|
||||
searchNonce: number;
|
||||
onCloseSearch: () => void;
|
||||
}
|
||||
|
||||
function Node({ node, path, ...rest }: NodeProps) {
|
||||
@@ -103,7 +112,7 @@ function Node({ node, path, ...rest }: NodeProps) {
|
||||
return <SplitView split={node.split} path={path} {...rest} />;
|
||||
}
|
||||
|
||||
function Leaf({ id, workspaceId, running, states, surfaces, focusedId, onFocus, zoomed, drop, onStartPanelDrag }: Omit<NodeProps, "node" | "path"> & { id: string }) {
|
||||
function Leaf({ id, workspaceId, running, states, surfaces, focusedId, onFocus, zoomed, drop, onStartPanelDrag, searchSurfaceId, searchNonce, onCloseSearch }: Omit<NodeProps, "node" | "path"> & { id: string }) {
|
||||
const focused = focusedId === id;
|
||||
const dropEdge = drop && drop.id === id ? drop.edge : null;
|
||||
|
||||
@@ -114,7 +123,11 @@ function Leaf({ id, workspaceId, running, states, surfaces, focusedId, onFocus,
|
||||
style={{
|
||||
position: "relative", display: "flex", flexDirection: "column", width: "100%", height: "100%",
|
||||
background: COLORS.bgPanel, borderRadius: 8, overflow: "hidden",
|
||||
border: focused ? `2px solid ${COLORS.accent}` : `1px solid ${COLORS.borderSubtle}`,
|
||||
// Constant 2px border, color-only on focus. A width change (1px<->2px)
|
||||
// would resize the inner content box, fire ResizeObserver -> fit -> PTY
|
||||
// SIGWINCH, making zsh/powerlevel10k reprint its prompt on every focus
|
||||
// switch (the "stacked prompts" bug).
|
||||
border: `2px solid ${focused ? COLORS.accent : COLORS.borderSubtle}`,
|
||||
boxSizing: "border-box",
|
||||
}}
|
||||
>
|
||||
@@ -170,6 +183,9 @@ function Leaf({ id, workspaceId, running, states, surfaces, focusedId, onFocus,
|
||||
<div style={{ flex: 1, minHeight: 0 }}>
|
||||
<TerminalView key={id} surfaceId={id} />
|
||||
</div>
|
||||
{searchSurfaceId === id && (
|
||||
<SearchBar surfaceId={id} reopenNonce={searchNonce} onClose={onCloseSearch} />
|
||||
)}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user