Skip to content

feat(editor): four keybind profiles — FeedBack / Logical / Cableton / Legacy (EOF) - #266

Merged
byrongamatos merged 4 commits into
mainfrom
feat/editor-keybind-profiles
Jul 14, 2026
Merged

feat(editor): four keybind profiles — FeedBack / Logical / Cableton / Legacy (EOF)#266
byrongamatos merged 4 commits into
mainfrom
feat/editor-keybind-profiles

Conversation

@ChrisBeWithYou

@ChrisBeWithYou ChrisBeWithYou commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

What

Fully fleshed-out shortcut profiles so a charter's DAW muscle memory just works:

  • Logical (Logic-style) — authentic Logic defaults, sourced from the Logic Pro user guide's key-command appendix: K metronome, Q quantize (Resnap selection), , / . rewind/forward by beat, C cycle (loop the selected region), Alt+' create marker (Add section), Ctrl+R repeat (Duplicate selection).
  • Cableton (Ableton-style) — authentic Live 12 defaults from the manual's shortcut chapter (Windows column): Ctrl+U Quantize, Ctrl+1 / Ctrl+2 Narrow/Widen Grid (snap finer/coarser), Ctrl+4 Snap to Grid, O Metronome, Ctrl+Shift+F Follow, Ctrl+L Loop Selection.
  • FeedBack and Legacy (EOF) are untouched — both keep their battle-tested hand resolvers bit-exactly ('eof' keeps its internal id, so saved localStorage prefs survive; only the display name changes).

Architecture: the new profiles are deltas. Each is a frozen sig→command override table resolved first; any key the table doesn't claim falls through to its FeedBack meaning — so every editor-specific command (techniques, tempo mapping, string moves) works identically in all profiles, and the old FeedBack key remains a harmless alias for relocated commands. Where a DAW key displaces a FeedBack one (Logical's K displaces pick-direction cycling; Cableton's O displaces pop, Ctrl+L displaces select-like), the displaced command relocates or goes explicitly keyless — and the registry's absent-vs-'' distinction makes the shortcut panel display exactly what resolves, never a stolen key.

Also in this PR (both improve the command palette's coverage): toggleLoopRegion and songFit join the command registry — Song Fit gains its first menu home (Tempo/Grid ▸ Song Fit), and the loop toggle is what Logical's C / Cableton's Ctrl+L drive.

Tests

tests/keybind_profiles.test.mjs (9, fail on main): every authentic binding resolves; fall-through inheritance; the shadow rule including honest keyless display; tempo-map overlay reachable through the fall-through; override sigs disjoint from the tempo-map overlay (validator); four-profile plumbing with unknown-value fallback; EOF rows byte-identical; the two new registry commands. user_guide_content, menu_model, eof_shortcuts all green unchanged. Full suite 147 green, lint 0 errors, routes.py untouched. docs/USER-GUIDE.md updated.

Live-verified

Real pak: switched to Logical → K toggles the metronome ("Metronome on — clicks follow the beat grid"), **, steps the playhead back one beat; switched to Cableton → Ctrl+4 flips S.snapEnabled, O toggles the metronome off; Legacy still selects and announces itself. Zero page errors.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q

Summary by CodeRabbit

  • New Features
    • Added Logical and Cableton shortcut profiles (with Legacy/EOF preserved) and improved profile switching/cycling.
    • Added globally available “Loop toggle for the selected region” and “Song Fit” commands (Song Fit added to the Tempo/Grid menu).
  • Documentation
    • Expanded the user guide with detailed shortcut profile behavior and how to switch profiles.
  • Tests
    • Added automated coverage for key mapping, inheritance/shadowing behavior, command resolution, and new registry commands.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@byrongamatos, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 24 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 7898a512-393f-4e17-9a8b-85ad0415cc68

📥 Commits

Reviewing files that changed from the base of the PR and between aabe1b1 and 0003993.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • docs/USER-GUIDE.md
  • screen.html
  • src/input.js
  • src/menu-bar.js
  • src/shortcuts.js
  • tests/keybind_profiles.test.mjs
📝 Walkthrough

Walkthrough

The editor now supports FeedBack, Logical, Cableton, and Legacy shortcut profiles with inherited and overridden mappings. Profile cycling and selectors expose all four profiles, while Loop toggle and Song Fit are registered and dispatched as global commands.

Changes

Shortcut profile system

Layer / File(s) Summary
Profile definitions and mappings
src/shortcuts.js
Adds Logical and Cableton profiles, display names, and profile-specific shortcut mappings.
Override resolution and shortcut rows
src/shortcuts.js, src/input.js
Adds override-first resolution, FeedBack fallback, shadow handling, collision validation, and profile-aware dispatch and status labels.
Command dispatch and profile cycling
src/input.js, src/menu-bar.js, screen.html
Dispatches Loop toggle and Song Fit, adds Song Fit to Tempo/Grid, cycles through four profiles, and updates both profile selectors.
Validation and user-facing documentation
tests/keybind_profiles.test.mjs, docs/USER-GUIDE.md, CHANGELOG.md
Tests mappings, inheritance, profile selection, and command registration while documenting the new profiles and commands.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant EditorUser
  participant ShortcutProfileSelector
  participant _editorDispatchFeedbackShortcut
  participant _editorTableCommandForKeyPure
  participant _editorRunEofCommand
  participant EditorCommand
  EditorUser->>ShortcutProfileSelector: choose or cycle shortcut profile
  EditorUser->_editorDispatchFeedbackShortcut: press shortcut
  _editorDispatchFeedbackShortcut->_editorTableCommandForKeyPure: resolve profile override or FeedBack mapping
  _editorTableCommandForKeyPure-->>_editorDispatchFeedbackShortcut: command identifier
  _editorDispatchFeedbackShortcut->_editorRunEofCommand: dispatch command
  _editorRunEofCommand->>EditorCommand: run toggleLoopRegion or songFit
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 27.27% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and accurately summarizes the main change: four editor keybind profiles.
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.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/editor-keybind-profiles

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/menu-bar.js (1)

189-193: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Transport ▸ Loop region should use the registry command row. The current { fn: 'editorToggleLoopRegion' } entry renders an empty key column, so the Logical/Cableton shortcut never appears in the menu. Switching it to { cmd: 'toggleLoopRegion' } would surface the accelerator.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/menu-bar.js` around lines 189 - 193, Update the Loop region menu entry in
the menu definition to use the registry command key toggleLoopRegion instead of
the direct editorToggleLoopRegion function reference, preserving its label so
the configured accelerator appears.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/menu-bar.js`:
- Around line 189-193: Update the Loop region menu entry in the menu definition
to use the registry command key toggleLoopRegion instead of the direct
editorToggleLoopRegion function reference, preserving its label so the
configured accelerator appears.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: be8e6525-d50f-4dea-8dac-49f9dfb0b799

📥 Commits

Reviewing files that changed from the base of the PR and between c85b7de and 8a68de5.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • docs/USER-GUIDE.md
  • screen.html
  • src/input.js
  • src/menu-bar.js
  • src/shortcuts.js
  • tests/keybind_profiles.test.mjs

@byrongamatos
byrongamatos force-pushed the feat/editor-keybind-profiles branch from 8a68de5 to 8692d5b Compare July 14, 2026 18:12
@byrongamatos

Copy link
Copy Markdown
Collaborator

Applied — good catch. toggleLoopRegion became a registry command in this PR but the Transport entry was still { label, fn: 'editorToggleLoopRegion' }, and the fn branch of _menuModelPure renders it.key || '', so the new Logical (C) / Cableton (Ctrl+L) accelerator never showed in the menu. Now { cmd: 'toggleLoopRegion' }, matching its sibling { cmd: 'toggleLoopAB' } right below. (49e0bd3)

Also landed two self-review fixes on this head, both real:

  • Logical bound Ctrl+R to duplicateSelection ('authentic: Cmd-R Repeat'). The Electron host registers an app menu with {role:'reload'}, whose CmdOrCtrl+R accelerator is handled in the main process before the renderer sees keydown — the dispatcher's preventDefault() cannot reclaim it. That chord would have reloaded the editor and dropped unsaved chart edits instead of duplicating. Dropped; Ctrl+D already duplicates in every profile (handled in input.js outside the resolvers).
  • Logical stranded two commands with no keyboard: K→metronome left cyclePickDirection keyless and C→loop left toggleGuideClap keyless. Cableton relocates both of its shadowed commands, so this was inconsistent as well as lossy — relocated to Shift+K and Ctrl+Shift+C.

Both now have regression guards in tests/keybind_profiles.test.mjs (no delta profile may orphan a command the default can reach; no profile may bind a host-reserved chord).

@byrongamatos

Copy link
Copy Markdown
Collaborator

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
tests/keybind_profiles.test.mjs (3)

18-18: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick win

Redundant static import may execute before the global mocks it depends on.

import * as shortcuts from '../src/shortcuts.js' (line 31) is a static import; per ES module semantics it is fully resolved — meaning shortcuts.js's top-level code runs — before any of this file's own top-level statements execute, including the globalThis.document/localStorage/window mock setup on lines 20-24. The subsequent await import(...) on line 26 just returns the already-cached, already-evaluated module namespace; it can't re-run shortcuts.js with the mocks in place. If shortcuts.js's module-level code reads any of those globals at import time (which is presumably why the mocks exist), it will see the real Node globals instead, not these stubs.

Since the dynamic import already yields the full module namespace, drop the redundant static import and reference everything through the dynamically-imported object.

🔧 Proposed fix
 const {
     EDITOR_PROFILE_NAMES, EDITOR_PROFILE_OVERRIDES,
     _editorProfileCollisionsPure, _editorShortcutRowsPure, _editorTableCommandForKeyPure,
     editorSetShortcutProfile,
-} = await import('../src/shortcuts.js');
-import * as shortcuts from '../src/shortcuts.js';
+} = shortcuts;
+const shortcuts = await import('../src/shortcuts.js');

