Skip to content

Port upstream 0.67.0: provider switcher shortcut model and default navigation keys (PR 1 of 2) - #693

Draft
Finesssee wants to merge 2 commits into
port/upstream-0.67.0from
port/micro-0.67.0-provider-switcher-keys
Draft

Finesssee wants to merge 2 commits into
port/upstream-0.67.0from
port/micro-0.67.0-provider-switcher-keys

Conversation

@Finesssee

Copy link
Copy Markdown
Collaborator

Summary

PR 1 of 2 for upstream 0.67.0 "provider switcher shortcuts". Adds provider navigation from the keyboard to the tray flyout and the pop-out window:

  • Left / Right: previous / next segment, wrapping Overview -> providers -> Overview.
  • Ctrl+1 ... Ctrl+9: Nth segment in displayed order, Overview = 1 (as upstream). Positions past the last segment do nothing (key not consumed).
  • Ignored while a text field, select or slider (the zoom slider uses the arrows) has focus, or while a grid drag is active. The selected grid item is scrolled into view; a collapsed grid is not expanded.
  • New lib/switcherShortcuts.ts: shortcut model (actions, defaults), normalizeShortcut, validateSwitcherShortcuts (unknown action / duplicate / reserved / invalid, none disables, omitted actions keep defaults), shortcutFromEvent, matchSwitcherAction, resolveSwitcherTarget. Not yet user-configurable: the mapping is the defaults until PR 2.
  • New hooks/useProviderSwitcherKeys.ts, shared by useTrayPanelController and PopOutPanel.

Approved design

Design note approved by the user (2026-09-29): design-provider-switcher-shortcuts.md. Summary:

  • Upstream (v0.67.0, commit 26dcc073c) customizes an existing menu-local mapping: actions previous, next, select1..9; defaults left, right, cmd+1..9; validated / normalized / action(key:modifiers:mapping:); stored under switcherShortcuts; edited in Settings; also a portable-preferences key. Windows has no such mapping, so this is (a) new navigation keys, then (b) an editor.
  • Windows behavior: surfaces TrayPanel and PopOut only (float bar and flyout excluded). Defaults left, right, ctrl+1..9. Grammar is upstream's; cmd is accepted as an alias for ctrl and normalized to ctrl so exported documents stay portable. Reserved: ctrl+r, ctrl+q, ctrl+,, ctrl+w. Same duplicate and letters-need-a-modifier rules.
  • Open questions resolved with the note's recommendations: cmd aliases to ctrl; arrow selection scrolls into view and does not expand the grid; selectN follows displayed order with Overview = 1; the editor ships in a follow-up PR (two micro PRs).
  • PR 2 (not in this PR): Settings.switcher_shortcuts, bridge/settings patch and Rust validation, the Settings menu editor section (11 rows, Clear, Reset to defaults, inline errors), 11 locale keys x 5 locales, portable-preferences allowlist key.

Upstream reference

Ported / Deferred

Ported: shortcut model, validation and normalization (TypeScript), default navigation keys in TrayPanel and PopOut.

Deferred to PR 2 (per the approved split): Rust Settings.switcher_shortcuts and validator (dead code without persistence), bridge fields, settings editor UI, locale keys, portable-preferences key.

Windows deviations from upstream: cmd -> ctrl alias; alt+f4 / alt+tab from the note need no reserved entry because f4 and tab are outside the grammar and are rejected as invalid; the reserved set drops macOS-only entries (cmd+h, cmd+m, alt+cmd+h).

Validation

Frontend only; no Rust files changed, so cargo was not run.

  • pnpm --dir apps/desktop-tauri exec vitest run src/lib/switcherShortcuts.test.ts src/hooks/useProviderSwitcherKeys.test.tsx: 2 files, 33 tests passed.
  • pnpm --dir apps/desktop-tauri exec vitest run (full): 69 files, 435 tests passed.
  • pnpm --dir apps/desktop-tauri run lint: no findings in touched files (existing warnings elsewhere unchanged).
  • pnpm --dir apps/desktop-tauri run build: passed.

Affected areas

  • Frontend (TrayPanel, PopOut, hooks, lib)
  • Docs (docs/CONFIGURATION.md, "Provider switcher keys")
  • Rust backend / CLI
  • Settings schema / bridge types
  • Provider fetch logic

UI proof

Pending: coordinator will capture CUA proof on a fresh build (Left/Right and Ctrl+3 in tray flyout and pop-out; confirm WebView2 does not swallow Ctrl+digit, design note risk 4; zoom slider still uses arrows).

@coderabbitai

coderabbitai Bot commented Sep 29, 2026

Copy link
Copy Markdown

Important

Draft PR not reviewed

Draft PRs are not automatically reviewed by default.

  • Trigger a manual review

To automatically review draft PRs, update your CodeRabbit configuration:

reviews:
  auto_review:
    drafts: true

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.

@Finesssee

