Load the neighbor session in the background after a delete - #1184
Merged
Conversation
`C-x k d` stuttered even though the delete RPC itself was already dispatched off the event loop and the daemon returned before doing its slow filesystem work. The stall came from the *reaction* to the resulting `session/deleted`: `on_session_deleted` reselects a neighbor and then awaited `refresh_selected_transcript`, which fetches that session's whole transcript and — for a PTY session with no cached history — its pty.log replay, plus the vt parse that rebuilds its scrollback. No frames, no keystrokes, until the daemon answered. Split `bootstrap_terminal` into a pure fetch (client + a plain-data snapshot of the app state it needs) and an install step, so the expensive half can run anywhere. The delete path now applies the local consequences inline — selection, view mode, scrollback, cleanup — and dispatches the loads on their own connection, landing as `SelectedSessionLoaded`. Each load carries the selection generation it was requested for; a load the user has navigated past is dropped rather than painted into whatever pane is showing when it returns. Same shape the fork picker's turn list already uses (spec 0163), recorded now as spec 0185 since it generalizes to any client-side reaction to a fleet notification.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The stall
C-x k→dfreezes the TUI for a beat. Deletion itself is already async on both sides —spawn_session_deletedispatches on a dedicated connection, and the daemon'sdelete()kills the adapter, broadcastsDeleted, and spawns the slow worktree/storage cleanup before returning.The freeze is in the reaction to the broadcast.
on_session_deletedmoves selection to a neighbor and then awaited, inline on the event loop:So the cost scales with the neighbor's history, not the deleted session's, and it's worst when the neighbor was never opened in this TUI (
bootstrap_terminalreturns early only on a cached history). Ordinary navigation never hits this —select_sessionis fully synchronous and fetches nothing.The change
bootstrap_terminalintofetch_terminal_bootstrap(takes a client plus a plain-data snapshot of the app state it needs, so it runs equally well inline or in a spawned task) andapply_terminal_bootstrap(installs history + replayed editor/agent/panel state). The existing inline caller keeps its behavior.on_session_deletednow callsspawn_selected_transcript_load: the local consequences (selection, view mode, scrollback, session transition, cleanup) still apply in the same frame; the transcript + PTY replay + parse go to a background connection and land asSessionMutationResult::SelectedSessionLoaded.pty_resizecalls that follow a bootstrap go out on their own connection too.Same shape the fork picker's turn list already uses (spec 0163). Added
specs/0185-fleet-events-never-block-the-client-loop.mdsince the rule generalizes: dispatching a mutation off the loop isn't enough when the completion notification's handler does the blocking work.Tests
deleting_the_selected_session_does_not_await_the_neighbor_load— mock daemon accepts the connection and then goes silent, so any inline request blocks for the client's full 120s timeout. Verified it fails on the old code (5s budget exceeded) and passes here.stale_selected_session_load_is_discarded— generation guard.Full
construct-clisuite: 1353 passed, 3 failed —empty_tui_renders_welcome_and_modeline_hint,modeline_tour_hint_clickable_only_in_empty_state,tour_cta_dims_and_loses_zone_while_tour_active, all three failing identically onmainat 8edba01, untouched by this diff.Binary
This PR is
crates/clionly → the relevant binary isconstruct:/Users/moon/construct/.claude/worktrees/async-post-delete-transcript-load/target/debug/constructNo recording: the change is the absence of a stall on an existing gesture, which a vhs capture wouldn't show any more clearly than the timing test does. Happy to record one if you'd like it on the PR.