Skip to content

pa-tui/pa-cli: dock panel exits restore the dock's own group, not the prompt bar (operator ruling 2026-09-26) - #2864

Merged
kevinjosethomas merged 10 commits into
rustfrom
lane/dock-esc-focus
Sep 27, 2026
Merged

kevinjosethomas merged 10 commits into
rustfrom
lane/dock-esc-focus

Conversation

@kevinjosethomas

@kevinjosethomas kevinjosethomas commented Sep 26, 2026 •

Copy link
Copy Markdown
Member

Summary

The operator's 2026-09-26 ruling: leaving a dock panel with ESC or the left arrow returns to the chat view with the panel's own dock item still selected — the dock holds the keyboard focus, not the prompt bar. Today every panel exit lands on the prompt bar: the heartbeats picker's Close, the bash (Shells) view's Close, and the goal panel's close all leave subagents_focused false, and the Subagents panel is cross-run (the dock's Subagents group opens the scoped agents view — the session run exits; ESC/left there reopens the scope root's chat as a fresh SessionUi with the dock unfocused).

The change

  • In-session panels (Heartbeats, Shells, the goal panel): the close paths call the new SessionUi::focus_activity_dock — the group-pick hand-off extracted from focus_subagents_summary (which keeps its TS tray-override gate and delegates). The originating dock group stays selected AND focused; the existing "focused selection must stay actionable" reconciliation in update_subagent_summary still moves/releases the selection when a group went unselectable while the panel was open.
  • The Subagents panel (cross-run): AgentsViewOutcome::scope_back is set in open_scope_root (the parent key with pop AND escape without pop; a plain row open keeps it false), the pa-cli agents-view flow passes it as InteractiveOptions::restore_dock_focus, and the reopened session parks it as pending_dock_focus_restore until update_subagent_summary sees the roster make the Subagents group actionable — so a late roster cannot bounce a focused-but-unselectable dock straight back to the editor.
  • Every InteractiveOptions literal grows restore_dock_focus (the repo's own precedent for agents-view-passed flags: session_has_children, client_settings).
  • The stale open_heartbeats_view doc line ("closing the picker returns to the editor") now describes the current behavior; the pa-tui README's dock focus-clause gains one sentence recording the ruling and the TS divergence.

Parity-diff evidence (AGENTS merge gate)

  1. Rendered output: no frame-shape change — the dock, the panels, and the prompt bar render exactly as before; only the post-close focus/selection state differs.
  2. Interactive behavior vs TS: TS has no multi-group dock — subagent-summary-line.ts is a single subagents entry (isSelectable = counts + openable), closeHeartbeatManager hides the overlay and focus returns to the editor (interactive-mode.ts:11995), and the TS scope_back reopen runs resetSubagentSummary (editor focus). The unified Heartbeats/Subagents/Shells/Goal dock is the Rust-side operator redesign (pa-tui: the activity dock's selection reads as a slight green band, not accent text (operator 2026-09-26 directive) #2831, the direct-navigation redesign), so the panel-exit restore is a sanctioned Rust divergence, cited in the pa-tui README dock clause. The editor's Down/Alt+A hand-off keeps the TS focusSubagentSummary gates (picker-open, selectability, the tray override label).
  3. Wire parity: heartbeats_list, list_kernel_bash, the roster, and the agents-view outcome shapes are unchanged; scope_back is a new client-side outcome field the wire never sees.
  4. User-visible invariants: ESC/left from a dock panel lands on the panel's own dock item — Enter re-opens the same panel; left/right move the dock groups; up/cancel returns to the editor (unchanged); a plain row open in the agents view still lands on the editor.

Ownership compliance

  • Classification: internals + one seam field — pa-tui (session_ui, agents_view, interactive, README) and pa-cli (the agents-view flow's option wiring). InteractiveOptions::restore_dock_focus is pub like its sibling open-metadata flags; AgentsViewOutcome::scope_back rides the existing pub outcome the flow already reads. Dependency direction unchanged (pa-tui -> pa-types; pa-cli -> pa-tui); no crate-internal edits outside pa-tui/pa-cli.
  • Crate scope: pa-tui session/dock focus model per its README scope; the pa-tui README sentence records the divergence.

Regression pins

All fail before the fix:

  • crates/pa-tui/tests/dock_panel_exit_focus_headless.rs (mock supervisor): Heartbeats/Shells/Goal panels x ESC and left. Proof: after the panel closes, a bare Enter re-opens the SAME panel (an Enter on the empty prompt bar submits nothing) — final-frame assert plus a never-handoff assert; the entry path exercises the dock's left/right group navigation (Down, Right(s), Enter).
  • crates/pa-cli/tests/dock_scope_exit_focus_e2e.rs (real daemon + ledger-seeded child): Subagents panel x ESC and left. Run 1 dock-opens the scoped view; run 2 exits it (asserts selection == Attach(parent) and scope_back); run 3 reopens the chat with the flag and asserts a bare Enter re-opens the scoped view (return_to_agents_view true; with the bug the Enter is swallowed and the assert fails).

Telemetry

No new event: the focus model of existing surfaces — no new user-visible surface or user action (the existing activity_opened events already count the panel opens).

Fold note

Folded as the tip moved (the standing lane rule: the second lander takes the fold):

  1. bc412c5f7 (pa-tui: refuse supervisor requests on a reader that already died (the post-turn refresh stall) #2829-pa-tui: the agents-view search corpus is the SESSION column's own title (operator directive 2026-09-26) #2841 incl. pa-tui: the activity dock's selection reads as a slight green band, not accent text (operator 2026-09-26 directive) #2831's green-band dock selection, pa-core/pa-tui/pa-cli: persist the Ctrl+O conversation-detail level as the chatDetail setting (TS #2709 port) #2834's chatDetail persistence) — clean rebase; the tip's three new test files with InteractiveOptions literals grew the field.
  2. a93d0c401 (incl. pa-tui: the dock's arrows visit every rendered group, empty ones included (operator directive 2026-09-26) #2861 — the dock's arrows visit every rendered group, empty ones included, one arrow one group) — a real reconciliation in session_ui.rs, keep-both: activity_selectable is gone in pa-tui: the dock's arrows visit every rendered group, empty ones included (operator directive 2026-09-26) #2861's world, so focus_activity_dock (the close restore) adopts the arrows' own semantics (the dock owns the hand-off exactly while it renders; every rendered group is traversable; a selection whose group left the row steps back), the scope-back restore fires on dock.visible() at the first post-attach summary, and the battery's navigation walks the rendered groups one press at a time (the barriers now pin the panel content, not the navigation — the pa-tui: the dock's arrows visit every rendered group, empty ones included (operator directive 2026-09-26) #2861 arrows make the group order feed-independent). The tip's new test literals (dock_arrows_headless, effort_tray_label_headless, plus a new interactive_daemon_e2e literal) grew the field in the same fold. Folded head: 17c6f9866.

Cross-lane: #2861 landed first, so this PR takes the fold (the README's dock clause merges both directives' sentences). #2862 (prompt-history-nav) was still open at fold time; if it lands first, its owner folds this.

  1. d5cd4b132 (pa-tui: clickable links, click-to-open, the plain-key login-URL copy, and a selectable onboarding pane #2846 clickable links + pa-tui: the chat-open first frame - the dock folds with the attach, direct opens hold the previous surface, content opens suppress the brand splash (operator directive 2026-09-26) #2854 the chat-open first frame — the dock folds with the attach, + pa-daemon: per-worker stderr capture - not-ready launch errors carry a 4 KiB tail of the worker's log #2851/pa-ai: the anthropic oauth tests stage the registered port and skip when the runner holds it (EADDRINUSE flake hardening) #2867) — clean rebase; the tip's three new test literals (mouse_click_open_headless, chat_open_first_frame_headless, chat_open_first_frame_pty_e2e) grew the field in the same fold. pa-tui: the chat-open first frame - the dock folds with the attach, direct opens hold the previous surface, content opens suppress the brand splash (operator directive 2026-09-26) #2854's first-frame fold does not touch the restore's internals: the one-shot still fires at the first post-attach summary, and the folded first paint carries the restored dock. Folded head: 881e6e8ec.

  2. 7064d039a (pa-tui: mouse/touch clicks on the core surfaces - click-to-expand condensed runs and tool cards, click-to-place-caret in the prompt bar, click-to-open rows in the agents view, picker row select #2865 the click surfaces + perf(pa-tui): submit prompt off the render path - TS onSubmit parity (ordered FIFO worker; ack p50 1.8->0.2ms) #2868 submit-off-render + pa-tui: wrap overlong words in one pass (first-frame transcript load) #2870 word wrap + pa-tui: the read-only inline info panel - the /context-family commands render as docked popup panels (ESC closes, transcript untouched); the ? quick-shortcut guide removed (operator directives 2026-09-26) #2863 the info panel + pa-daemon: supervisor.rs split 1/8 - the routing concern moves out of src/supervisor.rs #2877/perf(pa-daemon/pa-cli/pa-tui): return session-load and attach heap to the OS - loaded-session settled RSS -24 to -31pct #2872/perf: memoize the kernel venv runtime-ready probe (managed path; custom override keeps the direct probe) #2857/perf(pa-daemon): bound the legacy-registry parent probe to the header line #2869) — clean merge; six construction-site seams fixed in the fold commit 0c2805a05 (four interactive_daemon_e2e fixtures + touch_click_headless + info_panel_headless literals grew restore_dock_focus), plus the prompt_history_nav_headless alt_a two-Escape round-trip fix (fd79bf351).

  3. The post-test(pa-tui): the bang-during-streaming-turn e2e gates the mock's turn end on the ack bang, not a wall clock - every awaited state is causal or terminal, closing the two-channel load red (red-bang-stream-flush-20260926-1) #2904-unlock resume fold — THREE tip squashes folded as they landed mid-resume: 0038c3f72d59 (test(pa-tui): the bang-during-streaming-turn e2e gates the mock's turn end on the ack bang, not a wall clock - every awaited state is causal or terminal, closing the two-channel load red (red-bang-stream-flush-20260926-1) #2904 — the bang-during-streaming-turn e2e is now ack-gated and deterministic, closing the registered red-bang-stream-flush-20260926-1), 61d92a206 (pa-ai/pa-daemon: the faux repeat-last knob + the goal-recovery e2e opts in and pins the exhaustion race deterministically - closes red-goal-recovery-faux-exhaustion-20260926-1 #2902 — the faux repeat-last knob + the goal-recovery e2e determinized, closing red-goal-recovery-faux-exhaustion-20260926-1), and c94637534 (pa-tui: the inline pickers' selection wash reads off the surface (operator directive) #2908 — the inline pickers' selection wash; rendering-only, line-disjoint from the restore). All three merges clean. One construction seam fixed in fold commit 09bea71a1: this PR's own dock_scope_exit_focus_e2e.rs AgentsViewOptions literal grew the tip's new incident_notice_state field (pa-types/pa-cli/pa-tui: daemon incident forensics — prime-agent incident CLI + agents-view incident notices (TS #2406 port) #2866). Whole-tree audits on the folded head: every InteractiveOptions literal carries exactly one restore_dock_focus (67/67); every AgentsViewOptions literal carries incident_notice_state (25/25). Lint-discipline pass (de73b1466): the e2e's #[allow(clippy::zombie_processes)] now states its reason (the repo's interactive_fork_e2e precedent) and the supervisor readiness poll documents that the sleep is the poll interval, never a pass condition. Folded head: 7c7672197.

Advisory note (rulebook, non-blocking): dock_panel_exit_focus_headless.rs is 530 LoC — one mock-harness battery of the seven regression tests (one harness, one per-close-path family), the same shape as skills_menu_headless; splitting would separate the shared harness from its tests without a responsibility seam.

Note

Restore dock group focus on scoped panel exit in pa-tui/pa-cli sessions

  • Exiting a scoped agents-view panel (Escape or Left) now returns to the scope root's chat with the dock focused on the panel's own group, not the prompt bar. Implements operator ruling 2026-09-26.
  • Adds restore_dock_focus to InteractiveOptions in interactive.rs; run_agents_view_flow in interactive_mode.rs sets it from the new AgentsViewOutcome.scope_back flag. Direct CLI launches keep it false.
  • SessionUi arms a one-shot pending_dock_focus_restore on attach and focuses the selected activity-dock group on the first post-attach summary update. A new focus_activity_dock helper also restores focus when goal, bash, or heartbeat panels close; opening the heartbeats panel selects the Heartbeats dock group. See session_ui.rs.
  • Adds extensive test coverage: a headless mock-supervisor suite in dock_panel_exit_focus_headless.rs, end-to-end daemon scenarios in dock_scope_exit_focus_e2e.rs, and restore_dock_focus: false across all existing fixtures. Dock-arrow tests drop the repeated focus grab after panel exit.
  • Behavioral Change: closing goal, bash, or heartbeat panels now moves focus to the activity dock instead of the editor; scoped-view exit preserves dock selection so bare Enter reopens the subtree rather than submitting the prompt.

Macroscope summarized a12c284.


Note

Low Risk
UI focus/keyboard navigation only; no auth, wire protocol, or persistence changes beyond a client-side option flag.

Overview
Implements the 2026-09-26 panel-exit ruling: closing dock panels or backing out of the scoped Subagents view should land keyboard focus on that panel’s dock group, not the prompt bar.

Cross-run (Subagents panel): AgentsViewOutcome gains scope_back, set when the scoped view returns to the parent chat. The pa-cli agents-view loop maps it to InteractiveOptions::restore_dock_focus. The reopened chat arms a one-shot pending_dock_focus_restore after attach and focuses the dock on the first subagent summary update once the dock is visible.

In-session panels: New focus_activity_dock runs when Heartbeats, Shells (bash), or goal panels close (ESC/left), and opening /heartbeats selects the Heartbeats dock group. Session rebind clears any pending scope-back restore.

Tests add headless and daemon e2e coverage; existing fixtures set restore_dock_focus: false. Dock-arrow tests drop extra focus grabs now that panel exit keeps the dock focused.

Reviewed by Cursor Bugbot for commit a12c284. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-actions

github-actions Bot commented Sep 26, 2026 •

Copy link
Copy Markdown

Prime Agent performance — partial

PR a12c2847 compared with main cd1f215c.

Benchmark execution did not complete successfully. Missing measurements are not performance wins.

Failure diagnostics:

  • pr setup: RuntimeError: pr prepare 0 failed with exit code 1
  • PR: CalledProcessError: Command '['/usr/sbin/runuser', '-u', 'builder', '--', 'npm', 'ci', '--no-audit', '--no-fund']' returned non-zero exit status 1.

See the saved per-trial logs and terminal transcripts for details.

Overall: 0 regressed · 0 improved · 0 no clear change · 42 unavailable.

Metric Main This PR Change
Cold startup 679.0 ms — —
Warm startup 537.7 ms — —
Installation 5.29 s — —
Compressed release artifacts 73.16 MB — —
Installed footprint 595.66 MB — —
Idle memory, summed RSS 652.80 MB — —

Python runtime

Metric Main This PR Change
Python kernel startup 31.4 ms — —
Python cell round trip 0.102 ms — —
Empty bash command 2.0 ms — —
Bash git status 2.7 ms — —
Bash 32 KiB output 2.0 ms — —
35 cells / 9 shell calls 26.5 ms — —
Python interrupt to done 0.493 ms — —
Python state snapshot 9.3 ms — —
Python state restore 116.8 ms — —
Python idle RSS 21.26 MB — —
Python RSS after pandas workload 75.61 MB — —

Session transport

Metric Main This PR Change
Full-history transfers per warm session switch 1.00 transfers — —
Private frame decode, 32 MiB in 8 KiB chunks 14.4 ms — —

UI interactions

Metric Main This PR Change
Resume large session (cold) 1,722.8 ms — —
CPU, resume large session 1,950.0 ms — —
Switch into large session 1,445.2 ms — —
CPU, switch into large session 1,530.0 ms — —
Open agents view from a session 134.9 ms — —
CPU, open agents view 90.0 ms — —
Full agents roster, many sessions 4.03 s — —
CPU, full agents roster 0.95 s — —
Open another session from agents view 1,944.3 ms — —
CPU, open from agents view 1,090.0 ms — —
Reopen resident large session 191.3 ms — —
CPU, reopen resident session 200.0 ms — —
Open subagent session at depth 6 17,322.3 ms — —
CPU, open subagent at depth 6 4,640.0 ms — —
Open chain parent from agents view 2,966.8 ms — —
CPU, open chain parent 1,430.0 ms — —
Scheduled catalog, first request 418.7 ms — —
CPU, scheduled catalog 850.0 ms — —
Scheduled catalog, repeated request 0.4 ms — —
CPU, repeated catalog 0.0 ms — —
Cold worker with three catalog scans 445.4 ms — —
CPU, cold worker and scans 480.0 ms — —
UI memory after interactions 1,733.73 MB — —

Sandbox cost: ~$0.0513 — no inference calls.
Run, logs, and downloadable raw results

Methodology and samples

Main resolved at 2026-09-27T06:07:50.523029+00:00. Harness cd1f215c.
Linux x64, 4 vCPU, 8 GB RAM, 20 GB disk; region us.
Image: node:24-bookworm@sha256:be23f54a88d34e8824c741b19b91064094f92c1c97b194144bfc8b50d67258e2.
Stock tools, skills, daemon, and Python bootstrap enabled; fresh homes and a fixed Git fixture.
Onboarding is dismissed; the editor starts without a selected model or submitted prompt.
Medians shown. Arrows require a 20% timing/memory change plus absolute floors and IQR.
These practical noise floors are not a statistical significance test.
Cold means stopped Prime processes; OS filesystem caches are not flushed.
No model requests or credentials. Installation excludes build/setup time.
Installer tarballs use loopback; npm/Python downloads use the network with fresh caches.
Artifact size counts release tarballs; footprint after first use includes registry packages.
MB is decimal. Summed RSS can double-count shared pages; PSS is recorded when available.
Provisioning, setup, and build durations are recorded separately in the raw results.
Kernel probes use the installed JSONL runtime, outside the TUI/TypeScript host.
Per trial: 50 Python cells, 5 calls per shell case, and one 35-cell mix (9 git status calls).
Cell/shell values are batch means; other runtime timings are single operations.
State fixture: a 10,000-row × 8-column integer DataFrame and a 10,000-integer list.
Restore runs in a fresh kernel, including pandas imports; kernel startup is excluded.
Kernel RSS covers the isolated Python process; loaded RSS follows the pandas workload.
Transport benches run node against the prepared source build, outside the installed home.
The switch benchmark drives one warm switch into a 48k-entry session through a real
daemon and counts full-history crossings: streamed replacement snapshots, inline
replacements, and full-history refetch responses.
Frame decode times one 32 MiB private frame, snapshot-chunk header, pushed in
8 KiB chunks; the wire shape of multi-MB frames on the daemon-worker channels.
UI trials use a fresh fixture set: 194 top-level sessions including one ~40 MB transcript,
40 ledger fan-out children, and a 6-deep subagent chain (~46 spawn edges).
Large fixtures hold 1,999 complete triples (~5 MB JSONL); medium 119; subagents 399 each.
Interactions: cold --resume of a large session, warm /resume switch, left-arrow to agents view,
roster settle with many saved sessions, search-and-open of another large session,
reattaching to that resident session, opening the chain parent, and drilling to depth 6.
Readiness is the rendered transcript tail plus a confirmed editor echo.
CPU metrics sum utime+stime across the whole benchmark-user process tree per interaction.
UI memory sums RSS after the interactions; PTY byte counts are in the raw results.
A separate catalog fixture has 2,300 sessions, 2,298 edges, and 13 paused scheduled-job owners.
Catalog timings cover first/repeated reads and cold worker creation under three pending scans.
All expected jobs and owner metadata are checked; worker readiness excludes TUI rendering.
Costs estimate full sandbox lifetimes at configured rates, including setup and build.
Budget target: $1; not a billing cap. Performance changes are informational.
Failed or incomplete execution fails the workflow; saved artifacts remain available.
Each side stops a phase after 2 identical consecutive failures.
Skipped trials are not attempted samples. Warm startup requires a successful cold launch.

Metric Main successful/attempted PR successful/attempted Main spread PR spread
Cold startup 10/10 0/0 IQR 43.7 ms —
Warm startup 10/10 0/0 IQR 14.7 ms —
Installation 3/3 0/0 range 0.60 s —
Compressed release artifacts 1/1 0/0 — —
Installed footprint 1/1 0/0 — —
Idle memory, summed RSS 10/10 0/0 IQR 4.55 MB —
Python kernel startup 10/10 0/0 IQR 0.9 ms —
Python cell round trip 10/10 0/0 IQR 0.026 ms —
Empty bash command 10/10 0/0 IQR 0.1 ms —
Bash git status 10/10 0/0 IQR 0.091 ms —
Bash 32 KiB output 10/10 0/0 IQR 0.1 ms —
35 cells / 9 shell calls 10/10 0/0 IQR 2.8 ms —
Python interrupt to done 10/10 0/0 IQR 0.046 ms —
Python state snapshot 10/10 0/0 IQR 0.4 ms —
Python state restore 10/10 0/0 IQR 3.8 ms —
Python idle RSS 10/10 0/0 IQR 0.18 MB —
Python RSS after pandas workload 10/10 0/0 IQR 0.13 MB —
Full-history transfers per warm session switch 10/10 0/0 IQR 0.00 transfers —
Private frame decode, 32 MiB in 8 KiB chunks 10/10 0/0 IQR 1.4 ms —
Resume large session (cold) 3/3 0/0 range 418.1 ms —
CPU, resume large session 3/3 0/0 range 80.0 ms —
Switch into large session 3/3 0/0 range 252.8 ms —
CPU, switch into large session 3/3 0/0 range 90.0 ms —
Open agents view from a session 3/3 0/0 range 6.0 ms —
CPU, open agents view 3/3 0/0 range 80.0 ms —
Full agents roster, many sessions 3/3 0/0 range 0.01 s —
CPU, full agents roster 3/3 0/0 range 0.07 s —
Open another session from agents view 3/3 0/0 range 116.0 ms —
CPU, open from agents view 3/3 0/0 range 240.0 ms —
Reopen resident large session 3/3 0/0 range 5.9 ms —
CPU, reopen resident session 3/3 0/0 range 20.0 ms —
Open subagent session at depth 6 3/3 0/0 range 257.1 ms —
CPU, open subagent at depth 6 3/3 0/0 range 170.0 ms —
Open chain parent from agents view 3/3 0/0 range 221.0 ms —
CPU, open chain parent 3/3 0/0 range 90.0 ms —
Scheduled catalog, first request 3/3 0/0 range 70.2 ms —
CPU, scheduled catalog 3/3 0/0 range 90.0 ms —
Scheduled catalog, repeated request 3/3 0/0 range 0.073 ms —
CPU, repeated catalog 3/3 0/0 range 0.0 ms —
Cold worker with three catalog scans 3/3 0/0 range 60.2 ms —
CPU, cold worker and scans 3/3 0/0 range 10.0 ms —
UI memory after interactions 3/3 0/0 range 4.44 MB —

Failures:

  • pr setup: RuntimeError: pr prepare 0 failed with exit code 1
  • PR: CalledProcessError: Command '['/usr/sbin/runuser', '-u', 'builder', '--', 'npm', 'ci', '--no-audit', '--no-fund']' returned non-zero exit status 1.

Comment thread crates/pa-tui/src/session_ui.rs Outdated

@cursor cursor Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Stale Bugbot comment from a previous run.

Comment thread crates/pa-tui/src/session_ui.rs Outdated
Comment thread crates/pa-tui/src/session_ui.rs Outdated

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

There are 3 total unresolved issues (including 2 from previous reviews).

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 7f2b1a8. Configure here.

Comment thread crates/pa-tui/src/session_ui.rs
@kevinjosethomas
kevinjosethomas force-pushed the lane/dock-esc-focus branch 7 times, most recently from 21758cd to 2ad439f Compare September 26, 2026 05:25
… prompt bar (operator ruling 2026-09-26)

ESC/left from any dock panel now returns to the chat view with the
originating panel's dock item still selected: the heartbeats view, the
bash (shells) view, and the goal panel hand the keyboard focus back to
the dock on close (focus_activity_dock — the dock-owns-the-hand-off
shape the editor's Down/Alt+A path already used per the arrows
directive, minus the editor hand-off's tray gate), and the scoped agents
view (the Subagents panel) carries scope_back on its outcome so the
agents-view flow reopens the scope root's chat with restore_dock_focus:
the reopened chat hands the dock its focus exactly once, at the first
summary after the attach (the roster is seeded inside the attach, so the
Subagents group rides the rendered row at its first paint; a dock that
never mounts keeps the editor's focus, and a LATE roster can never yank
the keyboard back mid-composition; every later attach's rebind reset
drops the armed restore with the session it belonged to).

The heartbeats view IS the dock's Heartbeates item on every entry path:
open_heartbeats_view selects the Heartbeates group (the /heartbeats
command would otherwise leave the dock's previous selection, so its
panel exit would restore the wrong item — the Bugbot/Macroscope round).

TS has no multi-group dock: its SubagentSummaryLine is the single
subagents entry, the heartbeat manager closes back to the editor, and
the TS scope_back reopen runs resetSubagentSummary — so the panel-exit
restore is a sanctioned Rust divergence (cited in the pa-tui README's
dock clause).

Regression tests: the headless mock-supervisor battery
(dock_panel_exit_focus_headless.rs — Heartbeates/Shells/Goal panels x
ESC/left plus the /heartbeats command path; the navigation steps the
rendered groups one press at a time per the arrows directive, and each
barrier pins the panel's content), proven by the next Enter re-opening
the SAME panel (which an Enter on the empty prompt bar never does), and
the real-daemon e2e (dock_scope_exit_focus_e2e.rs — the Subagents panel
x ESC/left: the scope-back reopen's bare Enter re-opens the scoped view,
and the outcome carries scope_back). Every InteractiveOptions literal
grows the restore_dock_focus field.

Fold: the tip wave since the first fold (incl. the dock arrows
directive #2861 — the selection now rides the rendered groups, empty
ones included, one arrow one group) is reconciled here: focus_activity
_dock keeps the arrows' dock-visible/rendered-group semantics, the
scope-back restore rides dock.visible(), and the battery's navigation
walks the rendered groups.
…e the recall (#2864)

The 2026-09-26 panel-exit ruling (this PR) hands the keyboard focus to
the dock's own group when a dock panel closes: the Heartbeates view's
Escape now lands on the dock's Heartbeates item (focus_activity_dock),
never the prompt bar. #2862's alt_a coexistence test pinned the
pre-ruling path — one Escape from the Heartbeates panel straight back
to the editor, so one Up recalled — and under the ruling that Up is
consumed releasing the dock's focus, the final wait_render("first
prompt") barrier times out: the deterministic shard-2 red on rounds 9
(2ad439f) and 10 (d304155), identical failing step in both.

The test now pins the sanctioned round trip: the panel's Escape closes
onto the dock's own item, the cancel Escape hands the editor back, and
only then the Up recalls — the recall still must survive the trip, so
the protective intent is unchanged.
…us (#2864)

The fold onto 7064d03 (#2865's click surfaces, #2863's info panel,
#2872's heap returns) merged clean, but the tip's new InteractiveOptions
construction sites predate this PR's restore_dock_focus field: four new
interactive_daemon_e2e fixtures, the touch_click battery, and the info
panel battery build the options literal without it. Every new site takes
the field (false) at the same seam as the battery: after
session_has_children, before client_settings. No behavior change — the
new tests keep the editor's focus on entry, same as the pre-fold ones.
@kevinjosethomas
kevinjosethomas merged commit a2b1224 into rust Sep 27, 2026
11 of 12 checks passed
@kevinjosethomas
kevinjosethomas deleted the lane/dock-esc-focus branch September 27, 2026 06:18
kevinjosethomas added a commit that referenced this pull request Sep 27, 2026
… tip (SS7 #2903 update/restart cut + #2907 agent_engine model split + #2900 session_ui queue split + #2864 dock panel) over the SS4 sessions cut

- facade conflict resolved per the campaign pattern: the update/restart family (SS7's cut at tip) and the saved-session list family (my cut) both resolve OUT of the facade - one region, shared '}' tail dropped; keep-both mod decls hold (mod sessions; + mod update_restart; auto-merged alongside clients/adoption/routing/subscribers)
- no tip-side changes landed inside the moved sessions spans (region-disjoint, per the conductor ruling): sessions.rs UNCHANGED in this fold; per-method byte-proof vs the a2b1224 tip blobs all IDENTICAL modulo my recorded pub bumps; facade vs tip = +7 structural (mod sessions; test-only import) / -828 (the sessions family only)
- gates on the folded tree: cargo fmt --check clean; clippy -p pa-daemon --all-targets --locked -D warnings clean; env-stripped lib battery 769 passed / 2 failed, both the REGISTERED red-cache-rejects-mtime-fs-timing-20260927-1 family (same pair as the fold-2 differential; cited, not chased)
kevinjosethomas added a commit that referenced this pull request Sep 27, 2026
…n window, not after a quiet-wait

The exit-path LEFT_RELEASE write sat after drain_until_quiet(10) — a
>=250ms quiet wait during which a fast local exit (restore + child
leave, well under the wait) completes, so the release arrived on a pty
nobody reads: never in the ~50ms exit-drain window the case claims to
cover, and a write to the master after the slave is gone can fail the
harness outright. The release now rides immediately after the exit
key — inside the exit path's own drain window — which the case's
stream-hygiene asserts (no release echo after the alt-screen leave;
the kitty flags pop is the stream's last mode byte) actually verify.

Fold note: the merge of the 9b62f26 tip resolved the one add/add
conflict (kitty_release_handoff_e2e.rs) by keeping the lane's
restore_dock_focus fixture init — the #2916 squash on the tip predates
the fixture fix (the tip's own InteractiveOptions carries the #2864
field, so the squash copy alone does not build).

(Cursor thread PRRT_kwDOSXZbXs6mYBZZ: "Exit release injected after
drain".)
kevinjosethomas added a commit that referenced this pull request Sep 27, 2026
… restore_dock_focus - a #2864/#2916 fold artifact (the current tip reds the same target)
kevinjosethomas added a commit that referenced this pull request Sep 27, 2026
kevinjosethomas added a commit that referenced this pull request Sep 27, 2026
… forward-only resolutions)

The tree moved heavily since this lane was built (supervisor.rs split
7/8, session_ui.rs split 3/14, agent_engine 5/11, TU3's prompt channel
into SessionUi::open). Resolutions, forward-only:

- pa-cli/interactive_mode.rs: the tip's dock-focus + opened_cwd pre-open
  setup feeds the lane's run_interactive_agents_view_open route.
- pa-daemon/supervisor.rs: the SS2 split owns handle_client (tip side
  taken); the lane's three #2391/#2515 hunks re-homed: the typed
  UpdateRestarting errorInfo on the update-prepare gate ->
  supervisor/clients.rs; the session-naming resident refusal + helper +
  test -> supervisor/update_restart.rs (the facade's auto-merged helper
  copy removed - dead after the re-home).
- pa-tui/daemon_client.rs: the tip independently evolved
  RequestRejected.error_info - merged with the lane's fuller doc.
- pa-tui/session_ui.rs: describe_session_open_failure passes the typed
  error_info through the decorated refusal (the common context already
  binds it).
- pa-tui/interactive.rs: SessionOpenRoute + the tip's # Errors doc
  combined; the wait closure threads the tip's new prompt_tx; the tip's
  pre-open reader_dead binding dropped (the post-open re-home survives);
  the tip's opened_urls + the lane's wait notice combined.
- TIP REPAIR (pre-existing break, verified on pristine 9b62f26):
  pa-cli/tests/kitty_release_handoff_e2e.rs missed
  InteractiveOptions.restore_dock_focus (#2864's squash updated 38 of 39
  literals) - one line added; the target did not compile on the tip.

Gates (Prime VM rust:1-bookworm, rustc 1.98.1): fmt EXIT 0; clippy
--workspace --all-targets -D warnings EXIT 0; batteries: pa-types +
pa-daemon + pa-tui 96 binaries green (uv + xdg-utils installed for the
kernel-env and platform-opener tests), pa-cli 33 binaries green; the
registered wedge (interactive_daemon_e2e, red-interactivedaemon-e2e-
load-wedge-20260926-1) CITED, not rerun.
kevinjosethomas added a commit that referenced this pull request Sep 27, 2026
…live tip after the conductor's HOLD)

The previous fold (1cf0b62) was built against the stale tip
9b62f26; the kitty E0063 found there was the #2916/#2864 mid-air
break, healed on the live tip by #2927 (826f877) and settled by the
631fe82 dedupe. The one-line kitty repair rider from the previous
fold is DROPPED: the merge auto-resolves to the healed single-line
state (exactly one restore_dock_focus initializer — verified), the
E0062 double-apply class stays closed.

The 5-file resolution discipline from 1cf0b62 carries (all intact:
the supervisor re-homes in clients.rs/update_restart.rs, the
prompt_tx-threaded wait closure, the typed-error_info merge in
daemon_client.rs, the decorated-refusal pass-through in session_ui.rs —
the new tip's session_ui splits auto-merged around them).
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