Also applies to: 26-31

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/keybind_profiles.test.mjs` at line 18, Remove the static shortcuts
import from the test module so shortcuts.js is not evaluated before the global
document, localStorage, and window mocks are installed. Keep the existing
dynamic import after mock setup, and update all references to the statically
imported shortcuts binding to use that dynamically imported module namespace.

23-23: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

localStorage stub can't verify Legacy's persisted-key compatibility.

The mock's setItem is a no-op (line 23), so the profile-selection test only asserts the in-memory editorShortcutProfile value, not what actually gets persisted. Given the PR explicitly calls out "Legacy retaining its internal ID for localStorage compatibility" as an intentional guarantee, a spy-backed mock that records setItem calls would let this test assert the persisted key/value directly rather than only the exported variable.

Also applies to: 167-177

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/keybind_profiles.test.mjs` at line 23, Update the localStorage stub in
tests/keybind_profiles.test.mjs to record setItem calls and stored key/value
pairs instead of discarding writes, then extend the profile-selection test
around the legacy profile to assert that the persisted value uses Legacy’s
compatibility ID. Keep the existing in-memory editorShortcutProfile assertion.

113-118: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Hardcoded TEMPO_MAP_SIGS risks silent drift from the real overlay.

This literal array duplicates the actual tempo-map overlay signature set that presumably lives in src/shortcuts.js. If that overlay changes there, this list won't reflect it and the "disjoint from tempo-map overlay" guarantee silently stops being meaningful without the test failing. Prefer importing the canonical list (if exported) rather than re-declaring it here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/keybind_profiles.test.mjs` around lines 113 - 118, The test’s hardcoded
TEMPO_MAP_SIGS can drift from the real tempo-map overlay. Update the test to
import and reuse the canonical overlay signature list exported from
src/shortcuts.js, then keep both _editorProfileCollisionsPure assertions against
that shared list and remove the duplicate literal.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@tests/keybind_profiles.test.mjs`:
- Line 18: Remove the static shortcuts import from the test module so
shortcuts.js is not evaluated before the global document, localStorage, and
window mocks are installed. Keep the existing dynamic import after mock setup,
and update all references to the statically imported shortcuts binding to use
that dynamically imported module namespace.
- Line 23: Update the localStorage stub in tests/keybind_profiles.test.mjs to
record setItem calls and stored key/value pairs instead of discarding writes,
then extend the profile-selection test around the legacy profile to assert that
the persisted value uses Legacy’s compatibility ID. Keep the existing in-memory
editorShortcutProfile assertion.
- Around line 113-118: The test’s hardcoded TEMPO_MAP_SIGS can drift from the
real tempo-map overlay. Update the test to import and reuse the canonical
overlay signature list exported from src/shortcuts.js, then keep both
_editorProfileCollisionsPure assertions against that shared list and remove the
duplicate literal.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: e11d76f4-a5a8-4f44-9946-8ecc856ec519

📥 Commits

Reviewing files that changed from the base of the PR and between 8a68de5 and aabe1b1.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • docs/USER-GUIDE.md
  • screen.html
  • src/input.js
  • src/menu-bar.js
  • src/shortcuts.js
  • tests/keybind_profiles.test.mjs
🚧 Files skipped from review as they are similar to previous changes (6)
  • CHANGELOG.md
  • src/menu-bar.js
  • screen.html
  • docs/USER-GUIDE.md
  • src/shortcuts.js
  • src/input.js

ChrisBeWithYou and others added 4 commits July 14, 2026 21:09
Four profiles: feedback/eof keep their hand resolvers; logical (Logic
defaults: K click, Q quantize, ,/. transport, C cycle, Alt+' marker,
Ctrl+R repeat) and cableton (Live defaults: Ctrl+U quantize, Ctrl+1/2
grid, Ctrl+4 snap, O click, Ctrl+Shift+F follow, Ctrl+L loop) resolve
an override table first and fall through to the FeedBack meaning.
Shadowed commands relocate or go explicitly keyless (absent-vs-'' in
the registry keys drives honest display). toggleLoopRegion and songFit
join the registry; Song Fit gains a Tempo/Grid menu row.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
…d commands

Review fixes on the keybind-profile PR.

1. Logical bound Ctrl+R to duplicateSelection ("authentic: Cmd-R Repeat").
   The Electron host registers an app menu with {role:'reload'}, whose
   CmdOrCtrl+R accelerator is handled in the MAIN process before the renderer
   ever sees keydown — the dispatcher's e.preventDefault() cannot reclaim it.
   So Ctrl+R would have reloaded the editor and dropped unsaved chart edits
   instead of duplicating. Dropped the binding: duplicateSelection already
   answers Ctrl+D in every profile (input.js handles it outside the resolvers),
   so the command loses nothing and the row now displays the chord that works.

2. Logical stranded two commands with no keyboard at all: 'K' -> metronome left
   cyclePickDirection keyless, and 'C' -> loop left toggleGuideClap keyless.
   Cableton relocates both of ITS shadowed commands, so this was inconsistent as
   well as lossy. Relocated them the same way: Shift+K and Ctrl+Shift+C.

Two regression guards added, the checks that would have caught both:
  - no delta profile orphans a command the default profile can reach, and every
    override round-trips through the resolver it claims;
  - no profile binds a host-reserved chord (Ctrl+R/Ctrl+W/Ctrl+Q/F5).

Verified by exhaustive enumeration of the chord space: no profile double-binds a
chord, no profile loses a command (except EOF/toggleSnap, which is pre-existing
on main), and the feedback + eof resolvers are byte-for-byte identical to main
across all 403 bindings — no existing user's keys change.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
CodeRabbit finding, valid. This PR registered toggleLoopRegion as a registry
command and bound it (Logical C, Cableton Ctrl+L), but the Transport menu entry
was still the old direct-fn form { label, fn: 'editorToggleLoopRegion' }. The
fn branch of _menuModelPure renders `it.key || ''`, so the menu showed an empty
accelerator column and the new keybinding was invisible there. Switching to
{ cmd: 'toggleLoopRegion' } takes the label, key and ready/planned state from
the registry — same as its sibling { cmd: 'toggleLoopAB' } directly below it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
.gitignore lists node_modules/ (trailing slash), which matches a
directory but not the worktree symlink, so git add -A tracked it.
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