fix: make active panes follow DOM focus - #882
Conversation
Split review panes need one predictable owner for global paging. Focus alone is insufficient because blank pane surfaces do not receive focus, and an invisible owner makes keyboard behavior hard to anticipate. Record pointer, wheel, and focus activation together with a restrained active-border treatment before changing the shared pane renderer. Validation: context-sync structural check passed. Generated with Codex (gpt-5.6-sol) Co-authored-by: Codex <noreply@openai.com>
Split panes could change route ownership only when a focusable descendant received focus. Clicking or wheeling a blank pane left keyboard ownership on a different surface, with no visual indication of where global commands would land. Unify focus, pointer, and wheel activation in the shared pane renderer and show the current owner with a restrained inset accent border. Wheel events remain native and local to their target. Validation: TabbedPanelTree unit suite passed; Svelte analyzer reported no new issues. Generated with Codex (gpt-5.6-sol) Co-authored-by: Codex <noreply@openai.com>
A visible files pane kept its window-level Page Up and Page Down listener active while the conversation pane owned the interaction. That made page keys move the diff after a maintainer clicked or wheeled elsewhere. Thread route-derived ownership into pull request diff layouts, retain local paging for directly targeted diff input, and prefer live pane focus while route navigation settles. Apply the same contract to PR and Activity detail surfaces. Validation: full frontend unit suite passed with 3,894 tests and 2 skips; frontend checks and Effect diagnostics completed with zero errors; the PR detail pane Playwright suite passed 6 tests across Chromium and Firefox. Generated with Codex (gpt-5.6-sol) Co-authored-by: Codex <noreply@openai.com>
Issue detail panes had no default input owner, while standalone Workspaces panes did not promote pointer, focus, or wheel interaction into the shared ownership model. That left the active border absent or stale outside the PR and Activity surfaces. Apply the same single-owner contract to every detail pane and standalone workflow tree so keyboard routing and the visible one-pixel indicator stay predictable wherever panes are split. Validation: full frontend unit suite passed with 3,896 tests and 2 skips; frontend checks and Effect diagnostics completed with zero errors; the complete workspace-sidebar and PR detail pane Playwright files passed 169 tests with 1 skip across Chromium and Firefox. Generated with Codex (gpt-5.6-sol) Co-authored-by: Codex <noreply@openai.com>
Live pointer and wheel ownership could diverge from route-derived keyboard handling, while stale or hidden pane state could leave no visible owner. Nested workspace regions also painted their own focus marker when the enclosing PR or issue pane was inactive. Keep keyboard dispatch and the one-pixel ownership treatment on the same validated live pane, and model Workflow, Details, and bottom Terminal as sibling owners within a workspace. This preserves one coherent input target across PR, Issue, Activity, and Workspaces surfaces. Validation: frontend-check passed; full unit suite passed with 3,900 tests and 2 skips; mock Playwright passed 169 tests with 1 skip; full-stack Playwright passed 76 tests across Chromium and Firefox. Generated with Codex (gpt-5.6-sol) Co-authored-by: Codex <noreply@openai.com>
Files Changed could paint its toolbar above the active-pane outline and layer the selected-tab accent onto the same edge, producing a broken double border. Nested workspaces also left the enclosing detail leaf highlighted alongside the workflow or terminal that actually owned input. Keep pane ownership visually singular at every depth so the border identifies the surface that receives keyboard and wheel input, without changing layout or control focus styling. Validation: frontend-check passed; TabbedPanelTree and DiffToolbar unit tests passed (26 tests); PR detail pane Playwright passed across Chromium and Firefox (6 tests); full-stack nested issue/workspace/terminal ownership regression passed in Chromium; live Chrome inspection confirmed the corrected strips. Generated with Codex (gpt-5.6-sol) Co-authored-by: Codex <noreply@openai.com>
The rendered pane tree and hosted workspace could disagree after fallback, zoom, or container retirement because nested regions reread persisted focus and the surface dock had no way to claim ownership. That also left diff-local paging and workspace shortcuts active for a different surface. Publish the renderer-validated owner through the pane report and model an external dock claim that ordinary pane activation revokes. This keeps visual ownership and keyboard dispatch singular even when the workspace container is no longer in the pane tree. Validation: frontend-check passed; full frontend unit suite passed with 3,903 tests and 2 skips; PR detail Playwright passed 6 tests across Chromium and Firefox; focused full-stack issue ownership and PR row-only external-dock regressions passed in Chromium. Generated with Codex (gpt-5.6-sol) Co-authored-by: Codex <noreply@openai.com>
roborev: Combined Review (
|
External dock ownership could survive a return to the same pane or the hosted workspace leaving its surface. Agent terminals could also consume wheel input before the dock claimed ownership, so the visible border and keyboard target could disagree. Transfer ownership before deduplicating route callbacks, release external claims with their surface lifecycle, and observe terminal wheel input during capture without moving DOM focus. A browser-focused diff now suppresses native page keys whenever it is not the canonical owner. Generated with Codex (gpt-5.6-sol) Co-authored-by: Codex <noreply@openai.com>
roborev: Combined Review (
|
Pointer and wheel-derived ownership let the active border and pane-scoped PageDown routing disagree with actual browser focus. An unfocused Files Changed pane could therefore consume page keys, and wheel input could appear to transfer focus. Make DOM focus the sole live signal. Keep remembered pane state only for restoration and command targeting, so wheel input remains focus-neutral while deliberate terminal focus still updates the active pane. Generated with Codex (gpt-5.6-sol) Co-authored-by: Codex <noreply@openai.com>
roborev: Combined Review (
|
Dedicated Files routes load with focus on the document body, so focus-only ownership disabled Page Up, Page Down, and file navigation despite the accepted route contract.\n\nKeep the route fallback only until a pane or external dock claims live focus. This preserves focus-only active styling while preventing inactive diff shortcuts from taking input. Generated with Codex Co-authored-by: Codex <noreply@openai.com>
roborev: Combined Review (
|
The Files pane can share its leaf with another tab, so a role-only locator failed before the keyboard-routing assertions ran. Name the Files changed tab explicitly so the cross-browser regression reaches the behavior it is meant to verify. Generated with Codex Co-authored-by: Codex <noreply@openai.com>
roborev: Combined Review (
|
Narrow layouts render one synthetic leaf, so recovering focus ownership from the persisted tree could leave the visible pane without keyboard control. A same-leaf tab replacement could also strand focus on the document body, while Activity commit diffs kept global shortcuts whenever they were visible. Use renderer-owned leaf identity, reclaim only focus that actually falls to the document body, and route Activity commit shortcuts through the same live inputActive contract as other panes. This preserves one keyboard owner without stealing focus from a clicked tab or another control. Review findings fixed: - Narrow-layout rendered leaf identity: #882 (comment) - Activity commit diff ownership: #882 (comment) and #882 (comment) - Same-leaf tab replacement ownership: #882 (comment) and #882 (comment) Generated with Codex Co-authored-by: Codex <noreply@openai.com>
roborev: Combined Review (
|
Responsive flattening, parked workspace hosts, and disappearing sessions could remove the focused DOM node without leaving a current keyboard owner. Blocked workspace actions could also suppress legitimate focus changes, while the dedicated Files fallback could keep handling keys beneath a modal. Keep focus ownership tied to renderer-provided leaf identity, let focus updates pass through disabled Workflow trees, and give modal frames precedence over Files shortcuts. Restore focus after DOM replacement only when the browser leaves it on the document body. Generated with Codex Co-authored-by: Codex <noreply@openai.com>
roborev: Combined Review (
|
Workspace shortcuts could still change background layout while a modal owned input. Hiding focused Details or moving a focused bottom Terminal into Workflow could also leave keyboard focus on the document body. Give modal frames precedence over the workspace sidebar shortcut. When a focused workspace region disappears, restore the workspace root only if no replacement control claimed focus and the host remains visible. Generated with Codex Co-authored-by: Codex <noreply@openai.com>
roborev: Combined Review (
|
Firefox drops DOM focus while a pooled terminal moves through its parking host. The old detail leaf can remain rendered during a split, so disappearance-only ownership tracking let the layout fallback claim focus before the terminal pool restored it.\n\nKeep live ownership with the exact rendered tab when it moves to another leaf. A connected parked terminal keeps its restoration window, while removed content still falls back to the layout host.\n\nThe full unit lane could not complete locally because unrelated tests timed out and left Vitest consuming host CPU; focused units, frontend checks, both mock browser files, Chromium full-stack, and Firefox post-move terminal input passed. Generated with Codex Co-authored-by: Codex <noreply@openai.com>
roborev: Combined Review (
|
A focused inactive tab header could report the selected tab instead of itself, so a later split left live ownership on the wrong pane. The standalone Workspaces shortcut could also toggle its sidebar while application chrome held focus.\n\nReport the exact focused tab from renderer DOM identity. Let Cmd/Ctrl+] run only for unclaimed body focus or focus inside the workspace root, while preserving modal and terminal precedence.\n\nValidation: focused pane units passed; frontend checks passed; workspace shortcut and PR detail browser scopes passed in Chromium and Firefox. Generated with Codex Co-authored-by: Codex <noreply@openai.com>
roborev: Combined Review (
|
History navigation can replace a focused pane subtree while keeping the same leaf and selected tab. No layout state changes and DOM removal may emit no focusout, so the replacement could keep stale ownership while focus sat on the document body.\n\nObserve the renderer boundary through the app Effect runtime. Release ownership only when the recorded focus node is disconnected; a pooled terminal parked elsewhere remains connected and keeps its own restoration path.\n\nValidation: DetailPaneLayout units passed; frontend checks passed; Chromium full-stack terminal move passed; Firefox reached the post-move input assertions before its known later tmux prompt detector failure. Generated with Codex Co-authored-by: Codex <noreply@openai.com>
roborev: Combined Review (
|
Nested tab trees could report an inner session as the outer pane owner. History navigation could also clear the focused node before the layout observer saw that keyed Files content had been replaced, leaving focus on the document body. Keep tab identity within its closest rendered leaf, and retain a no-destination focus record long enough for the Effect-scoped observer to distinguish disconnected content from a connected pooled terminal. Generated with Codex Co-authored-by: Codex <noreply@openai.com>
roborev: Combined Review (
|
An ordinary blur could leave a pane focus record alive until an unrelated later DOM change, which could pull focus back after the user had left. A terminal session could also disappear without focusout while its dock remained visible, leaving stale dock and workspace ownership. Expire no-destination pane records after the immediate replacement window. Track dock focus by exact DOM containment, restore a surviving dock only for disconnected content, and leave connected pooled terminal moves to their own focus handoff. Review decision: focused-leaf removal without a replacement is not an issue. Existing Effect reconciliation covers disconnected pane content, and Collapse Terminal moves focus into its portalled controls before hiding panes. Generated with Codex Co-authored-by: Codex <noreply@openai.com>
roborev: Combined Review (
|
Browsers suspend animation frames in hidden tabs. A no-destination blur could therefore keep a pane or dock focus record alive until later content removal and pull focus back after the user had left. Race the immediate replacement window against the document becoming hidden. This keeps keyed replacement recovery intact while guaranteeing that ordinary blur ownership expires when rendering pauses. Generated with Codex Co-authored-by: Codex <noreply@openai.com>
A focused pooled terminal can leave a detail layout without dispatching focusout. The old leaf then keeps its active border and keyboard ownership even though the terminal is moving through another host. Release the layout claim whenever its exact focused node leaves the renderer. Restore layout focus only for disconnected content, so connected terminal moves remain under the pool's existing focus handoff. Generated with Codex Co-authored-by: Codex <noreply@openai.com>
roborev: Combined Review (
|
The full-stack terminal move regression proved focus and tmux input recovery but did not prove that the old leaf released its active border and keyboard claim. Assert source and destination ownership around the existing real move so the scenario now covers the complete user-visible focus contract without adding another workspace launch. Generated with Codex Co-authored-by: Codex <noreply@openai.com>
roborev: Combined Review (
|
An in-place workspace switch can replace focused Workflow or Details content with loading placeholders before the browser delivers a useful focusout. The old region then keeps stale keyboard ownership while focus falls to the document body. Treat runtime and detail readiness as part of rendered ownership. Keep the guarded one-tick recovery independent of phased readiness reruns so a later phase cannot interrupt the focus handoff it just requested. Generated with Codex Co-authored-by: Codex <noreply@openai.com>
roborev: Combined Review (
|
Command-palette promotion can move a focused pooled terminal while the palette subtree is still inert. Generic Workflow recovery and modal fallback could then replace xterm focus with a layout root, even though the terminal stayed connected and had a destination. Leave connected Workflow content to the pool, queue explicit focus for the exact promoted session, and keep a surviving external dock claim across pane-layout reset. DOM focusout and teardown remain responsible for releasing live ownership. Generated with Codex Co-authored-by: Codex <noreply@openai.com>
roborev: Combined Review (
|
Embedded workspace content can change twice while the browser reports a delayed focusout. The second reactive pass interrupted the one-tick recovery, so focus could remain on the document body after the focused session exited. Let the bounded recovery finish independently of reactive reruns. Its live-focus and connected-content guards still prevent it from replacing a deliberate focus destination or the terminal pool's handoff. Generated with Codex Co-authored-by: Codex <noreply@openai.com>
roborev: Combined Review (
|
Split panes could show an active border on one surface while Page Up or Page Down routed to another. Mousewheel was incorrectly treated as focus, so the UI could claim a pane was active even though browser focus had not moved.
This change makes actual DOM focus the only live source of active-pane state across PR, Issue, Activity, and Workspaces layouts. Wheel scrolling stays local and does not change focus. Deliberate terminal focus still changes the active pane because it moves DOM focus.
Narrow layouts use the exact leaf identity rendered on screen. Responsive flatten and unflatten transitions preserve that renderer-owned identity and reclaim focus only when the browser strands it on the document body. Same-leaf tab replacement uses the same rule.
Workflow focus ownership continues to update while workspace mutations are blocked. Parking a workspace host or removing focused Workflow content clears stale ownership. Readiness changes and Workflow content replacement run bounded post-render recovery independently of reactive reruns, and restore the Workspace root only when no deliberate destination received focus. Connected pooled terminals keep their own restoration path. Successful command-palette promotion queues focus for the exact promoted session. Activity commit diffs use the same live focus ownership as other review panes.
A dedicated Files route retains global diff shortcuts only while no pane, external dock, or modal has live ownership. Once one of those scopes claims the keyboard, the Files diff stops consuming shortcuts.
Workspace sidebar shortcuts yield to active modal frames. If focused Workspace Details or bottom Terminal disappears while the host stays visible, focus returns to the Workspace root only when the browser leaves focus on the document body. Resetting pane arrangement preserves a live external dock claim; DOM focusout, dock unmount, or renderer teardown releases it.
Validation
generated by a clanker