* feat(web): add Cmd/Ctrl+P quick file switcher
A keyboard-driven file palette overlaid on the workspace. Press Cmd/Ctrl+P
anywhere in the project view; type to fuzzy-filter the file list, ↑/↓ to
navigate, Enter to open in a tab, Esc to dismiss. With an empty query the
palette surfaces recents (per-project, localStorage) followed by the rest
of the file list sorted by mtime.
Adds:
- apps/web/src/components/QuickSwitcher.tsx: palette UI and matcher
- apps/web/src/quickSwitcherRecents.ts: per-project recents store
- index.css: scoped .qs-* styles using existing design tokens
- i18n: 6 new keys translated across all 16 locale files
Wires into FileWorkspace's existing openFile() so recents and tab state
behave identically to opening from DesignFilesPanel. Capture-phase keydown
beats the browser's print dialog. No backend changes; uses the files prop
already passed to FileWorkspace.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(web): address QuickSwitcher review feedback
Three fixes from the PR review:
- z-index: bump .qs-overlay from 200 to 1500 so the palette renders in
the modal tier (alongside prompt-template-modal-overlay) instead of
behind context menus and popovers (which sit at 200).
- Arrow-key guard: skip setCursor when matches is empty. Without this,
pressing ↓ on a no-results query set the cursor to -1, making the
highlight selector miss every row on the next render.
- Tests: add 19 unit tests covering scoreMatch ranking tiers, render
output (empty state / row count / kbd hints / placeholder), and the
full recents lifecycle (cap at 6, dedupe-on-push, corrupt-JSON
recovery, per-project scoping, quota-exceeded no-op). Vitest stays
on the node env via a small in-memory localStorage stub.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(web): QuickSwitcher review — wrap, IME, platform gate
Three follow-ups from @mrcfps's review on #556:
- ArrowUp/ArrowDown now wrap at list bounds (last → first, first → last)
via modulo arithmetic in a new pure helper `nextCursor(current, total,
direction)`. Previously they clamped, which contradicted the wrap
behavior the PR test plan promised. Pulled into a pure function so
boundary cases are unit-testable without simulating keyboard events.
- Palette's onKeyDown now early-returns on `e.nativeEvent.isComposing`,
so users typing CJK file names through an IME keep ↑/↓/Enter for
candidate navigation instead of having them steered by the palette.
The global Cmd/Ctrl+P opener already had the equivalent guard.
- Global keydown is now platform-gated: macOS responds only to metaKey,
win/linux only to ctrlKey. Previously both fired everywhere, which
meant Ctrl+P on macOS was stealing native readline "previous line" in
text fields (and the chat composer).
Tests: +6 unit tests for `nextCursor` covering forward/backward wrap,
mid-list moves, empty list (no division-by-zero), and single-item
no-op. Suite now 258 passing (up from 252).
Verified live: ↓ from last row → first row; ↑ from first row → last
row, in a mocked-project Playwright harness.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>