session-title already registers a title projection unit; /resume now
reads it instead of scanning logs: live rows from the registry
snapshot, persisted rows from the durable checkpoint row
(cachedSnapshot, zero I/O), and only rows without a usable checkpoint
pay a coldSnapshot — checkpoint plus readFrom tail, written back so the
next scan is metadata-only. Cold reads are bounded by the new
resumeScanConcurrency config; compositions without the cache fall back
to the bounded readTitleSnapshots batch. The TUI overlay mounts the
projection registry, storage, and projection-cache rows over the same
storages root the web surface uses, so checkpoints serve both.
Rows no longer read logs for anything but the batch title fold: the
activity timestamp is a live session's last in-memory event time or the
artifact mtime via the optional sessionPersistence.locate(), falling
back to creation time; the last-turn, route, and goal columns are gone.
Route availability moves to the Enter-time preflight, which already
fully reads and replay-validates the one chosen log. The projectSessions
public API this PR had added to session-query is reverted — the change
is now confined to the TUI package.
Drop the pending state and confirm step: Tab cycles the highlighted
entry and applies at once, so the transcript behind the dialog is the
live preview; Enter/Esc/Ctrl+C just close.
DetailsDialog now shows one entry per dimension (Tool cards, Reasoning)
seeded with the current values; Tab cycles the highlighted entry's
pending value (rendered as current -> pending), Enter applies every
changed dimension in one confirm, Esc/Ctrl+C cancels.
/details reports the transcript detail state bare, jumps tool cards to
collapsed|expanded|hidden, and sets or toggles reasoning blocks, sharing
the closure state behind Ctrl+O/Ctrl+R via setToolsVisibility/setReasoning.
Also fixes a replay defect the reasoning rebuild exposed: rebuildTranscript
reused a settled StreamingAssistantComponent for a later assistant/message
of the same step, overwriting the earlier content; the settled check now
lives in renderEvent for both live and replay paths (untrusted-controls
re-recorded with the previously dropped content present).
The selector overlay opens as soon as the command dispatches: the
picker renders a loading placeholder over an undefined candidate set,
owns terminal input from its first frame, answers Enter with a
still-loading error, and cancels on Escape exactly like the loaded
list. The finished scan swaps rows in through setCandidates without
replacing the overlay; a scan failure closes it and keeps the existing
notice.
The Ctrl+O hidden phase keeps one Assistant header per turn: the first
step with visible text/reasoning owns it, later steps render as
headerless continuations, and bodiless (tool-only) steps render
nothing. Leaving hidden restores per-step headers. Pure TUI
presentation; the session log is unchanged.
Conflicts, all in files this branch and master both touched:
- `chat/helpers.ts` — kept both sides. Master replaced the inline env
filter in `gitBranch` with `scrubbedParentEnv()`; this branch added the
surface-marker and compaction-source imports.
- `docs/module-graph.md` — regenerated. Both sides added a `tui` edge
(master `subprocess`, this branch `compact`); the generated row now
carries both.
- Three `.i18n.yaml` pairing records — re-recorded from the merged files.
Blob hashes cannot be hand-merged, and both sides' prose survives on
each side of every pair.
`docs/cordis-catalog/services.md` also regenerated for shifted source
lines and master's new `sessionTitle.rename` entry.
The terminal and history pagination both treated the model-visible surface as
the human transcript. A landed compaction replacement therefore erased the
conversation it summarized — messages the reader had already seen — and a
model-only replacement copy consumed a page's `maxMessages` quota, which could
also split a compaction's provenance from the replacement citing it.
`dsh-session` now exports the marker split `isAppendSurfaceEvent` /
`isReplacementSurfaceEvent`. The terminal replays append-origin surface events,
keeps a shadowed step's tool cards paired through its append-origin assistant
message, and renders one dim marker where a compaction landed; the checkpoint is
recognized through the compaction seam's `isCompactCheckpointSource` contract,
not the shape of the replacement. `session.history` counts only append-origin
human messages. Everything model-facing keeps reading `session.surface`.