Skip to content

[codex] Port v2 terminal hotkeys to v1#3724

Merged
Kitenite merged 2 commits into
mainfrom
port-v2-terminal-v1
Apr 25, 2026
Merged

[codex] Port v2 terminal hotkeys to v1#3724
Kitenite merged 2 commits into
mainfrom
port-v2-terminal-v1

Conversation

@Kitenite
Copy link
Copy Markdown
Collaborator

@Kitenite Kitenite commented Apr 25, 2026

Summary

  • Port v2 terminal hotkey routing to v1 so registered app hotkeys escape xterm before terminal-specific handling.
  • Route CLEAR_TERMINAL through the shared terminal hotkey hook while still clearing xterm display and persisted scrollback.
  • Share the v2 line-edit chord translator with v1, and prevent browser defaults when v1 consumes Cmd/Option/Ctrl line-edit chords.
  • Remove the brittle keyboard-handler unit test that imported app hotkey hooks and crashed CI in Bun's test environment.

Root Cause

V1 still handled clear and some shortcut routing inside the terminal custom key handler. That meant terminal-specific handling could win before the shared hotkey resolver, while v2 had already moved to the safer model where registered app hotkeys escape xterm first.

The review also identified that consumed Option/Ctrl word-navigation translations wrote terminal bytes without calling preventDefault(), which could leak browser navigation/editing behavior in Electron.

Validation

  • bun run test from apps/desktop - 1815 pass, 0 fail
  • bun run typecheck from apps/desktop
  • bunx biome check on touched terminal/hotkey files
  • git diff --check

Summary by CodeRabbit

  • Refactor
    • Reorganized terminal keyboard event handling and hotkey routing to improve code structure and maintainability without affecting user-visible functionality.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 25, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 1c16dd47-1964-44b8-9822-b5d72166f235

📥 Commits

Reviewing files that changed from the base of the PR and between a91a083 and 730dfd2.

📒 Files selected for processing (3)
  • apps/desktop/src/renderer/lib/terminal/line-edit-translations.ts
  • apps/desktop/src/renderer/lib/terminal/terminal-runtime.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/terminalKeyboardHandler.ts

📝 Walkthrough

Walkthrough

The changes refactor terminal keyboard handling by extracting logic into dedicated modules (terminalKeyboardHandler.ts, line-edit-translations.ts) and centralizing the clear-terminal hotkey through a callback-based approach. The keyboard handler now properly evaluates registered app hotkeys first, allowing them to bubble beyond the terminal while platform-specific line-edit chords are translated separately.

Changes

Cohort / File(s) Summary
Terminal Component & Hotkey Hooks
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx, hooks/useTerminalHotkeys.ts, hooks/useTerminalLifecycle.ts
Terminal component now defines handleClearHotkey to clear xterm display and scrollback, passing it as onClear callback to useTerminalHotkeys. Hook updated to accept and invoke onClear when CLEAR_TERMINAL hotkey triggers. Lifecycle hook sources keyboard handler from new terminalKeyboardHandler module and removes keyboard-level clear handling.
Keyboard Handler Refactoring
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts, terminalKeyboardHandler.ts
Removed 207 lines of keyboard handler implementation from helpers.ts (deleting setupKeyboardHandler function and KeyboardHandlerOptions interface). New dedicated terminalKeyboardHandler.ts module introduces functionally similar handler with semantic change: now checks resolveHotkeyFromEvent first to allow app hotkeys to bubble, preventing unbound modifier chords from being swallowed by xterm.
Line-Edit Translation Extraction
apps/desktop/src/renderer/lib/terminal/line-edit-translations.ts, terminal-runtime.ts
New module line-edit-translations.ts exports translateLineEditChord function and LineEditChordOptions interface, encapsulating platform-specific terminal chord translation logic. File terminal-runtime.ts updated to import and delegate translation to the new module, removing 41 lines of local implementation.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Terminal
    participant useTerminalHotkeys
    participant terminalKeyboardHandler
    participant xterm

    User->>terminalKeyboardHandler: Keyboard Event
    terminalKeyboardHandler->>terminalKeyboardHandler: Check resolveHotkeyFromEvent()
    alt App Hotkey Registered
        terminalKeyboardHandler-->>User: Return false (bubble hotkey)
    else Shift+Enter
        terminalKeyboardHandler->>terminalKeyboardHandler: preventDefault()
        terminalKeyboardHandler->>useTerminalHotkeys: onShiftEnter()
        terminalKeyboardHandler-->>User: Return false
    else Line-Edit Chord
        terminalKeyboardHandler->>terminalKeyboardHandler: translateLineEditChord()
        terminalKeyboardHandler->>useTerminalHotkeys: onWrite(translation)
        terminalKeyboardHandler-->>User: Return false
    else Select All / Clipboard
        terminalKeyboardHandler->>xterm: selectAll() or handle clipboard
        terminalKeyboardHandler-->>User: Return false
    else Other Keys
        terminalKeyboardHandler-->>xterm: Return true (xterm handles)
    end

    alt CLEAR_TERMINAL Hotkey Triggered
        useTerminalHotkeys->>Terminal: onClear()
        Terminal->>xterm: clear()
        Terminal->>Terminal: clearScrollbackRef(paneId)
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • Unbound Ctrl/Cmd chord handling in terminal — The refactored keyboard handler now evaluates registered app hotkeys via resolveHotkeyFromEvent first and returns false to allow hotkey bubbling, directly addressing the issue where modifier chords were being consumed by xterm instead of reaching the application hotkey system.

