feat(tui): replay the focus sweep when typing resumes after idle - #1299
Merged
Conversation
Focus acquisition answers "where did my focus go" at the instant it moves. It says nothing to a user coming back to a terminal they left minutes ago, where a fleet of similar panes gives the eye nothing to lock onto. Replay the same sweep on the already-focused pane when a keystroke reaches it after a long silence. The transition animates, not the typing: every key the focused pane consumes refreshes the idle clock, and a key landing while a sweep is already on screen is absorbed, so focus gain and idle resume can never stack. Keys swallowed by a modal, popup, or the prompt strip are not pane input and animate nothing. Threshold: 120s, in a named constant beside the other sweep constants, with a CONSTRUCT_FOCUS_SWEEP_IDLE_MS override for recordings.
0206 was taken by the Playbook mark-and-region spec (#1298) while this branch was open.
Merged
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.
What
The focus sweep (#1295, spec 0203) fires when a pane gains focus. That answers "where did my focus go" at the instant it moves — and says nothing to someone coming back to a terminal they left ten minutes ago, staring at a fleet of similarly shaped panes.
This replays the same sweep on the already-focused pane when a keystroke reaches it after a long silence. No second visual, no new drawing code: it re-arms the existing
FocusBorderSweepso anything that changes the animation's look or geometry keeps applying to both cases.State machine
FocusBorderSweepgains one field,last_key_at, and one method,note_focused_pane_key(now, idle_after), called once per dispatched key before routing — so the pane credited is the one about to consume the key. In order:idle_afteror later than the previous one replays the sweep on the focused pane. The first key of a session counts as ending an idle gap (the TUI has been sitting untouched since it opened).Focus-gain behavior is untouched:
observe()still owns target transitions, and the startup baseline still does not animate.Which keys count
Only keys a focus-bearing surface consumes.
keyboard_overlay_owns_input()mirrors the gates at the top ofon_keyand suppresses the cue while a floating surface owns the keyboard — help, session picker,/configure, remote-control, route menu, suggestion deck, tasks popup, inline title rename, pinned fleet panel, the prompt/minibuffer strip, and the disconnected state. None of those areFocusBorderTargets, so a key they swallow must not flash an unrelated pane's border.It is deliberately conservative: a popup that consumes some keys and falls through on others suppresses the cue for the key that dismisses it, and the pane gets it back on the next key. A missed cue beats flashing a border the keystroke never reached. Everything hosted inside a focused pane — PTY, Playbook, operator editor, inline UI, pinned clip — is the pane, and animates. Bare modifier presses are ignored.
Threshold: 120 s
FOCUS_BORDER_IDLE_RESUME_MS = 120_000, sitting next toFOCUS_BORDER_SWEEP_MS/FOCUS_BORDER_SWEEP_FRAME_MS. Two minutes is above every ordinary working pause — reading a diff, waiting out a harness turn, composing the next instruction — where the user has not lost track of focus and the motion would be pure distraction. It is short enough that stepping away for a coffee comes back to the cue. Anything in the tens of seconds fires during normal work; anything past a few minutes stops covering the common "switched windows and came back" case.CONSTRUCT_FOCUS_SWEEP_IDLE_MSoverrides it. That exists precisely so the recording below didn't require shortening the shipped constant — the committed default is 120 s, and the tape ran the daemon with the override at 4 s. Unset or unparseable falls back to the constant.Frames
The sweep already gates its own 40 ms cadence timer on
is_animating(), so nothing new runs while idle. One gap needed closing: the keystroke that re-arms the sweep is usually the same one the focused PTY consumes, and that path deliberately skips its paint (the child's echo is the visible change).skip_drawnow yields while a sweep is on screen, so the band starts at the top-left corner instead of wherever the cadence timer picks it up. Gated on a live animation — an idle TUI keeps every existing skip, and there is no permanent repaint loop.Tests
focus_border_sweep_tests— clock injected, no sleeping: a 160 s run of 400 keystrokes (deliberately outlasting the threshold) never re-triggers; the transition on either side of it does; a key during a focus-gain sweep leavesstarted_atuntouched; the env parser falls back on empty/garbage.app::tests(TestBackend) — idle → key paints a sweep whose every differing cell lies on the focused pane's border perimeter and nowhere else; continuous typing across five key kinds never re-arms; keys swallowed by help / the prompt / a disconnected TUI animate nothing, and the same key animates once the overlay is gone;C-x othen a key keeps one sweep on the new pane.cargo test --workspaceandcargo test -p construct-e2eboth green.Spec
specs/0207-tui-idle-typing-focus-sweep.md, with a pointer added to 0203. (Started life as 0206; renumbered after #1298 landed0206-playbook-mark-and-region.mdwhile this branch was open.)Recording
vhs, isolated
CONSTRUCT_*dirs, worktree binary,CONSTRUCT_FOCUS_SWEEP_IDLE_MS=4000. The tape types, keeps typing (no animation), goes quiet past the threshold, then presses one key. Verified frame-by-frame: the only multi-cell border repaint in the whole 32 s capture is the 200 ms band right after the resuming keystroke.Local artifacts:
/tmp/sweep-artifacts/(mp4, gif, verified midpoint frame, steady-while-typing frame).