Skip to content

feat(tui): per-session drafts, browser resume and honest ownership (T5 slice) - #431

Open
BunsDev wants to merge 5 commits into
feat/411-tool-approvalfrom
feat/412-session-nav
Open

BunsDev wants to merge 5 commits into
feat/411-tool-approvalfrom
feat/412-session-nav

Conversation

@BunsDev

@BunsDev BunsDev commented Nov 1, 2026 •

Copy link
Copy Markdown
Member

Refs #412. Stacked on #430 (#411), which is stacked on #429 (#410). Retarget to main after the stack merges. Classic stays the default renderer.

What this ships

The safe navigation slice of T5: move between sessions without losing a draft, without one session's views showing in another, and without the shell claiming authority that no producer has shown.

  • Session browser resume works in Next. /session showed "No sessions found" in the real binary, in both renderers. Its list loader ran only inside App::run, which the interactive CLI loop never calls. The loop now polls it each tick in Next. Enter on a row now resumes that session, as the browser's hint always said. The resume goes through /session resume <id>, the same path as typing it, and is kept out of prompt history.
  • Drafts and reading position are kept per session. On a switch, the outgoing session keeps its unsent draft, its pasted blocks, its pending images and its scroll position. Returning restores them, and a restored draft is shown but never sent. If the resume fails, the draft comes back.
  • Old views do not follow. A switch drops tool rows, permission decisions, interrupted tools, system notes, turn metadata, the response reader and the selection. Late runtime events from the old session were already dropped by the [TUI v2][P0] T1: extract a typed presentation seam and support Classic/Next without runtime divergence #408 generation scope; a test now covers that across a resume.
  • Resume is refused while work is in flight: during a turn, while a cancel is still settling, or into the session already shown.
  • Ownership is stated, not inferred. The Next header and Session page say engine-local. With the daemon ledger enabled they add the registration result (registering, ledger-registered or ledger unavailable), and only for the session that was registered. Registration is described as the daemon recording the session, not controlling it. Nothing claims daemon-managed execution.
  • Identity: a familiar name stays (unverified), a display name that no identity contract binds. The session title and id are shown through terminal_safe, and the title now follows startup, resume and rename.

Classic is unchanged

Everything above applies to Next only:

  • Browser list: Classic's browser still shows the empty list, because its loader is polled only for Next.
  • Enter and rename: Enter in Classic's browser does nothing, and Classic's rename behaves as before.
  • Resume: a Classic resume keeps its composer and notes.
  • Title: the title, which Classic uses in exports, is set only for Next.

Fixing Classic's empty browser is a one-line change, which can be its own PR if you want it.

Review

An independent reviewer read the first version. Resolved:

Finding Resolution
A kept draft lost its pasted blocks, so a later send would carry the placeholder text Paste bodies are kept with the draft
The header title was never set on startup, resume or rename, and a browser rename of another session changed it Set on all three; browser rename only retitles the shown session
A resume could be accepted after a remote cancel, before the turn ended Refused until the cancel settles
System notes and turn metadata from the old session showed in the new one Cleared on a switch
The synthetic resume command stayed in prompt history Removed
A ledger task that ended without answering left registering forever Shown as ledger unavailable
The session id was shown unsanitized Shown through terminal_safe

Not changed: App::run has no callers, and a browser resume there would wait forever. The queue of messages typed during a turn is kept across a switch, which predates this PR. Session cost is not reset on a switch.

Failing first: the four review regression tests each fail with their fix backed out. The tmux case fails 6 of 9 on the #411 head 7b1783c, where the browser lists nothing and Enter does nothing.

Verification

All results are on the head of this PR.

Check Result
cargo test -p claurst-tui --locked --test next_sessions 14 passed, including a Classic-unchanged test
cargo test --workspace --locked --no-fail-fast 2865 passed, 0 failed, 52 ignored
cargo clippy --workspace --all-targets --locked -- -D warnings clean
tmux case 09_next_sessions (real binary, two sessions, disposable home) 9/9
Full tmux suite at 120x40 84/84

The full suite showed one intermittent failure in case 08's cooked-mode check. The check typed before the app had exited. The fix is on #429 (51a7705) and merged up the stack; case 08 then passed 5 of 5.

Still open for #412

These need producers or a real daemon, and are not in this slice:

  • Negotiating daemon capabilities through GET /api/v1/health (coven.daemon.v1), and reconnect with a fresh snapshot and event cursor.
  • Root, revision and principal provenance for familiars and sessions. No producer supplies it, so the shell says unverified.
  • Invalidating protected actions after reconnect, account switch or revocation. That depends on protected approvals, which [TUI v2][P0] T4: ship first-class tools, diffs, and authoritative approval workflows #411 leaves unavailable.
  • Familiar and project navigation beyond session resume, and stating on exit whether work survives.
  • Real-daemon and headless/ACP compatibility journeys.

…5 slice)

The session browser now resumes the selected session with Enter, as its
hint always said. Its list never loaded in the interactive binary: the
loader ran only in App::run, which the CLI loop does not use, so every
browser showed "No sessions found". The CLI loop now polls it.

Switching sessions keeps each session's unsent draft (with pasted
blocks and images) and reading position, restores them on return
without sending, and drops views that belonged to the old session:
tool rows, decisions, notes, turn metadata, selection and the reader.
A failed resume gives the draft back. A resume is refused while a turn
runs or a cancel is settling.

Next shows who owns execution: engine-local, with the ledger
registration result only for the session it registered. Nothing claims
daemon control, and a familiar name stays a display name.

Refs #412
@vercel

vercel Bot commented Nov 1, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
docs Ready Ready Preview Nov 2, 2026 5:59am UTC

Request Review

Browser Enter-to-resume, list loading in the interactive loop, the
per-session draft and view switch, header titles and the rename guard
now apply to the Next renderer only. Classic behaves as before.

Refs #412

This branch was successfully deployed

1 active deployment
Preview — 5178971e Deployed Nov 2, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant