~bigbes/lethe

ref: ddd7c1f6065810c8bbdb86042bfe01923d63c554 lethe/docs/design_handoff_assistant_log/proto-search.jsx -rw-r--r-- 4.4 KiB
ddd7c1f6 — Eugene Blikh savedsearch: add /api/v1/saved-searches CRUD with 0002 migration a month ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// 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 });