fix(web): usable past-conversation list, and search that finds past sessions

Two home-screen reports from @jordan8037310, both about history that is
present but unreachable.

#260 — "Resume Conversation" rendered 4 rows, then a button that appended
every remaining row into a `max-height: 240px` box, so 35 conversations
landed in a four-row scroll well with no ordering or filtering. Rendering
now goes through `_renderHistoryList()` over a cached corpus: 10 rows to
start, Show more/Show less that grows and shrinks the box (the height cap
is class-driven, `.history-list.expanded`), plus a filter box (name,
folder, #case label, prompts), a sort control (recent / name / folder,
pinned rows still first) and a shown-of-total count. A filter implies
expansion, so every match is visible, and the whole header hides as one
unit while a federated search is active. The A-Z sort keys off the same
string the row renders, since most rows are transcript-backed and carry
no session name at all.

#261 — the search box could not match a past project by folder name:
`harvestSources()` built its session corpus from the live in-memory map,
while past sessions come from `/api/sessions/unified` (lifecycle log +
transcript scan). Folding that scan into the request path would have cost
the search its no-filesystem-reads property, so the corpus arrives via a
bounded snapshot instead: `session-history-index.ts` is published as a
side effect of `/api/sessions/unified` (the home screen fetches it on
open, which is the same screen the search box lives on) and rebuilt
fire-and-forget, single-flight and TTL-guarded when a search finds it
stale. A result for a closed session now resumes the conversation rather
than selecting a tab that no longer exists, and is badged RESUME.

The snapshot is stored unscoped with a per-row owner and re-filtered
through canAccessOwned() on read, so multi-user sees exactly what
/api/sessions/unified exposes: own sessions only, host-wide transcript
history admin-only. Live rows are harvested first and win the dedupe.

Verified end-to-end against a real instance with 60 past sessions: cold
process answers its first search without history and its second with it;
folder-name queries return resume targets; clicking one posts the right
resumeSessionId + workingDir.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Codeman maintainer
2026-08-10 03:02:11 +02:00
parent c942bb5dfb
commit 5d42f64393
15 changed files with 1206 additions and 54 deletions
@@ -0,0 +1,24 @@
---
'aicodeman': patch
---
Home screen: make the past-conversation list usable, and let search find past sessions.
- **#260** — "Resume Conversation" showed 4 rows and then dumped every remaining
one into a fixed 240px box, with no ordering or filtering. The list now opens
with 10 rows, "Show more"/"Show less" grows and shrinks the box itself (the
height cap is class-driven instead of fixed), and the header carries a filter
box (matches name, folder, `#case` label and the conversation's prompts), a
sort control (recent / name A–Z / folder A–Z, pinned rows still first) and a
shown-of-total count. Filtering implies expansion, so every match is visible.
- **#261** — the search box could not match a past project by folder name: its
session corpus was the live in-memory map, while past sessions come from
`/api/sessions/unified`. Search now also harvests a bounded snapshot of that
unified list, refreshed OUTSIDE the request path (published by
`/api/sessions/unified`, plus a fire-and-forget rebuild when stale), so the
search path keeps its no-filesystem-reads property. Results for a closed
session resume the conversation instead of trying to select a tab that no
longer exists, and are badged `RESUME`. In multi-user mode the snapshot is
re-scoped per row on read, matching what `/api/sessions/unified` exposes.
Reported by @jordan8037310.