Skip to content

History browser: list, filter and navigate past sessions already recorded in output.db #135

Description

@AThraen

This was generated by AI during triage.

Summary

CSM already records every session that has ever exited, and every line those sessions printed. There is no way to look at any of it. Add a history browser: list past sessions, filter them, open one to read its output, and relaunch it.

The archive already exists — it is just write-mostly

Two tables in output.db hold this today:

Table Contents Written by
session_history one row per exited session: session_id, session_name, working_folder, command, args, group_id, exited_at, snapshot_json on session exit
session_output every ANSI-stripped output line: session_id, session_name, ts, line, mirrored into the output_fts FTS5 index OutputIndexer, continuously

session_history has indexes on session_id and working_folder, and snapshot_json carries Kind plus the SSH/WSL fields — enough to relaunch an entry as the right kind of session.

But nothing lists it. SearchService exposes exactly two reads of session_history, and both are keyed point lookups: GetSessionHistoryAsync(sessionId) and GetLatestSessionHistoryForFolderAsync(folderPath). There is no query that returns "the past sessions" in any order, filtered by anything. The table is only reachable if you already know which row you want.

The output side has the mirror-image gap: SearchAsync returns FTS5 snippets matching a query term. There is no way to read a past session's output as contiguous ordered lines — no WHERE session_id = ? ORDER BY ts path exists. So even having found an old session, you cannot read it; you can only search inside it for something you already suspect is there.

So both halves are missing, and both are small: the data, the schema and the indexes are already in place.

Desired behavior

  • A list of past sessions, newest first, showing name, working folder, command, when it exited, and its group.
  • Filters over that list: by working folder / repo, by command (e.g. only claude sessions), by group, by date range, and free-text over the session name. Folder and session-id already have indexes; the others need considering.
  • Open an entry to read its output as an ordered, scrollable transcript — not a snippet list.
  • Search within a single past session, distinct from today's global search.
  • Relaunch an entry, via the snapshot_json that already exists for this purpose.
  • Delete an entry and its output. DeleteSessionLogsAsync(sessionId) already does the output half.

The retention collision — the part worth deciding first

session_history rows and session_output lines have completely independent lifetimes, and nothing reconciles them:

  • PruneOldOutputAsync runs at startup and executes DELETE FROM session_output WHERE ts < $cutoff. It does not touch session_history.
  • So with the default 30-day retention, a session_history row from two months ago still exists, and its transcript does not. A history browser built naively would list it, the user would click it, and it would open empty.

This is not hypothetical: #124 reports output.db at 1.32 GB in live use and proposes size-capped FIFO eviction, which would make transcripts disappear faster and on a schedule the user cannot predict from a date.

So the browser has to treat "this session existed" and "its transcript is still retained" as two different facts, and show the difference honestly — a listed entry whose output has been evicted should say so, not present as a session with no output. Options worth weighing:

  • Show retention state per entry (transcript retained / evicted), derived from whether any session_output rows remain for that session_id.
  • Prune session_history on the same policy, so the list only ever shows what is readable — simpler, but throws away the cheap metadata (and the relaunch snapshot) along with the expensive lines.
  • Keep history forever, evict output, and make "relaunch" still work on evicted entries — the snapshot is tiny and does not depend on the transcript.

The third is probably right: session_history is small and useful on its own; session_output is what actually costs 1.3 GB. But this should be an explicit decision rather than a side effect, and it interacts directly with #124.

Two overlapping "past sessions" concepts

Worth resolving in the UI rather than shipping a third list:

  • Recently closed — a ring buffer capped at 10, newest-first, in state.json, surfaced at the top of the New Session dialog and via Ctrl+Shift+T. Purpose: undo a close.
  • session_history — unbounded, in the DB, not surfaced anywhere. Purpose: the archive.

They overlap for the last 10 closes and diverge after that. The browser should either present a single coherent "past sessions" view that the ring is the recent head of, or make the distinction visible. Silently having two different answers to "what did I close?" is the outcome to avoid.

Key interfaces

  • SearchService owns the schema and every query, and every use of the shared connection must be serialized through DbGate (using var _ = await DbGate.AcquireAsync();) — the connection is handed to SearchService and every OutputIndexer and is not thread-safe. New queries here are the main work:
    • a listing query over session_history with filter predicates and paging — must page, since this table is unbounded by design;
    • an ordered transcript read over session_output for one session_id, also paged (a long Claude session is a lot of lines, and loading it all into a WPF control will not go well);
    • a scoped FTS query — output_fts MATCH ? restricted to one session_id — for search-within-session.
  • SessionHistoryEntry — the existing row model; may need a retention flag if the browser surfaces eviction state.
  • snapshot_json → relaunch. ReopenClosedSessionAsync is the existing precedent for rebuilding a session from a snapshot, including giving it a fresh Id so it is independent of anything still referencing the old one. Relaunch from history should reuse that behaviour, not invent a second one.
  • Where the UI lives. Claude history analytics + indexing across all sessions #66 already proposes a "History" / "Insights" panel for Claude transcript analytics. These should be one surface, not two panels both called history. Claude history analytics + indexing across all sessions #66 mines ~/.claude/projects/*.jsonl (Claude's own transcripts); this issue reads CSM's own session_output. Different sources, same question from the user's side — "what happened in my past sessions?"

Acceptance criteria

  • A list of past sessions is reachable from the UI, newest first, paged.
  • Filters by working folder, command, group and date range each narrow the list correctly, and combine.
  • Opening an entry shows its output as ordered contiguous lines, not FTS snippets, loaded incrementally rather than all at once.
  • Search within a single past session returns matches scoped to that session only.
  • An entry whose output has been pruned or evicted is shown as such, and does not present as an empty session.
  • Relaunching an entry produces a working session of the correct Kind (Local / SSH / WSL) with a fresh Id.
  • Deleting an entry removes both its history row and its output rows.
  • Every new query goes through DbGate, and none of them run on the UI thread.
  • The relationship to the recently-closed ring is coherent — no two lists giving different answers without explanation.

Out of scope

Related

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions