diff --git a/.planning/phases/09-scan-cancellation-keyboard-shortcuts/09-RESEARCH.md b/.planning/phases/09-scan-cancellation-keyboard-shortcuts/09-RESEARCH.md
new file mode 100644
index 0000000..bf995ef
--- /dev/null
+++ b/.planning/phases/09-scan-cancellation-keyboard-shortcuts/09-RESEARCH.md
@@ -0,0 +1,555 @@
+# Phase 9: Scan Cancellation & Keyboard Shortcuts - Research
+
+**Researched:** 2026-03-06
+**Domain:** Go context cancellation, frontend keyboard event management, Lit web component architecture
+**Confidence:** HIGH
+
+## Summary
+
+This phase adds two independent feature sets to YellowJacket: scan control (cancel/pause/resume) on the Go backend with frontend buttons, and a full keyboard shortcut system on the Lit frontend with configurable bindings persisted via the existing TOML config.
+
+**Scan cancellation** requires threading a cancellable `context.Context` through the existing scan pipeline. The current `Scan()` method already checks `l.ctx.Done()` in several `select` blocks within the directory walker and worker pool. The implementation adds a dedicated `scanCancel context.CancelFunc` field on `Library`, Pause/Resume via a sync-based mechanism (channel or mutex), and new Wails-bound methods (`CancelScan`, `PauseScan`, `ResumeScan`). The cancel confirmation dialog ("Keep X tracks found so far, or discard?") is a frontend concern — the backend simply stops and reports partial results vs rolls back.
+
+**Keyboard shortcuts** are a pure frontend feature. No external libraries are needed — the browser's `KeyboardEvent` API is sufficient for a Wails desktop app. A central `KeyboardShortcutService` singleton listens on `document.keydown`, resolves the active scope (Global, Panel-specific, Text Input), looks up the action, and dispatches it. Bindings are stored in the Go config (new `Shortcuts` TOML section) and exposed via Wails bindings. The settings UI adds a "Keyboard Shortcuts" tab to the existing `config-page` component with record-style key capture.
+
+**Primary recommendation:** Implement scan cancellation via `context.WithCancel` + a pause channel on the backend, and keyboard shortcuts as a frontend-only `KeyboardShortcutService` with Go config persistence. Both are zero-dependency — no new libraries needed on either side.
+
+
+## User Constraints (from CONTEXT.md)
+
+### Locked Decisions
+- Hybrid style: Space/arrows for player controls (no modifier), Ctrl+key for app actions
+- Up/Down arrows adjust volume, Left/Right seek within track
+- Both `/` and `Ctrl+F` focus the search box
+- `Q` toggles the queue panel
+- `S` for shuffle, `R` for repeat (single-key player controls)
+- `Ctrl+A` for select-all in any multi-select context (track lists, etc.)
+- All bindings are configurable — the above are defaults
+- Claude fills in remaining defaults (mute, etc.) using common media player conventions
+- Record-style key capture: click a shortcut row, press the new key combo, it captures live
+- Conflicts show a warning with the conflicting action — user chooses to overwrite (old becomes unbound) or cancel
+- Shortcuts grouped by category (Player, Navigation, App) in the settings view
+- "Reset to defaults" button resets all shortcuts; individual per-shortcut reset also available
+- Lives as a "Keyboard Shortcuts" tab within the existing settings dialog
+- Three scopes: Global (always active), Panel-specific (when a panel has focus), Text Input (shortcuts suppressed)
+- Global scope: player controls (Space, arrows, S, R, Q, etc.) fire regardless of which panel is focused
+- Panel-specific scope: track list gets Enter-to-play and Delete-to-remove when focused
+- Text Input scope: only Escape works (blurs the text input) — all other shortcuts suppressed
+- No visual scope indicator — relies on natural browser focus behavior; users learn through use
+- Pause and Cancel buttons placed next to the existing status label, above the existing progress bar in the scanner UI
+- On cancel: prompt the user — "Keep X tracks found so far, or discard?" — gives user control over partial results
+- On resume after pause: skip already-processed files and continue with remaining — no duplicate work
+- Scan control is buttons-only — no keyboard shortcuts for cancel/pause (scans are infrequent)
+
+### Claude's Discretion
+- Remaining default key assignments not explicitly discussed (mute, volume step size, etc.)
+- Scan progress detail level and error handling during scan
+- Loading/disabled states for scan control buttons
+- Visual design of the shortcut settings UI (spacing, grouping headers, etc.)
+- How the cancel confirmation dialog looks and behaves
+
+### Deferred Ideas (OUT OF SCOPE)
+None — discussion stayed within phase scope
+
+
+
+## Phase Requirements
+
+| ID | Description | Research Support |
+|----|-------------|-----------------|
+| SCAN-01 | User can cancel an in-progress library scan via a cancel button | Go context cancellation pattern; new `CancelScan()` Wails binding; frontend cancel button in config-page scan section |
+| SCAN-02 | Cancelled scan stops gracefully without corrupting the database | Batch-transactional writes already atomic; cancel skips orphan cleanup (STATE.md warning); partial results either kept or discarded per user choice |
+| SCAN-03 | User can pause a library scan and resume it without re-scanning processed files | Pause channel blocks worker pool goroutines; resume unblocks; existingPaths sync.Map already tracks processed files |
+| KEY-01 | Default keybindings work out of box | Frontend `KeyboardShortcutService` with hardcoded default map; Go config stores overrides |
+| KEY-02 | User can customize all keyboard shortcuts via a visual settings UI | "Keyboard Shortcuts" tab in config-page; record-style key capture component; Wails config bindings for persistence |
+| KEY-03 | Shortcut conflicts are detected and warned about when rebinding | Frontend conflict detection during key capture — compare against all bindings in same scope |
+| KEY-04 | Shortcuts are scoped — different bindings apply based on focused component | Three-scope system (Global, Panel, TextInput); scope resolved by checking `document.activeElement` shadow DOM chain |
+| KEY-05 | Shortcuts are disabled when text input has focus (except Escape to blur) | TextInput scope check: if active element is ``, `
+
+## Standard Stack
+
+### Core
+| Library | Version | Purpose | Why Standard |
+|---------|---------|---------|--------------|
+| Go `context` | stdlib | Scan cancellation via `context.WithCancel` | Standard Go cancellation pattern; already used in scan pipeline |
+| `sync` | stdlib | Pause/resume via channel or conditional variable | No external dependency needed for goroutine coordination |
+| Browser `KeyboardEvent` API | Web standard | Key capture, modifier detection, key identification | Native API, no library needed for desktop Wails app |
+| Lit 3.x | 3.2.1 (existing) | Shortcut settings UI components | Already the project's component framework |
+| BurntSushi/toml | existing | Config persistence for shortcut bindings | Already the project's config format |
+
+### Supporting
+| Library | Version | Purpose | When to Use |
+|---------|---------|---------|-------------|
+| `golang.org/x/sync/errgroup` | existing | Worker pool with context-aware cancellation | Already used in scan worker pool |
+
+### Alternatives Considered
+| Instead of | Could Use | Tradeoff |
+|------------|-----------|----------|
+| Custom key manager | `hotkeys-js` or `tinykeys` | Unnecessary dependency for a Wails app — no global OS hotkeys needed, browser events suffice |
+| TOML config for shortcuts | JSON file or SQLite | TOML is the existing config format — consistency wins |
+| sync.Cond for pause | Channel-based pause | Channels are simpler and more idiomatic in Go; sync.Cond is error-prone |
+
+## Architecture Patterns
+
+### Recommended Project Structure
+```
+backend/
+├── library/
+│ ├── library.go # Add scanCancel, scanPaused fields; modify Scan()
+│ ├── scan_control.go # New: CancelScan(), PauseScan(), ResumeScan() methods
+│ └── metrics.go # Add Cancelled bool field to ScanMetrics
+├── config/
+│ └── config.go # Add Shortcuts *shortcuts.Config section
+├── shortcuts/ # New package
+│ ├── config.go # ShortcutConfig struct, defaults, validation
+│ └── config_test.go # Unit tests for config validation
+└── events/
+ └── events.go # Add ScanCancelled, ScanPaused, ScanResumed events
+
+frontend/src/
+├── services/
+│ └── keyboard-shortcut-service.ts # New: singleton, keydown listener, scope resolution, action dispatch
+├── store/
+│ └── shortcuts-store.ts # New: persisted shortcut bindings from config
+├── components/
+│ └── config-page/
+│ ├── config-page.ts # Add "Keyboard Shortcuts" tab
+│ └── shortcut-capture.ts # New: record-style key capture widget
+```
+
+### Pattern 1: Context Cancellation for Scan
+**What:** Use `context.WithCancel` to create a per-scan context that propagates cancellation to all goroutines.
+**When to use:** Every call to `Scan()` creates a child context from `l.ctx`.
+
+```go
+// In library.go — Scan() method modification
+func (l *Library) Scan() (*ScanMetrics, error) {
+ // Create cancellable context for this scan
+ scanCtx, cancel := context.WithCancel(l.ctx)
+
+ l.mu.Lock()
+ l.scanCancel = cancel
+ l.scanActive = true
+ l.mu.Unlock()
+
+ defer func() {
+ l.mu.Lock()
+ l.scanCancel = nil
+ l.scanActive = false
+ l.mu.Unlock()
+ }()
+
+ // Pass scanCtx instead of l.ctx to all operations
+ // Workers check scanCtx.Done() for cancellation
+ // ...
+}
+```
+
+### Pattern 2: Channel-Based Pause/Resume
+**What:** Use a channel that workers check before processing each file. When paused, the channel blocks; when resumed, it's replaced with a closed channel (always readable).
+**When to use:** Pause/resume scan control.
+
+```go
+type Library struct {
+ // ...
+ scanPauseCh chan struct{} // nil = not paused, non-nil closed = running, non-nil open = paused
+}
+
+// Workers call this before processing each file:
+func (l *Library) waitIfPaused(ctx context.Context) error {
+ l.mu.Lock()
+ ch := l.scanPauseCh
+ l.mu.Unlock()
+
+ if ch == nil {
+ return nil
+ }
+
+ select {
+ case <-ch: // channel closed = unpaused, proceed
+ return nil
+ case <-ctx.Done():
+ return ctx.Err()
+ }
+}
+```
+
+### Pattern 3: Frontend Keyboard Shortcut Service
+**What:** A singleton service that listens on `document.keydown`, resolves scope, looks up binding, and dispatches action.
+**When to use:** The service is created once at app startup and never destroyed.
+
+```typescript
+// keyboard-shortcut-service.ts
+class KeyboardShortcutService {
+ private bindings: Map;
+
+ constructor() {
+ document.addEventListener('keydown', this.handleKeydown);
+ }
+
+ private handleKeydown = (e: KeyboardEvent) => {
+ // 1. Check if text input focused — suppress all except Escape
+ if (this.isTextInputFocused()) {
+ if (e.key === 'Escape') {
+ (document.activeElement as HTMLElement)?.blur();
+ e.preventDefault();
+ }
+ return;
+ }
+
+ // 2. Build key string: "Ctrl+Shift+K" format
+ const keyStr = this.buildKeyString(e);
+
+ // 3. Check panel-specific bindings first, then global
+ const scope = this.resolveScope();
+ const action = this.findAction(keyStr, scope);
+
+ if (action) {
+ e.preventDefault();
+ this.dispatch(action);
+ }
+ };
+
+ private isTextInputFocused(): boolean {
+ const el = this.getDeepActiveElement();
+ if (!el) return false;
+
+ const tag = el.tagName.toLowerCase();
+ if (tag === 'input' || tag === 'textarea') return true;
+ if ((el as HTMLElement).isContentEditable) return true;
+
+ return false;
+ }
+
+ // Shadow DOM aware active element resolution
+ private getDeepActiveElement(): Element | null {
+ let el = document.activeElement;
+ while (el?.shadowRoot?.activeElement) {
+ el = el.shadowRoot.activeElement;
+ }
+ return el;
+ }
+}
+```
+
+### Pattern 4: Config Extension for Shortcuts
+**What:** Add a `Shortcuts` section to the existing TOML config following the same pattern as Theme, TrackList, Favorites.
+**When to use:** Persisting user-customized keyboard shortcuts.
+
+```go
+// backend/shortcuts/config.go
+type Config struct {
+ Bindings map[string]string `toml:"Bindings"` // action -> key combo
+}
+
+func (c *Config) ApplyDefaults() {
+ if c.Bindings == nil {
+ c.Bindings = DefaultBindings()
+ }
+}
+
+// backend/config/config.go — add to Config struct
+type Config struct {
+ // ... existing fields
+ Shortcuts *shortcuts.Config `toml:"Shortcuts"`
+}
+```
+
+### Anti-Patterns to Avoid
+- **Anti-pattern: Global mutable state for pause:** Don't use a global variable. Keep pause state on the Library struct, protected by the existing mutex.
+- **Anti-pattern: Keyboard listeners on individual components:** Don't add `keydown` handlers to every component. Use a single document-level listener that delegates based on scope.
+- **Anti-pattern: Storing shortcuts in localStorage:** Don't bypass the Go config system. All persistent config flows through the TOML config file via Wails bindings, consistent with existing patterns (theme, tracklist columns, favorites).
+- **Anti-pattern: Using `e.keyCode` or `e.which`:** Use `e.key` and `e.code` — they're the modern standard and handle international keyboards correctly.
+- **Anti-pattern: Cancelling scan inside a transaction:** The batch commit is already atomic. Cancellation should happen between batches, not mid-transaction.
+
+## Don't Hand-Roll
+
+| Problem | Don't Build | Use Instead | Why |
+|---------|-------------|-------------|-----|
+| Key event normalization | Custom key string builder from scratch | `e.key` + modifier booleans (`e.ctrlKey`, `e.shiftKey`, etc.) | The browser API is sufficient; `e.key` returns the logical key value |
+| Context cancellation | Custom goroutine signaling | `context.WithCancel` | Standard Go pattern, already partially in use in the scan pipeline |
+| Goroutine pause | Manual sync.Mutex lock/unlock cycling | Channel-based blocking | Channels compose naturally with `select` and context cancellation |
+
+**Key insight:** Both features (scan control and keyboard shortcuts) are well-served by standard library/platform capabilities. No external dependencies are needed.
+
+## Common Pitfalls
+
+### Pitfall 1: Orphan Cleanup After Cancelled Scan
+**What goes wrong:** The scan's orphan cleanup phase (Phase 5) iterates `existingPaths` and deletes DB entries for files not found on disk. If a scan is cancelled mid-way, `existingPaths` still contains files that weren't visited yet — they'd be incorrectly deleted as "orphans."
+**Why it happens:** The scan loads all existing files into `existingPaths` at the start, then removes entries as they're found during the walk. A cancelled walk leaves legitimate files in the map.
+**How to avoid:** Skip orphan cleanup entirely when the scan is cancelled. This is already called out as a warning in STATE.md: "Scan cancellation: skip orphan cleanup on cancelled scans."
+**Warning signs:** Tracks disappearing from the library after cancelling a scan.
+
+### Pitfall 2: Shadow DOM Active Element Detection
+**What goes wrong:** `document.activeElement` returns the host element of a shadow root, not the actual focused element inside. Shortcut suppression during text input would fail because the check sees `` not ``.
+**Why it happens:** Lit components use Shadow DOM. The focused `` inside `` shadow root isn't directly visible to `document.activeElement`.
+**How to avoid:** Walk the `shadowRoot.activeElement` chain recursively until reaching the leaf focused element (shown in Pattern 3 above).
+**Warning signs:** Keyboard shortcuts firing while typing in the search box.
+
+### Pitfall 3: Race Between Cancel and Batch Commit
+**What goes wrong:** Calling `CancelScan()` while a batch transaction is in progress could leave the database in an inconsistent state if the context is cancelled during `tx.Commit()`.
+**Why it happens:** SQLite `Commit()` with modernc.org/sqlite checks context cancellation.
+**How to avoid:** The scan context should be checked between batches, not during a commit. Use a separate check: after each `flushBatch()` call, check if `scanCtx` is done before processing more results. The batch commit itself should use the parent `l.ctx` (not the scan-specific cancellable context) so in-flight transactions always complete.
+**Warning signs:** "database is locked" errors or partial batch commits.
+
+### Pitfall 4: Key Combo String Normalization
+**What goes wrong:** Different representations of the same key combo: "ctrl+f" vs "Ctrl+F" vs "Control+f" — lookups fail.
+**Why it happens:** No consistent normalization of key strings.
+**How to avoid:** Define a canonical format: modifiers in fixed order (Ctrl+Alt+Shift+Meta) + lowercase key name. Always normalize both when storing and when matching.
+**Warning signs:** Shortcuts not firing after reassignment, or duplicate entries in settings.
+
+### Pitfall 5: Space Key Conflicts with Scrollable Areas
+**What goes wrong:** Space is the default browser scroll-down key. If Space is bound to play/pause globally, scrollable panels may stop scrolling.
+**Why it happens:** `e.preventDefault()` on Space prevents the browser's native scroll behavior.
+**How to avoid:** The scope system handles this — when a scrollable panel has focus and the user intends to scroll, the panel-specific scope should not have Space bound. The Global scope's Space binding calls `preventDefault()` which is acceptable since this is a desktop app (not a web page), and the primary use of Space is play/pause.
+**Warning signs:** Users unable to scroll with keyboard in track lists.
+
+### Pitfall 6: Partial Results Handling on Cancel
+**What goes wrong:** When user cancels and chooses "discard," the backend has already committed batches to the database. Rolling back multiple committed transactions is complex.
+**Why it happens:** Scan writes in batches of 50 that are committed as they go.
+**How to avoid:** "Discard" means "delete the tracks added during this scan." Track which audio file IDs were added during the current scan (via the `added` counter mechanism — extend to track IDs). On discard, delete those specific records. Alternatively, simpler: "discard" triggers a FullRescan minus the cancel-interrupted data. Given complexity, the simpler approach is: "Keep" is the default, "Discard" just clears the entire library (same as FullRescan clear phase) since partial state is unreliable.
+**Warning signs:** Stale or duplicate entries after cancel-and-discard.
+
+## Code Examples
+
+### Scan Control — Backend Methods
+
+```go
+// scan_control.go
+
+// CancelScan cancels an in-progress scan. Returns immediately;
+// the scan goroutines will stop at their next check point.
+func (l *Library) CancelScan() {
+ l.mu.Lock()
+ defer l.mu.Unlock()
+
+ if l.scanCancel != nil {
+ l.scanCancel()
+ }
+}
+
+// PauseScan pauses an in-progress scan. Workers block at their
+// next pause checkpoint until ResumeScan is called.
+func (l *Library) PauseScan() {
+ l.mu.Lock()
+ defer l.mu.Unlock()
+
+ if !l.scanActive || l.scanPaused {
+ return
+ }
+
+ l.scanPaused = true
+ l.scanPauseCh = make(chan struct{})
+
+ runtime.EventsEmit(l.ctx, events.LibraryScanPaused)
+}
+
+// ResumeScan unblocks a paused scan.
+func (l *Library) ResumeScan() {
+ l.mu.Lock()
+ defer l.mu.Unlock()
+
+ if !l.scanPaused {
+ return
+ }
+
+ l.scanPaused = false
+ close(l.scanPauseCh) // unblocks all waiting workers
+
+ runtime.EventsEmit(l.ctx, events.LibraryScanResumed)
+}
+
+// IsScanActive returns the current scan state for the frontend.
+func (l *Library) IsScanActive() bool {
+ l.mu.Lock()
+ defer l.mu.Unlock()
+ return l.scanActive
+}
+
+// IsScanPaused returns whether the scan is currently paused.
+func (l *Library) IsScanPaused() bool {
+ l.mu.Lock()
+ defer l.mu.Unlock()
+ return l.scanPaused
+}
+```
+
+### Key String Builder
+
+```typescript
+// keyboard-shortcut-service.ts
+function buildKeyString(e: KeyboardEvent): string {
+ const parts: string[] = [];
+
+ if (e.ctrlKey || e.metaKey) parts.push('Ctrl');
+ if (e.altKey) parts.push('Alt');
+ if (e.shiftKey) parts.push('Shift');
+
+ // Normalize key name
+ let key = e.key;
+
+ // Skip standalone modifier presses
+ if (['Control', 'Alt', 'Shift', 'Meta'].includes(key)) {
+ return '';
+ }
+
+ // Normalize common key names
+ if (key === ' ') key = 'Space';
+ if (key === 'ArrowUp') key = 'Up';
+ if (key === 'ArrowDown') key = 'Down';
+ if (key === 'ArrowLeft') key = 'Left';
+ if (key === 'ArrowRight') key = 'Right';
+
+ // Single character keys: uppercase for display
+ if (key.length === 1) key = key.toUpperCase();
+
+ parts.push(key);
+
+ return parts.join('+');
+}
+```
+
+### Default Bindings Map
+
+```typescript
+// Based on user decisions + common media player conventions
+const DEFAULT_BINDINGS: Record = {
+ // Player controls (Global scope, no modifier)
+ 'player.playPause': { key: 'Space', scope: 'global', category: 'Player' },
+ 'player.volumeUp': { key: 'Up', scope: 'global', category: 'Player' },
+ 'player.volumeDown': { key: 'Down', scope: 'global', category: 'Player' },
+ 'player.seekForward': { key: 'Right', scope: 'global', category: 'Player' },
+ 'player.seekBack': { key: 'Left', scope: 'global', category: 'Player' },
+ 'player.shuffle': { key: 'S', scope: 'global', category: 'Player' },
+ 'player.repeat': { key: 'R', scope: 'global', category: 'Player' },
+ 'player.mute': { key: 'M', scope: 'global', category: 'Player' },
+ 'player.next': { key: 'N', scope: 'global', category: 'Player' },
+ 'player.previous': { key: 'P', scope: 'global', category: 'Player' },
+
+ // Navigation (Global scope)
+ 'nav.search': { key: '/', scope: 'global', category: 'Navigation' },
+ 'nav.searchAlt': { key: 'Ctrl+F', scope: 'global', category: 'Navigation' },
+ 'nav.queue': { key: 'Q', scope: 'global', category: 'Navigation' },
+
+ // App actions (Global scope, Ctrl modifier)
+ 'app.selectAll': { key: 'Ctrl+A', scope: 'global', category: 'App' },
+
+ // Panel-specific (track list focused)
+ 'tracklist.play': { key: 'Enter', scope: 'panel:track-list', category: 'Navigation' },
+ 'tracklist.delete': { key: 'Delete', scope: 'panel:track-list', category: 'Navigation' },
+};
+```
+
+### Shortcut Settings Tab — Key Capture Widget
+
+```typescript
+// shortcut-capture.ts — Record-style key capture (VS Code inspired)
+@customElement('shortcut-capture')
+class ShortcutCapture extends LitElement {
+ @property() action = '';
+ @property() currentKey = '';
+ @state() private recording = false;
+ @state() private pendingKey = '';
+
+ private handleClick = () => {
+ this.recording = true;
+ this.pendingKey = '';
+ };
+
+ private handleKeydown = (e: KeyboardEvent) => {
+ if (!this.recording) return;
+
+ e.preventDefault();
+ e.stopPropagation();
+
+ const keyStr = buildKeyString(e);
+ if (!keyStr) return; // bare modifier press
+
+ if (keyStr === 'Escape') {
+ // Cancel recording
+ this.recording = false;
+ this.pendingKey = '';
+ return;
+ }
+
+ this.pendingKey = keyStr;
+ this.recording = false;
+
+ // Dispatch event for parent to handle conflict check + save
+ this.dispatchEvent(new CustomEvent('shortcut-change', {
+ detail: { action: this.action, key: keyStr },
+ bubbles: true, composed: true,
+ }));
+ };
+
+ override render() {
+ return html`
+
+ `;
+ }
+}
+```
+
+## State of the Art
+
+| Old Approach | Current Approach | When Changed | Impact |
+|--------------|------------------|--------------|--------|
+| `KeyboardEvent.keyCode` | `KeyboardEvent.key` / `.code` | Deprecated for years | Use `.key` for logical key, `.code` for physical position |
+| Manual goroutine cancellation with channels | `context.WithCancel` | Standard since Go 1.7 (2016) | Composes with existing context-aware APIs |
+| Global keyboard shortcut libraries (mousetrap, hotkeys.js) | Native KeyboardEvent API | N/A | Desktop Wails app doesn't need library overhead |
+
+**Deprecated/outdated:**
+- `KeyboardEvent.keyCode` / `KeyboardEvent.which`: Deprecated. Use `.key` for the logical key value.
+- `KeyboardEvent.charCode`: Removed. Not relevant for this use case.
+
+## Open Questions
+
+1. **Volume step size for arrow keys**
+ - What we know: Up/Down arrows should adjust volume. Player.SetVolume accepts 0-100 integer.
+ - What's unclear: Step size per keypress (5? 10?)
+ - Recommendation: Default to 5 units per keypress (matches common media player conventions). This is a Claude's Discretion item.
+
+2. **Seek step size for arrow keys**
+ - What we know: Left/Right arrows should seek. Player.Seek accepts seconds.
+ - What's unclear: How many seconds per keypress.
+ - Recommendation: Default to 5 seconds per keypress. This is a Claude's Discretion item.
+
+3. **"Discard" implementation on scan cancel**
+ - What we know: User can choose "Keep X tracks" or "Discard." Keeping is straightforward (do nothing).
+ - What's unclear: Precise discard mechanism — delete individual added IDs vs clear-and-rescan approach.
+ - Recommendation: Track added audio file IDs during the scan. On discard, batch-delete those IDs within a transaction. This avoids the nuclear option of a full library clear while being precise. If this proves too complex, a simpler fallback is to trigger the library clear tables operation (existing `clearLibraryTables()`) and leave the user with an empty library that they can rescan.
+
+4. **N and P for next/previous vs typing**
+ - What we know: Single-key shortcuts (S, R, Q) work in global scope. N/P follow the same pattern.
+ - What's unclear: Whether N/P could conflict with other planned features (e.g., future search-as-you-type).
+ - Recommendation: Include N/P as defaults but since all bindings are configurable, users can remap if conflicts arise. The text input scope suppression ensures they don't fire during typing.
+
+## Sources
+
+### Primary (HIGH confidence)
+- **Codebase analysis** — Direct reading of all scanner, config, events, and frontend component source files
+- **Go `context` package** — Standard library documentation for `WithCancel` pattern
+- **MDN `KeyboardEvent`** — `e.key`, `e.code`, modifier properties (`ctrlKey`, `altKey`, `shiftKey`, `metaKey`)
+
+### Secondary (MEDIUM confidence)
+- **VS Code keybinding UX** — Reference for record-style key capture interaction pattern (widely adopted UX pattern)
+- **Wails v2 event system** — `runtime.EventsEmit` / `EventsOn` patterns verified from existing codebase usage
+
+## Metadata
+
+**Confidence breakdown:**
+- Standard stack: HIGH — no new dependencies, all patterns verified from existing codebase and Go/Web standards
+- Architecture: HIGH — extends existing patterns (config sections, Wails bindings, Lit components, event system)
+- Pitfalls: HIGH — identified from direct codebase analysis (shadow DOM, orphan cleanup, batch commits)
+
+**Research date:** 2026-03-06
+**Valid until:** 2026-04-06 (stable domain — no rapidly changing dependencies)