Skip to content

fix(app): keep keyboard focus in opened and switched terminals - #57980

Merged
Brendonovich merged 4 commits into
betafrom
ghostty-terminal-focus
Oct 22, 2026
Merged

Brendonovich merged 4 commits into
betafrom
ghostty-terminal-focus

Conversation

@Brendonovich

@Brendonovich Brendonovich commented Oct 22, 2026 •

Copy link
Copy Markdown
Member

Summary

  • Updates ghostty-web to anomalyco/ghostty-web#5, which hides the cursor while the terminal is unfocused and makes open() mount-only. The branch already includes ghostty-web's mouse reporting fix, so the local ghostty-web@0.3.0 patch is removed.
  • Fixes opening a terminal from the prompt. When a terminal mounted with a focus request, consuming the request flipped autoFocus and the same mount then ran the restore branch, handing focus back to the prompt. The terminal now reads the request once.
  • Fixes ⌘1–9 between terminal tabs. Selecting a tab already requested terminal focus, but the side region focused its selected tab trigger first and the terminal model treated that as focus leaving, cancelling the request. Focus inside the side region no longer cancels it; focus elsewhere, such as the prompt, still does.

ghostty-web#5 is still open, so the dependency points at its head commit (e91e38a). It should move to the merge commit once that lands.

Recording

The recording below shows the panel-opening fix: Ctrl+` from the prompt, hiding and reopening an existing terminal, and Ctrl+Alt+T for a new one, with focus staying in the terminal each time. It doesn't show the ⌘1–9 fix.

terminal-focus-fix.mp4

Validation

  • packages/app/component-tests/terminal-focus.spec.ts: a terminal opened with a focus request keeps focus, and one opened without a request leaves focus in the composer. The first case fails without the fix; 10/10 repeats pass at 2 workers.
  • packages/gui-extensions/src/terminal/model.test.ts: focus moving within the side region keeps a terminal focus request, and focus leaving it cancels the request. The first case fails without the fix.
  • bun test and bun typecheck in packages/gui-extensions; bun typecheck in packages/app.
  • Tried in the desktop dev app: Ctrl+` from the prompt, ⌘2/3/1/3/2/2, clicking the prompt then ⌘1 (stays in the prompt), Ctrl+Alt+T, then ⌘1 and ⌘9. Focus ended where expected at every step.
  • Pushed with --no-verify. The pre-push check fails on a type error already on beta: packages/app/src/session/composer/activity-chips.fixture.tsx imports MarkedProvider from @opencode/ui/context/marked, which feat(app): highlight code with @pierre/highlights instead of Shiki #57937 removed. Nothing else in the check failed.

Added by the opencode bot (AI-generated).

Fixes #57536

Related: #55992

@Brendonovich
Brendonovich requested a review from Hona as a code owner October 22, 2026 01:37
@Brendonovich
Brendonovich merged commit 7ea9e17 into beta Oct 22, 2026
13 of 20 checks passed
@Brendonovich
Brendonovich deleted the ghostty-terminal-focus branch October 22, 2026 01:38

@opencode-agent opencode-agent 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.

The terminal.tsx change looks right: reading the focus request once stops the same mount from running the restore branch and handing focus back to the prompt. I couldn't run the app here because there's no browser in the sandbox, so I only checked the model test (it passes, and fails with the base model.ts) and the type checks (gui-extensions is clean; app fails only on the existing activity-chips.fixture.tsx error you mentioned).

Public API: unchanged. This is an internal dependency bump plus focus handling inside the app.

Concerns:

  • The #review-panel exemption in model.ts is broader than the ⌘1–9 case needs. See the inline comment.
  • ghostty-web is pinned to the head commit of an open PR. If that branch is rebased or squash-merged before this lands, the commit could disappear and installs would break. It would be safer to wait for ghostty-web#5 to merge and pin the merge commit.

Fixes #57536
Related: #55992


if (event.target.closest("#terminal-panel")) return
// The side region focuses its selected tab trigger or root while switching tabs; only focus leaving it cancels.
if (event.target.closest("#terminal-panel, #review-panel")) return

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.

This exempts focus anywhere in the side region, not just the tab trigger or root that the tab switch focuses. Any request that hasn't been used yet now outlives the user moving to other side-panel content. Two examples:

  • Ctrl+Alt+T, then clicking into the file tree or review diff before pty.create resolves. The pending request survives, and the new terminal takes focus when it mounts.
  • ⌘2 to a terminal, then ⌘3 to a non-terminal tab before the request is used. The request stays open and later takes focus when that terminal becomes active again.

Could this only ignore the elements the switch itself focuses (the #review-panel root and its tab triggers, e.g. event.target.id === "review-panel" || event.target.closest('#review-panel [role="tab"]'))? Also, this would no longer tie the terminal extension to all of the host's panel DOM.

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