refactor: codebase audit cleanup — dead code, dedup, module splits
Multi-agent audit + aggressive cleanup across server/web/coder/booterm, delivered behind a DEFER discipline so none of the in-flight files were touched. Removes dead code/deps/columns, dedups server + coder helpers, and splits the oversized modules (tools.ts, opencode-server.ts, sentinel-summaries, turn.ts, TerminalPane.tsx) behind stable contracts. Adds 78 parity/unit tests (server 587, coder 323); fixes two latent bugs (ChatPane queue keys, FileViewerOverlay blank-line parity). Intended tag: v2.7.12-audit-cleanup.
This commit is contained in:
163
apps/web/src/components/panes/terminal/SearchBar.tsx
Normal file
163
apps/web/src/components/panes/terminal/SearchBar.tsx
Normal file
@@ -0,0 +1,163 @@
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import type { SearchAddon } from '@xterm/addon-search';
|
||||
import { ChevronDown, ChevronUp, X } from 'lucide-react';
|
||||
import { type TermTheme } from './theme';
|
||||
|
||||
// ============================================================
|
||||
// SearchBar — kept from v1.10.4
|
||||
// ============================================================
|
||||
interface SearchBarProps {
|
||||
searchRef: React.MutableRefObject<SearchAddon | null>;
|
||||
theme: TermTheme;
|
||||
onClose: () => void;
|
||||
}
|
||||
export function SearchBar({ searchRef, theme, onClose }: SearchBarProps) {
|
||||
const [q, setQ] = useState('');
|
||||
const [counts, setCounts] = useState<{ idx: number; total: number }>({ idx: -1, total: 0 });
|
||||
const inputRef = useRef<HTMLInputElement | null>(null);
|
||||
useEffect(() => {
|
||||
inputRef.current?.focus();
|
||||
}, []);
|
||||
useEffect(() => {
|
||||
const addon = searchRef.current;
|
||||
if (!addon) return;
|
||||
const sub = addon.onDidChangeResults(({ resultIndex, resultCount }) => {
|
||||
setCounts({ idx: resultIndex, total: resultCount });
|
||||
});
|
||||
return () => sub.dispose();
|
||||
}, [searchRef]);
|
||||
useEffect(() => {
|
||||
const addon = searchRef.current;
|
||||
if (!addon) return;
|
||||
if (q.length === 0) {
|
||||
addon.clearDecorations?.();
|
||||
setCounts({ idx: -1, total: 0 });
|
||||
return;
|
||||
}
|
||||
addon.findNext(q, {
|
||||
incremental: true,
|
||||
decorations: {
|
||||
matchBackground: theme.selectionBackground,
|
||||
matchOverviewRuler: theme.cursor,
|
||||
activeMatchBackground: theme.cursor,
|
||||
activeMatchColorOverviewRuler: theme.cursor,
|
||||
},
|
||||
});
|
||||
}, [q, searchRef, theme]);
|
||||
|
||||
function findNext(): void {
|
||||
if (!q) return;
|
||||
searchRef.current?.findNext(q);
|
||||
}
|
||||
function findPrev(): void {
|
||||
if (!q) return;
|
||||
searchRef.current?.findPrevious(q);
|
||||
}
|
||||
function onKey(ev: React.KeyboardEvent<HTMLInputElement>): void {
|
||||
if (ev.key === 'Escape') {
|
||||
ev.preventDefault();
|
||||
onClose();
|
||||
return;
|
||||
}
|
||||
if (ev.key === 'Enter') {
|
||||
ev.preventDefault();
|
||||
if (ev.shiftKey) findPrev();
|
||||
else findNext();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
position: 'absolute',
|
||||
top: 8,
|
||||
right: 8,
|
||||
background: '#1a1d24',
|
||||
border: '1px solid #2a2d34',
|
||||
borderRadius: 8,
|
||||
padding: 4,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 4,
|
||||
boxShadow: '0 4px 12px rgba(0,0,0,0.4)',
|
||||
zIndex: 40,
|
||||
}}
|
||||
>
|
||||
<input
|
||||
ref={inputRef}
|
||||
value={q}
|
||||
onChange={(ev) => setQ(ev.target.value)}
|
||||
onKeyDown={onKey}
|
||||
placeholder="Search…"
|
||||
style={{
|
||||
background: 'transparent',
|
||||
border: 0,
|
||||
outline: 'none',
|
||||
color: '#d6deeb',
|
||||
padding: '8px 8px',
|
||||
fontSize: 13,
|
||||
width: 160,
|
||||
minHeight: 36,
|
||||
}}
|
||||
/>
|
||||
{q.length > 0 && (
|
||||
<span
|
||||
style={{
|
||||
fontSize: 11,
|
||||
color: counts.total === 0 ? '#ef5350' : '#575656',
|
||||
minWidth: 56,
|
||||
textAlign: 'right',
|
||||
padding: '0 4px',
|
||||
whiteSpace: 'nowrap',
|
||||
}}
|
||||
>
|
||||
{counts.total === 0
|
||||
? 'No match'
|
||||
: counts.idx === -1
|
||||
? `${counts.total}+`
|
||||
: `${counts.idx + 1} of ${counts.total}`}
|
||||
</span>
|
||||
)}
|
||||
<button
|
||||
type="button"
|
||||
onClick={findPrev}
|
||||
aria-label="Previous match"
|
||||
title="Previous (Shift+Enter)"
|
||||
style={iconBtnStyle}
|
||||
>
|
||||
<ChevronUp size={16} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={findNext}
|
||||
aria-label="Next match"
|
||||
title="Next (Enter)"
|
||||
style={iconBtnStyle}
|
||||
>
|
||||
<ChevronDown size={16} />
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
onClick={onClose}
|
||||
aria-label="Close search"
|
||||
title="Close (Esc)"
|
||||
style={iconBtnStyle}
|
||||
>
|
||||
<X size={16} />
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
const iconBtnStyle: React.CSSProperties = {
|
||||
display: 'inline-flex',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
width: 44,
|
||||
height: 44,
|
||||
background: 'transparent',
|
||||
border: 0,
|
||||
color: '#d6deeb',
|
||||
cursor: 'pointer',
|
||||
borderRadius: 6,
|
||||
};
|
||||
Reference in New Issue
Block a user