Poem

🐰 Hops through hotkeys with newfound grace,
Chords bubble up to their rightful place,
Clear terminals with a callback's call,
Keyboard logic, refactored and tall!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically summarizes the main change: porting v2 terminal hotkeys to v1, which aligns perfectly with the core objective of refactoring terminal hotkey handling.
Description check ✅ Passed The description provides a clear summary, explains the root cause, includes validation steps, and generally addresses most required sections, though the 'Type of Change' section from the template is not explicitly filled.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch port-v2-terminal-v1

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 and usage tips.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 25, 2026

Greptile Summary

This PR ports the v2 terminal hotkey model to v1 by moving CLEAR_TERMINAL out of the xterm custom key handler and into the shared useHotkey hook, and by making resolveHotkeyFromEvent run first in the custom handler (before line-edit translations or macOS Cmd bubbling). The setupKeyboardHandler is extracted into its own terminalKeyboardHandler.ts module and its interface drops the onClear option. Regression tests cover hotkey bubbling, override precedence, line-edit translations, and Cmd+A select-all.

Confidence Score: 5/5

Safe to merge; refactor is behaviorally correct and all changed paths are covered by new regression tests.

No P0/P1 issues found. The key ordering change (resolveHotkeyFromEvent first) intentionally matches v2 and is explicitly tested. CLEAR_TERMINAL routing through useHotkey is clean. The only remaining items are pre-existing style asymmetries (e.g., isMac not guarding isCmdLeft/Right/Backspace) that are unchanged by this PR.

No files require special attention.

Important Files Changed

Filename Overview
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/terminalKeyboardHandler.ts New module extracted from helpers.ts; key change is resolveHotkeyFromEvent checked first so registered app hotkeys always bubble before line-edit translations.
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/terminalKeyboardHandler.test.ts New test file; covers hotkey bubbling priority, override precedence, line-edit fallback, and Cmd+A select-all; all scenarios look correct.
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalHotkeys.ts CLEAR_TERMINAL wired through useHotkey hook with onClear callback; clean addition that correctly takes over from the removed handler-level check.
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx handleClearHotkey useCallback correctly accesses xtermRef.current at call time and is passed as onClear; no issues.
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts setupKeyboardHandler and KeyboardHandlerOptions removed; now imported from terminalKeyboardHandler; no leftover dead code.
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts onClear removed from setupKeyboardHandler call; import updated to new module; handleClear still registered via registerClearCallbackRef for programmatic clearing.

Sequence Diagram

sequenceDiagram
    participant User
    participant xterm
    participant KeyHandler as terminalKeyboardHandler
    participant Document
    participant useTerminalHotkeys

    User->>xterm: Keypress (e.g. CLEAR_TERMINAL chord)
    xterm->>KeyHandler: attachCustomKeyEventHandler(event)
    KeyHandler->>KeyHandler: resolveHotkeyFromEvent(event) !== null?
    alt Registered app hotkey (e.g. CLEAR_TERMINAL)
        KeyHandler-->>xterm: return false (bubble)
        xterm-->>Document: Event propagates to document
        Document->>useTerminalHotkeys: useHotkey("CLEAR_TERMINAL") fires
        useTerminalHotkeys->>xterm: xterm.clear()
        useTerminalHotkeys->>useTerminalHotkeys: clearScrollbackRef({ paneId })
    else Line-edit translation (e.g. Cmd+Left, unregistered)
        KeyHandler->>KeyHandler: Apply translation (Ctrl+A, Ctrl+E...)
        KeyHandler-->>xterm: return false (handled in-process)
    else Terminal-reserved chord (Ctrl+C/D/Z..., unregistered)
        KeyHandler-->>xterm: return true (goes to PTY)
    end
Loading

