Skip to content

feat(native-chat): focus the message box when a chat appears - #19868

Merged
brennanb2025 merged 2 commits into
mainfrom
brennanb2025/focus-native-chat-textbox
Sep 10, 2026
Merged

brennanb2025 merged 2 commits into
mainfrom
brennanb2025/focus-native-chat-textbox

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 7 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​687 $\color{#cf222e}{\Huge{\mathbf{−}}}$​7 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​680
Prod 9 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​163 $\color{#cf222e}{\Huge{\mathbf{−}}}$​4 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​159

ELI5

You open a chat and the cursor isn't in the message box, so you have to click before you can type. Now it's already there.

What Changed

A new hook, useNativeChatComposerRevealFocus, focuses the chat composer whenever a chat pane reveals it. It's called from both chat views, which already held a composerRef, so nothing new had to be plumbed.

It fires on the reveal edge, not on mount. Structured panes are retained — hidden with display:none + inert and never unmounted on a tab switch (RetainedPaneHost.tsx:145-157) — so a mount-only effect would miss every switch-back. Covered events: a new chat tab, a worktree-create that lands in chat, the Cmd/Ctrl+Shift+J view toggle, and re-selecting an existing chat tab.

It reuses the existing NativeChatComposerHandle.focus() seam and the existing shouldPreserveEditableFocus predicate (pane-helpers.ts, now exported) rather than adding a parallel mechanism. No Tiptap autofocus — that would be a second, competing path.

The first attempt is deferred one frame and retried over a bounded run of frames, because two things can beat a same-tick take: Tiptap publishes its imperative adapter only after EditorContent attaches, and Radix's FocusScope restores a closing dialog's trigger in a setTimeout(0). The verification stops once focus remains in the pane, so the happy path is two frames. If a closing dialog restores focus afterward, the next frame reclaims the composer. Pointer input or Tab/Shift+Tab navigation cancels the remaining attempts so user intent wins; printable keys do not cancel the initial claim.

Two supporting changes:

isFocusedGroup, derived from activeGroupIdByWorktree. On worktree activation isWorktreeActive flips once for the whole layer, so in a 2-column split both chat panes go visible in the same commit and would fight over the caret. The bridge route already had this bit as controller.isActive (TerminalPaneOverlayLayer.tsx:142); only the structured overlay needed it added.

A focus-steal fix in focusRuntimeTerminalSurface. Legacy chat is a terminal tab with viewMode: 'chat' whose xterm stays mounted underneath. The DOM-path twin already declines chat view via data-terminal-chat-view (focus-terminal-tab-surface.ts:74-77), but the runtime path — which queueWorkspaceActivationTerminalFocus tries first — focused the covered xterm unconditionally and pulled the caret back out of the composer. The runtime path now resolves any explicit leaf before applying that policy. It activates an uncovered requested leaf with { focus: true }, and a covered requested chat leaf with { focus: false }; that preserves leaf selection without focusing the hidden xterm. Returning true still prevents the DOM fallback from targeting an unrelated xterm.

Why

Nothing in the chat surface focused the composer on open. The only existing focus calls were reactive — typing on the bridge pane background, accepting a picker item, resolving an attachment, starting dictation — and the structured pane had none of them. The view-mode toggle ended at toggleTabViewMode and focused nothing.

A pane-local reveal effect was chosen over a store flag like the browser's pendingAddressBarFocusByTabId. That flag carries a one-shot creation intent across a mount gap and is consumed so revisits don't refocus — the opposite of what's wanted here. It would need re-arming at eight sites, each a silent bug if missed, all re-deriving what the pane layer already knows.

Linked Issue

No existing open issue covers this; it came from a direct request. Happy to file one and link it if that's preferred.

Visual Proof

Same flow both times: open a chat from the tab-bar "+" → Claude, then type typing without clicking anything.

BEFORE — document.activeElement is BODY, the six keystrokes go nowhere, composer still shows its placeholder:

orca-pr-BEFORE.png

AFTER — focus is the composer's ProseMirror contenteditable and the text lands in it:

orca-pr-AFTER.png

Captured on macOS against this branch, in one app session, with only the hook toggled between the two shots — so the difference is attributable to this change and not to setup drift.

Testing

Ran in the real app (macOS) on isolated CDP ports and throwaway profiles, driving the actual tab controls. The final current-source Electron evidence covers the settled source:

  • New chat tab focuses the composer, and typing lands there with no click — verified on both routes: the portal/bridge chat, and a structured agent-session chat.
  • Switching away to another tab and back re-focuses the composer.
  • Ablation: with the hook disabled via HMR, the identical flow leaves document.activeElement on BODY. Re-enabled, it focuses again.

Automated:

  • native-chat-composer-reveal-focus.test.tsx — 17 tests: reveal/focused-group edges, split ownership, latch/re-arm behavior, bounded delayed-restoration recovery, preserving editable focus, pointer and Tab cancellation, immediate printable-key behavior, and covered-xterm handoff.
  • focus-runtime-terminal-surface-chat-view.test.ts — explicit covered and uncovered leaf activation, including mixed splits.
  • TerminalPaneNativeChatPortal.test.tsx — active-leaf focus ownership on both PTY-backed and structured routes.
  • Extended workspace-activation-terminal-focus.test.ts, StructuredAgentSessionPaneOverlayLayer.test.tsx, and fixed container-less fake panes in sync-runtime-graph-terminal-registration-ownership.test.ts.

Each guard was ablated individually and confirmed to turn its test red. Worth calling out: the first version of the hook test passed for the wrong reason — the four guards masked each other pairwise, so removing the latch or the in-pane check changed nothing. Two tests were added that isolate them.

  • I manually tested these changes locally
  • Automated tests added/updated, or explained why not below

Gates on the final review source: pnpm tc clean, 7 focused files / 61 tests green (including 17 hook and 2 portal cases), check:code-quality:changed reports 0 new findings, oxlint clean, and git diff --check clean. Post-push CI is green: 20 successful checks, 14 intentionally skipped, and no failures or pending checks.

Not covered: Linux and Windows were not exercised (no platform-specific code in this change — no keyboard modifiers, no paths). SSH/remote was not exercised live; the runtime-path guard is the piece that matters there and it is unit-tested. The batch worktree-create case — several worktrees in sequence, where the composer must not steal the modal's next name field — is covered by unit test but was not exercised live.

AI Disclosure

Claude Opus 4.5 via Claude Code and Codex.

Review

Agent skill upstream boundary

  • Not applicable, or this change follows docs/reference/agent-skill-sharing-upstream-boundary.md and copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

  • Cross-platform: no platform-dependent code. No metaKey hardcoding, no path handling, no shell.
  • Remote SSH: the runtime focus path is the SSH-relevant surface; the guard makes it decline a chat-covered pane, which is strictly less focus-stealing than before. No execution-host semantics touched.
  • Remote wire: no RPC params, stream frames, or published content changed.
  • Mobile: untouched. The composer's canSend still reflects the mobile presence-lock; a locked composer is non-editable, so focus() returns false and the bounded retry simply expires.
  • Performance: one effect per chat pane, running only on a reveal edge, latched to at most one claim per visibility episode; at most 6 frames and normally 2. Temporary document listeners are removed on success, cancellation, exhaustion, dependency changes, and unmount.
  • Backwards compatibility: isFocusedGroup is a required prop deliberately — an omitted prop would silently disable the feature for a route, so the typecheck is the forcing function.

Checklist

  • This PR is small and focused
  • I explained what changed and why (including ELI5)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

Opening a native chat left focus nowhere, so you had to click the
composer before typing. Nothing in the chat surface focused it on open;
the only existing focus calls were reactive (typing on the bridge pane
background, picker acceptance, attachments, dictation), and the
structured pane had none of those.

useNativeChatComposerRevealFocus focuses the composer on the reveal
edge, covering a new chat tab, a worktree-create landing in chat, the
chat-view toggle, and switching back to an existing chat tab. Mount is
the wrong signal: retained panes hide with display:none + inert and
never unmount on a tab switch. It reuses the existing composer handle
and shouldPreserveEditableFocus rather than adding a parallel path, and
retries across a bounded run of frames because Tiptap publishes its
adapter after mount and Radix restores a closing dialog's trigger in a
setTimeout(0).

Two supporting changes:

- isFocusedGroup, from activeGroupIdByWorktree. On worktree activation
  both columns of a split flip visible in the same commit, so without it
  two revealed chats fight over the caret. The bridge route already had
  this bit as controller.isActive; only the structured overlay needed it.

- focusRuntimeTerminalSurface bails on a chat-covered pane. Its DOM-path
  twin already declines chat view via data-terminal-chat-view, but the
  runtime path focused the covered xterm unconditionally and pulled the
  caret out of the composer. Returns true, not false: false sends the
  caller to the DOM fallback, which for a structured tab id focuses an
  unrelated tab's xterm.
@coderabbitai

coderabbitai Bot commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 7b23d5a1-d7de-444c-901c-e215655e2a6e

📥 Commits

Reviewing files that changed from the base of the PR and between b085b25 and 3ad37df.

📒 Files selected for processing (6)
  • src/renderer/src/components/native-chat/native-chat-composer-reveal-focus.test.tsx
  • src/renderer/src/components/native-chat/use-native-chat-composer-reveal-focus.ts
  • src/renderer/src/components/terminal-pane/TerminalPaneNativeChatPortal.test.tsx
  • src/renderer/src/components/terminal-pane/TerminalPaneNativeChatPortal.tsx
  • src/renderer/src/runtime/focus-runtime-terminal-surface-chat-view.test.ts
  • src/renderer/src/runtime/sync-runtime-graph.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • src/renderer/src/components/terminal-pane/TerminalPaneNativeChatPortal.tsx
  • src/renderer/src/runtime/sync-runtime-graph.ts
  • src/renderer/src/runtime/focus-runtime-terminal-surface-chat-view.test.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 6 remain after this review.


📝 Walkthrough

Walkthrough

The change propagates isFocusedGroup through native chat views and overlay layers. A new hook focuses the composer when the active chat pane becomes visible, while preserving existing editable focus and retrying until the composer is ready. Runtime terminal focus now stops when native chat covers the pane. Tests cover focused split groups, composer focus behavior, covered terminal surfaces, and updated test fixtures.

Merge Risk: ⚪ Minimal · up to 3ad37

The change adds reveal-edge chat composer autofocus while preserving user-selected focus and preventing covered terminals from reclaiming it. Current evidence shows the behavior is ready to merge.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.53% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 17 functions across 16 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ⚠️ Warning The description is detailed and covers the change, rationale, testing, visual proof, AI disclosure, risks, and checklist. However, the required Linked Issue section does not contain an issue link or a… Add the issue reference in the required format, such as Fixes #1234``. If no issue exists, create or link the appropriate issue before merging.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly and concisely describes the primary change: focusing the native chat message box when a chat appears.
Full details: Description check

Explanation

The description is detailed and covers the change, rationale, testing, visual proof, AI disclosure, risks, and checklist. However, the required Linked Issue section does not contain an issue link or a Fixes #... reference; it explicitly states that no issue exists.

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Advanced

Run ID: 8ad25015-4853-49f4-865f-cf5bb9a6f370

📥 Commits

Reviewing files that changed from the base of the PR and between 2bf298d and b085b25.

📒 Files selected for processing (15)
  • src/renderer/src/components/native-chat/NativeChatResolvedView.tsx
  • src/renderer/src/components/native-chat/NativeChatStructuredSession.test.tsx
  • src/renderer/src/components/native-chat/NativeChatStructuredSession.tsx
  • src/renderer/src/components/native-chat/NativeChatView.tsx
  • src/renderer/src/components/native-chat/StructuredAgentSessionPaneOverlayLayer.test.tsx
  • src/renderer/src/components/native-chat/StructuredAgentSessionPaneOverlayLayer.tsx
  • src/renderer/src/components/native-chat/native-chat-composer-reveal-focus.test.tsx
  • src/renderer/src/components/native-chat/native-chat-view-types.ts
  • src/renderer/src/components/native-chat/use-native-chat-composer-reveal-focus.ts
  • src/renderer/src/components/terminal-pane/TerminalPaneNativeChatPortal.tsx
  • src/renderer/src/components/terminal-pane/pane-helpers.ts
  • src/renderer/src/lib/workspace-activation-terminal-focus.test.ts
  • src/renderer/src/runtime/focus-runtime-terminal-surface-chat-view.test.ts
  • src/renderer/src/runtime/sync-runtime-graph-terminal-registration-ownership.test.ts
  • src/renderer/src/runtime/sync-runtime-graph.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 2 remain after this review.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ℹ️ No critical issues — one minor test-hygiene nit inline.

Reviewed changes

  • useNativeChatComposerRevealFocus hook — focuses the composer on the isVisible && isFocusedGroup reveal edge (not mount, since retained panes never unmount), deferring one frame and retrying over a bounded run of frames until the caret actually lands in the pane.
  • isFocusedGroup prop threading — a new required prop across all three native-chat view types; the structured overlay derives it from activeGroupIdByWorktree while the bridge route reuses the controller's existing isActive.
  • focusRuntimeTerminalSurface chat-view guard — bails (return true) when the active pane is covered by the chat portal, so worktree activation no longer focuses the covered xterm and pulls the caret out of the composer; mirrors the DOM path's data-terminal-chat-view bail.
  • Test coverage — 14 hook tests (including latch and in-pane-check isolation), a runtime-guard suite, plus updates to the overlay-layer, workspace-activation, and registration-ownership tests.

I traced the focus flow end-to-end: the reveal-edge latching, the isFocusedGroup derivation on both routes (StructuredAgentSessionPaneOverlayLayer.tsx and the bridge route's controller.isActive), and the runtime guard's true-vs-false return contract against the DOM fallback. The runtime guard checks the active pane rather than a requested leafId, but that matches the DOM path's data-terminal-chat-view (activePaneIsChatLeaf) semantics, so it is not a new regression. Ran the four touched/new test suites locally (31 tests green).

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@@ -0,0 +1,248 @@
// @vitest-environment happy-dom

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The createRoot + act harness in this file doesn't set globalThis.IS_REACT_ACT_ENVIRONMENT = true, so every run emits The current testing environment is not configured to support act(...) warnings (visible in the test output; tests still pass). Setting it once at module top silences the noise and matches the convention used by NativeChatTranscriptChrome.test.tsx and native-chat-working-status-shared-clock.test.tsx.

Suggested change
// @vitest-environment happy-dom
// @vitest-environment happy-dom
globalThis.IS_REACT_ACT_ENVIRONMENT = true

@brennanb2025

brennanb2025 commented Sep 10, 2026 •

Copy link
Copy Markdown
Contributor Author

Final Electron QA evidence — rerun after the latest uncommitted review fixes settled

Identity and launch:

  • window.api.app.getIdentity() returned Orca: brennanb2025/focus-native-chat-textbox, dev label umibozu-3 @ brennanb2025/focus-native-chat-textbox, and repo root /Users/brennanbenson/orca/workspaces/orca/umibozu-3.
  • Exact launch/attach commands were:
ORCA_BACKGROUND_LAUNCH=1 REMOTE_DEBUGGING_PORT=9343 ORCA_DEV_USER_DATA_PATH=/tmp/orca-electron-profile-umibozu3-final.EfzEJL node config/scripts/run-electron-vite-dev.mjs -c /tmp/orca-electron-vite-umibozu3-5182.mjs
command playwright-cli -s=umibozu3_final attach --cdp=http://127.0.0.1:9343
command playwright-cli -s=umibozu3_final eval "(async () => JSON.stringify(await window.api.app.getIdentity()))()"
  • Renderer port 5182 and CDP port 9343 were free before launch. The runner logged Background launch: window stays off screen; automate through CDP; every interaction and screenshot used Playwright CDP, with no OS input or window activation.

Observed behavior:

  1. Through the rendered UI, enabled Experimental → Chat UI, created a fresh Codex tab, and opened its chat view. Before any composer click, document.activeElement was DIV[role="textbox"][contenteditable="true"][aria-label="Send a message…"] inside .native-chat-pane-shell; store evidence was viewMode: "chat". page.keyboard.type('FINAL QA new chat: typing landed without click') landed in that composer.
  2. Switched via rendered tab controls to Terminal 1 and back. Before typing again, document.activeElement was again the same chat textbox with the first draft intact; page.keyboard.type(' | FINAL after switch away and back') appended to that draft without a composer click.
  3. Clicked the rendered Split Terminal Right control, leaving the left leaf chat-covered and the right terminal leaf active, then switched away and back. On reveal, document.activeElement remained the right leaf's TEXTAREA.xterm-helper-textarea[aria-label="Terminal input"] (leaf aceb5f2c-d972-4cdb-94de-66821c2c81fb, pane 2, rect x=834,width=544); the composer remained in distinct left leaf bc0505f6-dfcb-4a83-b028-16dd5337d838 (rect x=281,width=544), and the active-pane overlay matched the right pane. Without clicking either pane, keyboard input executed echo FINAL_QA_SPLIT_TERMINAL_FOCUS_PRESERVED; window.api.pty.getMainBufferSnapshot() returned source: "headless" and contained the marker, while the chat draft remained unchanged and did not contain it.

Fresh native chat composer accepted typing without a composer click

Composer reclaimed focus and accepted appended typing after switching away and back

Split reveal preserved focus on the active right terminal sibling

Final source status/mtimes stayed unchanged through this rerun. A transient host network change occurred while the worker was idle, so I reloaded the hidden renderer before the final scenarios; the recovered final page reported 0 renderer console errors and one unrelated outdated-React-Grab warning. Gap: I did not submit the draft to an agent, so this covers rendered reveal/focus and input routing rather than a billed/model response; the requested Grok attempt remains unavailable because its TUI reported zero remaining usage before execution.

@pullfrog pullfrog Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

ℹ️ No new issues in this delta — the split-ownership, user-intent, and runtime-leaf changes are clean. One prior test-hygiene nit remains unaddressed (inline).

Reviewed changes

  • Split-group focus ownership — TerminalPaneNativeChatPortal.tsx now passes isFocusedGroup={isActive && activePaneIsChatLeaf} instead of isActive alone, so only the chat leaf's column claims the caret and an active split terminal sibling keeps focus.
  • User-intent cancellation in the reveal hook — use-native-chat-composer-reveal-focus.ts registers pointerdown and Tab/Shift+Tab listeners (capture phase) that cancel pending retries via a finish() helper; printable keys still don't cancel the initial claim, and delayed programmatic focus restoration is still reclaimed.
  • Explicit-leaf resolution in focusRuntimeTerminalSurface — sync-runtime-graph.ts resolves a requested leafId and activates the requested pane with focus: !paneIsCoveredByNativeChat(requestedPane), so a covered chat leaf activates without focusing its xterm while an uncovered split terminal leaf in the same tab still takes focus.
  • New and updated tests — TerminalPaneNativeChatPortal.test.tsx ownership checks, four covered/uncovered leaf-activation cases in focus-runtime-terminal-surface-chat-view.test.ts, plus hook tests isolating user-intent cancellation from delayed programmatic restoration.

I traced the delta end-to-end: the isFocusedGroup derivation against activePaneIsChatLeaf (use-terminal-pane-projection.ts:160), the runtime path's new leafId branch against resolveLeafIdForManager and paneIsCoveredByNativeChat, and the hook's cleanup/finish() lifecycle. The new tests assert exact values ({ focus: false } / { focus: true }, toBe(1)/toBe(2)), not loose predicates, and each would fail on regression.

Pullfrog  | Fix all ➔ | Fix 👍s ➔ | View workflow run | Using DeepSeek Pro (free via Pullfrog for OSS) | 𝕏

@@ -0,0 +1,290 @@
// @vitest-environment happy-dom

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Still unaddressed from the prior review: the createRoot + act harness in this file doesn't set globalThis.IS_REACT_ACT_ENVIRONMENT = true, so every run emits The current testing environment is not configured to support act(...) warnings (tests still pass).

Suggested change
// @vitest-environment happy-dom
// @vitest-environment happy-dom
globalThis.IS_REACT_ACT_ENVIRONMENT = true

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Acknowledged. This is nonfunctional test-harness warning hygiene, and the tests plus CI pass. I am leaving it out to keep this review commit limited to meaningful behavior and regression coverage.

@brennanb2025

Copy link
Copy Markdown
Contributor Author

Review status: CLEAN — recommend land

The review/fix commit is 3ad37df. Two adversarial review rounds and the closing readiness audit found no remaining in-scope P0, P1, or P2 issue.

Issues fixed

  • Native-chat composer ownership now requires both the active tab/group and the active internal split leaf, on PTY-backed and structured routes. A chat sibling can no longer steal focus from the active terminal leaf.
  • Explicit runtime focus requests resolve and activate the requested leaf before applying chat-cover policy. Covered chat leaves activate without focusing their hidden xterm; uncovered terminal leaves still receive focus.
  • Reveal focus now uses bounded verification to recover from delayed dialog/editor focus restoration, while pointer input and Tab or Shift+Tab navigation cancel remaining attempts so user intent wins.

Functional correctness

PASS. New-chat typing, switch-away/back focus, covered-chat activation, and mixed chat/terminal split routing are covered. The final Electron run visibly proved that typing lands in a fresh composer without a click, focus returns after switching away and back, and a revealed mixed split preserves the active terminal sibling and routes input to its PTY.

Final Electron screenshots and backing state: #19868 (comment)

Architecture and precedent

PASS. Focus ownership and direct delivery follow the active group/leaf model: the selected leaf is activated, and only the surface that actually owns that leaf may claim focus. The bounded six-frame verification is a deliberate local lifecycle adaptation for retained panes and delayed focus restoration; it is not used as a replacement for ownership.

Performance and resources

PASS. At most six animation-frame attempts run per reveal, normally two, with only one frame pending at once. Temporary document listeners are removed on success, cancellation, exhaustion, dependency change, and unmount. No polling, subprocess churn, broad scans, caches, persistent growth, or hot-path fanout was added.

Validation

  • Same-model review/fix loop: clean after two rounds.
  • Opening and closing readiness reviews: PASS/CLEAN.
  • Focused suite: 7 files, 61 tests; hook suite 17/17; portal suite 2/2.
  • pnpm tc, changed-code quality checks, React-specific lint, and git diff --check: passed.
  • Post-push CI: 20 successful, 14 intentionally skipped, 0 failing, 0 pending. This includes all eight test shards, typecheck, static analysis, macOS/Linux package, Windows package, changed e2e, and aggregate verify.

Compatibility and remaining gaps

No mobile-facing UI, persistence, RPC/wire, process, shell, path, or platform-specific behavior changed, so mobile emulator, Windows/WSL live UI, and SSH live QA were inapplicable. The Electron run validated focus and input routing rather than submitting a billed model request. The requested Grok lane had no remaining usage, so the final Electron validation was completed by the fallback QA worker.

Remaining in-scope issues: none.

@brennanb2025
brennanb2025 merged commit 5a96158 into main Sep 10, 2026
34 checks passed
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