Copy link
Copy Markdown
Collaborator Author

Thermo-nuclear review

Reviewed against the 0.67.0 switcher design note and AGENTS.md. Overall the model (lib/switcherShortcuts.ts) is clean, pure and well tested, and the hook is shared by both surfaces instead of duplicated. No file crosses 1k lines (largest touched: useTrayPanelController.ts, ~410). Findings, most severe first:

  1. Tray keys navigate a different list than the grid displays (behavior bug). useTrayPanelController fed the hook sorted, but in the dense overview (>32 enabled) TrayPanel renders expectsDenseOverview ? denseTrayProviders : sorted, which contains placeholders for enabled providers that have not loaded. Right/Left and Ctrl+N therefore skipped or mis-numbered visible segments, contradicting the design ("selectN follows displayed order"). The ternary was also written inline in TrayPanel. Fix: one gridProviders value in the controller, used by both the grid and the hook.
  2. Hook reaches into the grid's DOM to scroll. useProviderSwitcherKeys ran a global document.querySelector(".provider-grid__item--active") effect, leaking ProviderGrid markup into a keyboard hook. The grid owns that DOM; scroll-into-view belongs there (ref + effect keyed on selectedProviderId). Fix: moved into ProviderGrid, with a focused test.
  3. Speculative mapping prop. useProviderSwitcherKeys({ mapping }) has no caller passing it (the editor is the planned follow-up PR). Removed the optional prop and the ref plumbing; the editor PR re-adds it with a real consumer.
  4. Not changed, non-blocking. validateSwitcherShortcuts, normalizeShortcut, SwitcherShortcutError and shortcutFromEvent have no production caller until the editor PR. They are the ported upstream model and are covered by tests, so I left them rather than churn the PR. The drag guard still uses a class-name query (.provider-grid__item--dragging) because the drag state is local to ProviderGrid; acceptable for now, revisit if the grid exposes gesture state.

Fixes for 1-3 are pushed as a follow-up commit.

@Finesssee

Copy link
Copy Markdown
Collaborator Author

Pushed 4e94fdb "Address thermo review": tray keys now navigate the same list the grid displays (dense overview placeholders included), scroll-into-view moved into ProviderGrid with a test, unused mapping prop removed. Frontend: vitest 435/435 pass, lint has only pre-existing warnings, build OK. Rust untouched.

@Finesssee

Copy link
Copy Markdown
Collaborator Author

CUA proof

Build commit: 4e94fdbba168f18937af6cc54f1ee021982960f6 (debug build, prebuilt, not rebuilt).
Proof-only patch (uncommitted, not part of this PR): logging::config_root honors CODEXBAR_PROOF_CONFIG_ROOT; token_accounts, api_keys, manual_cookies paths use it. This isolates settings from the real user config.

Commands: bash launch.sh trayPanel, bash launch.sh popOut (isolated config, empty CODEX_HOME/CLAUDE_CONFIG_DIR/GEMINI_HOME, CODEXBAR_SEED_USAGE_JSON); cua-driver serve; cua-driver call list_windows | get_window_state --screenshot-out-file | hotkey (foreground) | click with a session label.

# Assertion Result
1 trayPanel shows Overview + Codex, Claude, Gemini tiles, dark theme PASS (01.png)
2 Right moves active tile in display order (All, Codex, Claude, Gemini); Left moves back PASS (02-04, 07)
3 Right from last wraps to first; Left from first wraps to last PASS (05, 06)
4 Ctrl+1..4 select Overview, Codex, Claude, Gemini; detail follows PASS (08-ctrl1..4)
5 Ctrl+9 leaves selection unchanged PASS (08-ctrl9)
6 Zoom slider focused: Left/Right change zoom (150 -> 145 -> 150), selection stays on Gemini PASS (12-slider-left/right)
7 Steps 2-4 and 5 in popOut PASS (21-30)
8 Ctrl+R, Ctrl+W do not switch provider (Codex stayed selected); Ctrl+Q not exercised PASS (13-ctrlr, 14-ctrlr, 15-ctrlw)

Notes:

  • The seeded Codex snapshot (42% / 67%) was not visible: Codex showed "auth.json not found" (empty CODEX_HOME), so the "Session 42%" detail check in step 4 was verified only on provider selection, not on usage values. This is a kit or seed-eviction limitation, not a PR defect.
  • With the slider focused, Ctrl+2 was also ignored (13-ctrl2); after clicking blank panel area it worked (14-ctrl2-after-blank). Consistent with an input-focus guard; noted, not a failure of the plan.
  • 09-, 10-tab and 11-pre/14-pre are exploratory frames from failed focus attempts, not proof.

Screenshots (local, not committed): C:\Users\FSOS\AppData\Local\Win-CodexBar\port-audit\proof\693\shots (strip-02-07.png, strip-08-ctrl.png, strip-12-slider.png, strip-13.png, strip-popout.png are contact sheets). No personal data visible.

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