Reviews (1): Last reviewed commit: "port v2 terminal hotkeys to v1" | Re-trigger Greptile

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (3)
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/terminalKeyboardHandler.ts (1)

183-187: Dead branch: isTerminalReservedEvent check is unreachable in effect.

Both arms return true, so the conditional has no observable effect today. Either drop it or add a side-effect/log to make the intent meaningful. If kept as documentation-by-code, a brief comment plus a single return would be clearer.

♻️ Suggested simplification
-		// Unregistered terminal-reserved chords (ctrl+c/d/z/s/q) stay with xterm.
-		if (isTerminalReservedEvent(event)) return true;
-
-		return true;
+		// Default: let xterm process the event (covers terminal-reserved chords
+		// like ctrl+c/d/z/s/q and any other unhandled keys).
+		return true;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/terminalKeyboardHandler.ts`
around lines 183 - 187, The conditional that checks
isTerminalReservedEvent(event) is dead because both branches return true; remove
the redundant if and replace it with a single return true, keeping or expanding
the inline comment that explains terminal-reserved chords should be handled by
xterm (so the intent remains clear); update the handler function (the closure
returned by terminalKeyboardHandler) to eliminate the unreachable branch and
leave a concise comment referencing terminal-reserved chords.
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/terminalKeyboardHandler.test.ts (2)

68-76: Optional: also restore navigator in afterEach for cross-suite isolation.

originalOverrides is restored, but navigator is left mutated to MacIntel. Cheap to add a snapshot/restore for symmetry, in case future tests in the same worker depend on the real platform.

♻️ Suggested restore
 describe("setupKeyboardHandler", () => {
 	let originalOverrides: Record<string, string | null>;
+	let originalNavigator: PropertyDescriptor | undefined;

 	beforeEach(() => {
+		originalNavigator = Object.getOwnPropertyDescriptor(globalThis, "navigator");
 		setPlatform("MacIntel");
 		originalOverrides = useHotkeyOverridesStore.getState().overrides;
 		useHotkeyOverridesStore.setState({ overrides: {} });
 	});

 	afterEach(() => {
 		useHotkeyOverridesStore.setState({ overrides: originalOverrides });
+		if (originalNavigator) {
+			Object.defineProperty(globalThis, "navigator", originalNavigator);
+		}
 	});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/terminalKeyboardHandler.test.ts`
around lines 68 - 76, The test mutates navigator via setPlatform("MacIntel") in
beforeEach but never restores it; update the afterEach to also restore navigator
to its original value by capturing the original navigator/platform before
calling setPlatform (e.g., save current navigator or its platform value) and
then call setPlatform with that saved value in afterEach so setPlatform and
navigator state are symmetric with the useHotkeyOverridesStore restore;
reference setPlatform, beforeEach, and afterEach in your change.

65-150: Consider adding coverage for Option+Arrow / Ctrl+Arrow word navigation.

The PR’s primary regression surface is line-edit translation routing, but the Option+Left/Right (macOS) and Ctrl+Left/Right (Windows) branches in terminalKeyboardHandler.ts are uncovered here. Adding one or two cases would lock in their preventDefault/onWrite("\x1bb"|"\x1bf") behavior and catch any future drift (see related comment on the handler file).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/terminalKeyboardHandler.test.ts`
around lines 65 - 150, Add tests to cover Option+Arrow (macOS altKey) and
Ctrl+Arrow (Windows ctrlKey) branches in the keyboard handler: in the existing
describe("setupKeyboardHandler") block add cases that call
setupKeyboardHandler(terminal.xterm, { onWrite }) and then synthesize
ArrowLeft/ArrowRight key events with altKey=true (Option) and ctrlKey=true
(Ctrl) via makeKeyEvent; assert terminal.invoke returns false,
key.defaultPrevented is true, and the collected writes equal ["\x1bb"] for left
and ["\x1bf"] for right. Reference the helper/test utilities makeXterm,
makeKeyEvent, setupKeyboardHandler and the onWrite capture used in other tests
to locate where to add these new assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/terminalKeyboardHandler.ts`:
- Around line 100-161: The Option/Ctrl word-navigation branches (the
isOptionLeft, isOptionRight, isCtrlLeft, isCtrlRight checks) are missing
event.preventDefault() before calling options.onWrite, causing leaked
browser/navigation behavior; update each branch so when event.type === "keydown"
and options.onWrite is called you first call event.preventDefault() and then
options.onWrite("\x1bb") or options.onWrite("\x1bf") as appropriate (keep the
same escape codes and return false behavior), matching how the Cmd branches
handle preventDefault.

---

