// Search results — flat turn-level results with FTS marks
const SearchScreen = () => {
const { query, go } = useRouter();
const q = (query || '').trim();
// Fake search corpus — turns with snippets that match the query
const corpus = [
{ ts: '04-23 10:54', cwd: '~/work/scarlet-svc', n: 18, tool: 'claude-code', sid: 'a3', text: "use jittered exp backoff with 30s cap, but always read Retry-After from server", hits: ['backoff', 'Retry-After', 'retry'] },
{ ts: '04-23 10:52', cwd: '~/work/scarlet-svc', n: 14, tool: 'claude-code', sid: 'a3', text: "tolerate 429s without sleep — retry should respect Retry-After", hits: ['429', 'retry', 'Retry-After'] },
{ ts: '04-19 16:30', cwd: '~/code/tt-bundle', n: 8, tool: 'claude-code', sid: 'a1', text: "rate limited; we retry once at the call site, anything beyond is real 429", hits: ['retry', '429'] },
{ ts: '04-15 09:11', cwd: '~/work/atelier', n: 3, tool: 'opencode', sid: 'a2', text: "queue worker hits 429 bursts when upstream cache misses", hits: ['429'] },
{ ts: '04-12 14:02', cwd: '~/work/scarlet-svc', n: 22, tool: 'claude-code', sid: 'a3', text: "the backoff library you're using has Decorrelated Jitter built in", hits: ['backoff'] },
{ ts: '04-09 11:45', cwd: '~/code/tt-bundle', n: 12, tool: 'claude-code', sid: 'a1', text: "for the retry loop in the resolver, bump to 4 attempts but cap at 30s", hits: ['retry'] },
{ ts: '04-04 17:20', cwd: '~/code/tt-bundle', n: 5, tool: 'claude-code', sid: 'a6', text: "bumping a transitive dep should not invalidate every output", hits: ['transitive'] },
{ ts: '04-02 10:08', cwd: '~/code/tt-bundle', n: 19, tool: 'claude-code', sid: 'a1', text: "lockfile entries store the resolved transitive tree, not declared", hits: ['lockfile', 'transitive'] },
];
const lc = q.toLowerCase();
const results = !q
? []
: corpus.filter(r => r.text.toLowerCase().includes(lc) || r.hits.some(h => h.toLowerCase().includes(lc)));
const renderSnippet = (text, hits) => {
if (!q) return text;
// Build a regex from query + hit list
const terms = [q, ...hits].filter(Boolean).map(t => t.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
const re = new RegExp(`(${terms.join('|')})`, 'gi');
const parts = text.split(re);
return parts.map((p, i) =>
re.test(p) ? <mark key={i} className="fts">{p}</mark> : <span key={i}>{p}</span>
);
};
const cols = '110px 110px 200px 50px 1fr';
const sids = new Set(results.map(r => r.sid));
return (
<>
<div className="subbar" style={{ flexWrap: 'wrap' }}>
<span className="mono" style={{ fontSize: 12, fontWeight: 600 }}>"{q || '(empty)'}"</span>
<span className="muted mono">→ {results.length} turns / {sids.size} sessions / 0.012s</span>
<span style={{ flex: 1 }} />
<FilterChip dim="tool" value="claude-code" onChange={() => {}} onRemove={() => {}} />
<FilterChip dim="host" value="workpc" onChange={() => {}} onRemove={() => {}} />
<FilterChip dim="since" value="7d" onChange={() => {}} onRemove={() => {}} />
<span className="tag dashed click">★ save</span>
</div>
<div className="thead" style={{ gridTemplateColumns: cols }}>
<span>ts</span><span>tool</span><span>cwd</span><span className="right">#</span><span>snippet</span>
</div>
<div className="body">
{!q && (
<Empty glyph="⌕" title="Type a query in ⌘K to search every turn."
hint="full-text across all tools, hosts, and projects" />
)}
{q && results.length === 0 && (
<Empty glyph="∅" title={`No turns match "${q}".`}
hint="try a shorter query or remove a filter" />
)}
{results.map((r, i) => (
<div key={i} className="row"
style={{ gridTemplateColumns: cols }}
onClick={() => go({ name: 'session', id: r.sid })}>
<span className="mono muted">{r.ts}</span>
<span><ToolTag tool={r.tool} /></span>
<span className="mono muted truncate" style={{ cursor: 'pointer' }}
onClick={(e) => { e.stopPropagation(); go({ name: 'project', cwd: r.cwd }); }}>{r.cwd}</span>
<span className="right mono muted">#{r.n}</span>
<span className="truncate">{renderSnippet(r.text, r.hits)}</span>
</div>
))}
</div>
</>
);
};
Object.assign(window, { SearchScreen });