Skip to content

feat(desktop): add global "Use Option as Meta key" setting#2280

Open
emilbryggare wants to merge 2 commits into
superset-sh:mainfrom
emilbryggare:eb/option-as-meta-setting
Open

feat(desktop): add global "Use Option as Meta key" setting#2280
emilbryggare wants to merge 2 commits into
superset-sh:mainfrom
emilbryggare:eb/option-as-meta-setting

Conversation

@emilbryggare
Copy link
Copy Markdown

@emilbryggare emilbryggare commented Mar 9, 2026

Summary

  • Add a global "Use Option as Meta key" toggle in Settings > Behavior (macOS only), replacing the per-key approach from PR fix: map Option+P to Meta+P escape sequence on macOS (#1359) #1900
  • When enabled, all Option+letter combos send ESC+letter (Meta key) to the terminal, and Option+Left/Right send Alt-modified CSI sequences for TUI compatibility (Zellij, tmux)
  • When disabled, behavior is unchanged (Option produces Unicode characters, Option+arrows do readline word navigation)

Changes

  • DB: Add optionAsMeta boolean column to local-db settings with migration
  • tRPC: Add getOptionAsMeta/setOptionAsMeta procedures
  • Keyboard handler: Generic Option+letter → ESC+letter using event.code for layout independence; Option+arrows send CSI sequences when enabled
  • Settings UI: macOS-only toggle with optimistic updates, registered in settings search
  • Tests: 5 new test cases covering enabled/disabled, arrow keys, and keyboard layout independence

Test plan

  • Toggle setting on → Option+P sends ESC+p (triggers Claude Code model picker)
  • Toggle setting off → Option+P produces π (Unicode character)
  • Option+Left/Right always navigate by word (ESC+b/f when off, CSI sequences when on)
  • Option+Left/Right work in Zellij for pane navigation when setting is on
  • Setting only visible on macOS
  • bun test helpers.test.ts passes (122 tests)
  • bun run typecheck passes

Summary by cubic

Adds a macOS-only “Use Option as Meta key” setting that maps Option+letter to ESC+letter in the terminal and enables Alt‑modified arrow sequences for TUI apps. When off, behavior is unchanged.

  • New Features
    • Adds a toggle in Settings > Behavior (macOS only), searchable with optimistic updates; query is gated to macOS via PLATFORM.IS_MAC.
    • Persists optionAsMeta in local DB (default DEFAULT_OPTION_AS_META=false) and exposes getOptionAsMeta/setOptionAsMeta via tRPC.
    • Terminal handler reads the setting via a ref at keypress time, uses event.code for layout independence, maps Option+letter to ESC+letter, preserves Shift (Option+Shift+P → ESC+P), and sends Alt‑modified CSI for Option+Left/Right when enabled (falls back to ESC+b/f when disabled).
    • Adds tests covering enabled/disabled states, arrow key behavior, non‑US layouts, and Shift modifier.

Written for commit bd26967. Summary will update on new commits.

Summary by CodeRabbit

  • New Features

    • Added "Use Option as Meta key" toggle in Behavior settings (macOS only) with a persisted preference (default: off).
    • Terminal now respects the new setting so Option+letter can be sent as ESC+letter for Meta key bindings.
  • Settings / Search

    • New setting is discoverable via Settings search with title, description, and keywords.
  • Tests

    • Added comprehensive keyboard handling tests covering the new behaviors.

Replace per-key Option+letter mappings with a global toggle (like iTerm2,
VS Code, Alacritty) that maps all Option+letter combos to ESC+letter on
macOS. When enabled, Option+Left/Right send Alt-modified CSI sequences
for TUI compatibility (Zellij, tmux). When disabled, behavior is unchanged.

- Add optionAsMeta column to local-db settings schema with migration
- Add get/set tRPC procedures following existing boolean setting pattern
- Add generic Option+letter → ESC+letter keyboard handler using event.code
- Add macOS-only toggle in Behavior settings with optimistic updates
- Register setting in search index with relevant keywords
- Add comprehensive tests covering enabled/disabled/arrow/layout scenarios
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 9, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7b2703f0-edca-489d-a0e3-d98e9722613d

📥 Commits

Reviewing files that changed from the base of the PR and between 70ec78e and bd26967.

📒 Files selected for processing (3)
  • apps/desktop/src/renderer/routes/_authenticated/settings/behavior/components/BehaviorSettings/BehaviorSettings.tsx
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.test.ts
  • apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • apps/desktop/src/renderer/routes/_authenticated/settings/behavior/components/BehaviorSettings/BehaviorSettings.tsx

📝 Walkthrough

Walkthrough

Adds a persisted "Option as Meta" setting with DB migration, TRPC read/write endpoints, settings UI entry and toggle (macOS-only), terminal wiring to propagate the setting via a ref, and keyboard handler logic/tests to translate Option key events accordingly.

Changes

Cohort / File(s) Summary
Database Schema & Migrations
packages/local-db/drizzle/0036_add_option_as_meta_setting.sql, packages/local-db/drizzle/meta/0036_snapshot.json, packages/local-db/drizzle/meta/_journal.json, packages/local-db/src/schema/schema.ts
Adds option_as_meta integer column to settings table, updates Drizzle schema, and records migration snapshot and journal entry.
TRPC Settings API
apps/desktop/src/lib/trpc/routers/settings/index.ts
Adds getOptionAsMeta query and setOptionAsMeta mutation; uses DEFAULT_OPTION_AS_META fallback and upsert pattern for persisting.
Constants & Settings Search
apps/desktop/src/shared/constants.ts, apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.ts
Adds DEFAULT_OPTION_AS_META = false and registers BEHAVIOR_OPTION_AS_META setting item with title, description, and keywords for search.
Settings UI
apps/desktop/src/renderer/routes/_authenticated/settings/behavior/components/BehaviorSettings/BehaviorSettings.tsx
Renders a macOS-only "Use Option as Meta key" switch, wired to getOptionAsMeta and setOptionAsMeta with optimistic updates and error rollback.
Terminal Integration
apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/Terminal.tsx, .../hooks/useTerminalLifecycle.ts
Queries optionAsMeta, stores it in a ref, and propagates optionAsMetaRef through useTerminalLifecycle to keyboard setup.
Keyboard Handling & Tests
apps/desktop/src/renderer/screens/main/components/.../Terminal/helpers.ts, .../helpers.test.ts
Adds optionAsMetaRef?: { current: boolean } to handler options; implements macOS Option→Meta translations (ESC+letter or CSI sequences based on setting) and extensive tests covering behaviors and layout independence.

Sequence Diagram

sequenceDiagram
    participant User as User (macOS)
    participant UI as Behavior Settings UI
    participant TRPC as TRPC Settings API
    participant DB as Settings DB
    participant Terminal as Terminal Component
    participant KB as Keyboard Handler
    participant XTerm as XTerm

    User->>UI: toggle "Use Option as Meta"
    UI->>TRPC: setOptionAsMeta({ enabled: true })
    TRPC->>DB: upsert option_as_meta = 1
    DB-->>TRPC: success
    TRPC-->>UI: { success: true }
    UI->>Terminal: invalidate / refresh

    Terminal->>TRPC: getOptionAsMeta()
    TRPC->>DB: read option_as_meta
    DB-->>TRPC: 1
    TRPC-->>Terminal: optionAsMeta = true
    Terminal->>Terminal: optionAsMetaRef.current = true

    User->>XTerm: press Option+B
    XTerm->>KB: KeyboardEvent (Option+B)
    KB->>KB: check optionAsMetaRef.current
    alt optionAsMeta enabled
        KB->>XTerm: write ESC + b
    else disabled
        KB->>XTerm: write ESC+b or CSI sequence
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 a twitch and a hop
Option keys leap into Meta's song,
ESC and letters humming along,
On Mac I press and bridges form,
The rabbit smiles — terminal warm.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding a global "Use Option as Meta key" setting for the desktop app, which matches the primary objective across all modified files.
Description check ✅ Passed The PR description includes all key sections (Summary, Changes, Test plan) with comprehensive details about the feature, implementation across layers, and testing coverage. All required template sections are adequately filled.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

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.

1 issue found across 12 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts">

<violation number="1" location="apps/desktop/src/renderer/screens/main/components/WorkspaceView/ContentView/TabsContent/Terminal/helpers.ts:648">
P2: Option+Shift+letter silently drops the Shift modifier. The handler always sends ESC+lowercase, so Option+Shift+P sends `ESC+p` instead of the expected `ESC+P`. These are distinct keybindings in tmux, vim, and other TUI apps. Honor `event.shiftKey` to send the correct case.</violation>
</file>

Since this is your first cubic review, here's how it works:

  • cubic automatically reviews your code and comments on bugs and improvements
  • Teach cubic by replying to its comments. cubic learns from your replies and gets better over time
  • Add one-off context when rerunning by tagging @cubic-dev-ai with guidance or docs links (including llms.txt)
  • Ask questions if you need clarification on any suggestion

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

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.

🧹 Nitpick comments (2)
apps/desktop/src/renderer/routes/_authenticated/settings/behavior/components/BehaviorSettings/BehaviorSettings.tsx (1)

166-169: Use the shared platform constant instead of querying it.

process.platform is static for the renderer, so window.getPlatform just adds async state and an extra failure path for whether this control renders. There's already a shared platform constant in apps/desktop/src/shared/constants.ts; using that here would also let you gate getOptionAsMeta with enabled: isMac.

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

In
`@apps/desktop/src/renderer/routes/_authenticated/settings/behavior/components/BehaviorSettings/BehaviorSettings.tsx`
around lines 166 - 169, Replace the async platform query with the shared
compile-time platform constant from apps/desktop/src/shared/constants.ts (use
the exported name there, e.g., SHARED_PLATFORM or IS_MAC) instead of calling
electronTrpc.window.getPlatform.useQuery; compute isMac from that constant
(isMac = SHARED_PLATFORM === "darwin" or use exported IS_MAC), remove the
electronTrpc.window.getPlatform.useQuery call, and pass enabled: isMac to
electronTrpc.settings.getOptionAsMeta.useQuery so getOptionAsMeta only runs on
macOS.
packages/local-db/src/schema/schema.ts (1)

211-211: Avoid persisting a tri-state for this boolean setting.

This works today because the router coerces NULL back to false, but it means settings.optionAsMeta is not actually boolean at rest. If this migration is still pre-release, I'd make this .default(false) here and add DEFAULT 0 in packages/local-db/drizzle/0036_add_option_as_meta_setting.sql so future callers don't have to remember the nullish fallback.

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

In `@packages/local-db/src/schema/schema.ts` at line 211, The column declaration
optionAsMeta currently allows NULL; change integer("option_as_meta", { mode:
"boolean" }) to include a default false (e.g. integer("option_as_meta", { mode:
"boolean", default: false })) so the setting is stored as a proper boolean at
rest, and update the corresponding migration
(0036_add_option_as_meta_setting.sql) to set DEFAULT 0 for the option_as_meta
column so existing/new DB rows get a non-null default.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/settings/behavior/components/BehaviorSettings/BehaviorSettings.tsx`:
- Around line 166-169: Replace the async platform query with the shared
compile-time platform constant from apps/desktop/src/shared/constants.ts (use
the exported name there, e.g., SHARED_PLATFORM or IS_MAC) instead of calling
electronTrpc.window.getPlatform.useQuery; compute isMac from that constant
(isMac = SHARED_PLATFORM === "darwin" or use exported IS_MAC), remove the
electronTrpc.window.getPlatform.useQuery call, and pass enabled: isMac to
electronTrpc.settings.getOptionAsMeta.useQuery so getOptionAsMeta only runs on
macOS.

In `@packages/local-db/src/schema/schema.ts`:
- Line 211: The column declaration optionAsMeta currently allows NULL; change
integer("option_as_meta", { mode: "boolean" }) to include a default false (e.g.
integer("option_as_meta", { mode: "boolean", default: false })) so the setting
is stored as a proper boolean at rest, and update the corresponding migration
(0036_add_option_as_meta_setting.sql) to set DEFAULT 0 for the option_as_meta
column so existing/new DB rows get a non-null default.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: e9405c5e-0a66-4f56-9be9-8d0882b0d4e9

📥 Commits

Reviewing files that changed from the base of the PR and between 3dd4c0f and 70ec78e.

📒 Files selected for processing (12)
  • apps/desktop/src/lib/trpc/routers/settings/index.ts
  • apps/desktop/src/renderer/routes/_authenticated/settings/behavior/components/BehaviorSettings/BehaviorSettings.tsx
  • apps/desktop/src/renderer/routes/_authenticated/settings/utils/settings-search/settings-search.ts
  • 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.test.ts
  • 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/useTerminalLifecycle.ts
  • apps/desktop/src/shared/constants.ts
  • packages/local-db/drizzle/0036_add_option_as_meta_setting.sql
  • packages/local-db/drizzle/meta/0036_snapshot.json
  • packages/local-db/drizzle/meta/_journal.json
  • packages/local-db/src/schema/schema.ts

- Honor Shift modifier: Option+Shift+P now sends ESC+P (uppercase),
  distinct from ESC+p, for correct tmux/vim keybindings
- Use PLATFORM.IS_MAC constant instead of tRPC query for platform check
- Gate getOptionAsMeta query with enabled: isMac to skip on non-macOS
- Add test for Shift modifier preservation
@vitto32
Copy link
Copy Markdown

vitto32 commented May 14, 2026

Bump from #1359 reporter — sharing some additional context and a design question.

Worth flagging: when this bug fires, Claude Code itself shows an in-terminal hint:

To enable alt+p, set Option as Meta in Kitty preferences

Since Superset claims TERM_PROGRAM=kitty (#3667), users follow that pointer expecting a toggle that doesn't exist yet. Makes the PR all the more visible to users hitting the issue.

A bit of historical context for reviewers seeing this for the first time — this PR is the natural follow-up to #899, which (rightly) made Option+letter pass through to native character composition so users on PL / DE / IT / FR / ES / Nordic layouts can type @, accents, and diacritics in the terminal. Side effect: Option now always produces a character, which trades away Alt+letter for every CLI tool (Claude Code's Alt+P model picker, readline's Alt+B/F, vim/emacs Meta chords, …). Related reports across the tracker converging on the same area:

Design question / alternative worth considering: both Kitty (macos_option_as_alt = left | right | both | no) and Ghostty (macos-option-as-alt = left | right | true | false) expose per-side configuration. That's the genuine best-of-both-worlds — one Option key kept for native composition (à, è, @ on DE, etc.), the other dedicated to Meta for CLI shortcuts. Costs little implementation-wise (boolean → enum), removes the trade-off entirely instead of just shifting it, and matches the precedent of the two terminals closest to Superset's stack (especially given the TERM_PROGRAM=kitty claim).

Are there other alternatives already on the table I might've missed?

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.

2 participants