Nitpick comments:
In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/terminalKeyboardHandler.test.ts`:
- Around line 68-76: The test mutates navigator via setPlatform("MacIntel") in
beforeEach but never restores it; update the afterEach to also restore navigator
to its original value by capturing the original navigator/platform before
calling setPlatform (e.g., save current navigator or its platform value) and
then call setPlatform with that saved value in afterEach so setPlatform and
navigator state are symmetric with the useHotkeyOverridesStore restore;
reference setPlatform, beforeEach, and afterEach in your change.
- Around line 65-150: Add tests to cover Option+Arrow (macOS altKey) and
Ctrl+Arrow (Windows ctrlKey) branches in the keyboard handler: in the existing
describe("setupKeyboardHandler") block add cases that call
setupKeyboardHandler(terminal.xterm, { onWrite }) and then synthesize
ArrowLeft/ArrowRight key events with altKey=true (Option) and ctrlKey=true
(Ctrl) via makeKeyEvent; assert terminal.invoke returns false,
key.defaultPrevented is true, and the collected writes equal ["\x1bb"] for left
and ["\x1bf"] for right. Reference the helper/test utilities makeXterm,
makeKeyEvent, setupKeyboardHandler and the onWrite capture used in other tests
to locate where to add these new assertions.

In
`@apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/terminalKeyboardHandler.ts`:
- Around line 183-187: The conditional that checks
isTerminalReservedEvent(event) is dead because both branches return true; remove
the redundant if and replace it with a single return true, keeping or expanding
the inline comment that explains terminal-reserved chords should be handled by
xterm (so the intent remains clear); update the handler function (the closure
returned by terminalKeyboardHandler) to eliminate the unreachable branch and
leave a concise comment referencing terminal-reserved chords.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b48a9429-9e04-45a0-8d36-ac6ec07a917c

📥 Commits

Reviewing files that changed from the base of the PR and between e07aef6 and a91a083.

📒 Files selected for processing (6)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalHotkeys.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/hooks/useTerminalLifecycle.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/terminalKeyboardHandler.test.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/terminalKeyboardHandler.ts
💤 Files with no reviewable changes (1)
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

No issues found across 6 files

@Kitenite Kitenite merged commit eae6008 into main Apr 25, 2026
6 of 7 checks passed
@Kitenite Kitenite deleted the port-v2-terminal-v1 branch April 25, 2026 07:04
@github-actions
Copy link
Copy Markdown
Contributor

🧹 Preview Cleanup Complete

The following preview resources have been cleaned up:

  • ⚠️ Neon database branch

Thank you for your contribution! 🎉

MocA-Love added a commit to MocA-Love/superset that referenced this pull request Apr 25, 2026
upstream の 10 commits は #426#427 で fork 固有の差分を保ちながら個別に
cherry-pick 取り込み済み。本 merge は ours strategy で **記録だけ** マージ済みに
することで behind=0 を達成し、git 履歴上の追跡を正しくする。

Cherry-pick 取り込み済 (PR #426):
- 5aab22a fix closed picker filters (superset-sh#3702) → cdb52f9
- 99db5be [codex] simplify workspace controls (superset-sh#3714) → f079606
- 186078a fix(chat): prevent ask_user question from shadowing sandbox access prompt (superset-sh#3662) → 09d6b57
- 47893c2 fix desktop workspace creation title clamp (superset-sh#3718) → 6a8c4ae
- 09323ff Add diff pane file viewer action (superset-sh#3715) → 817ed8d
- a5891c6 remove pending launch log (superset-sh#3721) → 0764d03
- c83de0c Add Tiptap table support (superset-sh#3719) → e67a885
- 486b621 [codex] Fix v2 terminal lifecycle after sleep (superset-sh#3711) → b71fbbb (+ #426 内 review fixups)

Cherry-pick 取り込み済 (PR #427):
- e07aef6 feat(desktop): play v2 notification hooks client-side (superset-sh#3675) → 27ac18a
- eae6008 [codex] Port v2 terminal hotkeys to v1 (superset-sh#3724) → 05a77b8 (+ #427 内 Windows .ps1 v2 化)

Fork 固有領域は変更ゼロで保持: 19 tRPC procedures (workspaces.githubExtended)、
AudioScheduler / Aivis TTS / notification-manager、terminal suggestion handler
(新 terminalKeyboardHandler.ts に移植)、TERMINAL_OPTIONS、SUPERSET_WORKSPACE_NAME、
MainWindowEffects、INCEPTION_AUTH_PROVIDER_ID、v1MigrationState、TiptapPromptEditor、
electron-builder.ts (dmg.size="4g", fileAssociations)、Service Status Dashboard、
Linux daemon systemd、Worktree auto-sync、Windows support、DnD scratch route 他。
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