Skip to content

refactor(editor): move the annotation lanes to src/annotation-lanes.js (R2, step 16) - #167

Merged
byrongamatos merged 2 commits into
mainfrom
refactor/r2-step16-annotation-lanes
Jul 9, 2026
Merged

refactor(editor): move the annotation lanes to src/annotation-lanes.js (R2, step 16)#167
byrongamatos merged 2 commits into
mainfrom
refactor/r2-step16-annotation-lanes

Conversation

@byrongamatos

@byrongamatos byrongamatos commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Nineteenth module. src/main.js 17,756 → 16,078 — 1,678 lines, and the graph stays acyclic.

What moved

The tone lane, the anchor lane and the handshape lane. They turned out to be a contiguous tail of the main.js IIFE, so the excision is a truncation.

They travel together rather than as three modules because they lean on each other: the handshape lane positions itself off _anchorLaneTopY, and both it and the anchor lane share _currentAnchorArr. Split apart, those become cross-module imports for no gain. main.js keeps the canvas event routing — deciding which strip is under the cursor — and forwards to the on*LaneMouse* handlers.

Four main.js symbols travel back and would close a cycle, so they arrive through setLaneHooks(). Two of them deliberately stay behind:

  • snapTime — its onset-snap path reaches _ensureOnsets and the onset cache.
  • _editorPromptText — it owns a modal and the shared _editorPromptCancel handle.

Neither is a lane concern. TONE_LANE_H moved to geometry.js, joining ANCHOR_LANE_H and HS_LANE_H where it always belonged.

A regression this refactor’s own predecessors introduced

Codex caught it on review, and it is already on main.

draw is reassigned near the bottom of main.js to a wrapper that refreshes seven toolbar buttons before repainting:

const _origDraw = draw;
draw = function () {
    _refreshDrumEditButton(); _refreshDrumDensityButton(); _refreshTempoMapButton();
    /* …4 more… */
    return _origDraw.apply(this, arguments);
};

setHistoryHooks({ draw, … }) and setDrumHooks({ draw, … }) were handed the bare identifier, so they captured the original function at wiring time. Since #165/#166, every undo, redo and drum-density toggle has skipped those seven refreshes.

The canvas repaints either way — which is precisely why nothing looked wrong, and why my own verify_drum.py passed. The one visible symptom: the drum-density button keeps its Rows: Full label after the grid has already collapsed to Compact. All three hook sites now take _drawLive = (...args) => draw(...args), resolving the live binding at call time, as the in-IIFE call sites always did.

verify_drum.py now asserts the label. It fails on the pre-fix code.

Tests

anchor_authoring (CJS → .mjs) and handshape_authoring stop brace-matching declarations out of main.js and import them instead.

Why a headless harness, again

Only the anchor/handshape authoring helpers had unit coverage. The draw passes, the mouse handlers and all four hooks are invisible to node --test. verify_lanes.py clicks the anchor lane:

check hooks wired hooks missing
the lanes paint their titles PASS PASS
clicking the anchor lane adds an anchor PASS FAIL
the click repaints (_hooks.draw) PASS FAIL
the anchor snaps to the grid (_hooks.snapTime) PASS FAIL
the tones modal opens (window.* re-attach) PASS PASS

The snapTime check is the interesting one: its no-op default is the identity function, so a dead hook silently places the anchor exactly under the cursor instead of on the grid. Comment out setLaneHooks(...) and all 88 unit tests still pass.

Verification

node --test 88/88 · pytest 248/248 · npm run lint 0 errors (9 warnings) · Codex clean · all 12 headless harnesses pass.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added interactive annotation lanes for tones, anchors, and handshapes with drag, edit, delete, and context-menu actions.
    • Added tone lane controls and a dedicated “Tones” modal for updating tone slots.
    • Included tone lane overlay at the top of charts.
  • Bug Fixes
    • Fixed toolbar refreshes being skipped after hook-callback updates.
    • Improved annotation authoring/save consistency, including promoting fallback anchors into authored anchors.
  • Tests
    • Updated authoring tests to use direct imports and command-based state seeding.

…s (R2, step 16)

src/main.js 17,756 -> 16,078. Nineteenth module; the graph stays acyclic.
1,678 lines — the tone lane, the anchor lane and the handshape lane, which
turned out to be a contiguous tail of the IIFE.

They move together because they lean on each other: the handshape lane
positions itself off _anchorLaneTopY, and both it and the anchor lane share
_currentAnchorArr. Split apart, those would be cross-module imports for no gain.
main.js keeps the canvas event routing (deciding which strip is under the
cursor) and forwards to the on*LaneMouse* handlers.

Four main.js symbols travel back — draw, hideContextMenu, snapTime,
_editorPromptText — and would close a cycle, so they arrive through
setLaneHooks(). snapTime stays behind because its onset-snap path reaches
_ensureOnsets and the onset cache; _editorPromptText stays because it owns a
modal and the shared _editorPromptCancel handle. Neither is a lane concern.

TONE_LANE_H moved to geometry.js, where ANCHOR_LANE_H and HS_LANE_H already
live. The tones modal's three window.* handlers became exported functions that
main.js re-attaches: a top-level `window.x =` throws when the module is
imported under node. Two internal call sites that reached back through
window.editorHideTonesModal() now call the module-local function (Codex).

Also fixes a regression this refactor's own predecessors introduced.

`draw` is reassigned near the bottom of main.js to a wrapper that refreshes
seven toolbar buttons before repainting. setHistoryHooks() and setDrumHooks()
were handed the bare identifier, so they captured the ORIGINAL function at
wiring time; every undo, redo and drum-density toggle has been skipping those
refreshes since #165/#166. The canvas repaints either way, which is exactly why
it went unnoticed — the only visible symptom is the drum-density button keeping
its "Rows: Full" label after the grid has collapsed to Compact. All three hook
sites now take `_drawLive = (...args) => draw(...args)`, resolving the live
binding at call time as the in-IIFE call sites always did. Found by Codex;
verify_drum.py now asserts the label, and fails on the pre-fix code.

Tests: anchor_authoring (CJS -> .mjs) and handshape_authoring stop brace-matching
declarations out of main.js and import them.

Verified beyond the unit tests, which cannot see hook wiring: verify_lanes.py
clicks the anchor lane and asserts an anchor is added, the canvas repaints
(_hooks.draw), the anchor lands on the grid rather than under the cursor
(_hooks.snapTime), and the tones modal opens through the re-attached window.*
handler. Comment out setLaneHooks() and all 88 unit tests still pass while three
of its eight checks fail.

node --test 88/88, pytest 248/248, npm run lint 0 errors, Codex clean, all 12
headless harnesses pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 9, 2026 20:11
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 279bccca-4299-43cc-a2b5-f20be0fc8ec2

📥 Commits

Reviewing files that changed from the base of the PR and between c9ab2d8 and a2aa2c1.

📒 Files selected for processing (2)
  • src/annotation-lanes.js
  • tests/handshape_authoring.test.mjs
🚧 Files skipped from review as they are similar to previous changes (2)
  • tests/handshape_authoring.test.mjs
  • src/annotation-lanes.js

📝 Walkthrough

Walkthrough

Extracts tone, anchor, and handshape annotation lane logic into src/annotation-lanes.js, rewires src/main.js hook registration through a live draw wrapper, adds a tone-lane height constant, and updates tests to import lane helpers directly.

Changes

Annotation lanes extraction and hook wiring fix

Layer / File(s) Summary
Live-binding hook wrapper fix and module wiring
src/main.js, src/geometry.js, CHANGELOG.md
Adds _drawLive for hook registration, attaches tone modal handlers to window, updates annotation-lanes and chords imports, removes the old hoisted tone-lane constants block, adds TONE_LANE_H, and documents the change.
Tone lane: state, rendering, commands, modal
src/annotation-lanes.js
Adds tone slot defaults/colors, slot derivation, dirty tracking, internal-field stripping, canvas rendering, hit-testing, undoable add/remove/move/rename commands, mouse/context-menu handlers, and the Tones modal with validation.
Anchor lane: promotion, commands, interactions
src/annotation-lanes.js
Adds authored-versus-auto anchor rendering, hit-testing, promotion of auto anchors into anchors_user, add/remove/edit-template commands, and mouse/context-menu handlers.
Handshape lane: drawing, commands, interactions
src/annotation-lanes.js
Adds handshape drawing, edge hit-testing, span-to-fret resolution, add/remove/move/resize commands with template relinking, and mouse/context-menu handlers.
Test migration to direct module imports
tests/anchor_authoring.test.mjs, tests/handshape_authoring.test.mjs
Replaces CommonJS brace-matching extraction from src/main.js with direct ESM imports from src/annotation-lanes.js, using seedState-based history stubbing.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant MainJS as main.js
  participant DrawLive as _drawLive
  participant AnnotationLanes as annotation-lanes.js
  MainJS->>DrawLive: wrap current draw reference
  MainJS->>AnnotationLanes: setLaneHooks(_drawLive)
  AnnotationLanes->>DrawLive: invoke hook after edit
  DrawLive->>MainJS: call current draw() implementation
Loading
sequenceDiagram
  participant User
  participant AnchorLane as annotation-lanes.js
  participant PromoteAnchorsCmd
  participant Arrangement as arr.anchors_user
  User->>AnchorLane: click or drag auto anchor
  AnchorLane->>PromoteAnchorsCmd: exec(autoAnchors, clicked, extra)
  PromoteAnchorsCmd->>Arrangement: seed full fallback set into anchors_user
  Arrangement-->>AnchorLane: updated authored anchors
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.55% 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 accurately summarizes the main change: moving the annotation lanes into src/annotation-lanes.js.
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 docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch refactor/r2-step16-annotation-lanes

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

Copilot AI 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.

Pull request overview

Refactors the editor by extracting the tone/anchor/handshape “annotation lanes” out of the monolithic src/main.js into a dedicated module, while also fixing a subtle regression where hook consumers captured the pre-wrapper draw() implementation and therefore skipped toolbar refreshes during undo/redo and drum-density toggles.

Changes:

  • Moved the tone lane, anchor lane, and handshape lane into src/annotation-lanes.js and wired the module back into main.js via setLaneHooks() plus window.* re-attachments for modal handlers.
  • Fixed stale draw captures by passing a live-binding thunk (_drawLive) to setHistoryHooks(), setDrumHooks(), and setLaneHooks().
  • Updated lane geometry (TONE_LANE_H), tests to import real module symbols, and the changelog entry for the regression/fix.

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/annotation-lanes.js New module containing tone/anchor/handshape lane drawing, commands, and mouse handlers, with cycle-breaking hooks.
src/main.js Imports and wires annotation-lanes; fixes hook wiring to use a live draw binding; re-attaches window.* modal handlers.
src/geometry.js Adds exported TONE_LANE_H constant alongside other lane height constants.
tests/anchor_authoring.test.mjs Stops brace-matching from main.js; imports lane authoring code from annotation-lanes.js and seeds real S.
tests/handshape_authoring.test.mjs Stops brace-matching from main.js; imports _handshapeSpanFrets from annotation-lanes.js.
CHANGELOG.md Documents the draw hook capture regression fix and the annotation lanes extraction.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread tests/handshape_authoring.test.mjs Outdated
Comment thread src/annotation-lanes.js Outdated
Comment thread src/annotation-lanes.js Outdated
…ed code

Copilot, on #167. The three comments travelled verbatim with the code and now
describe an ES module: TONE_LANE_H comes from geometry.js rather than being
declared at the top of the IIFE; _editorConfirmToneDefinitions is an ordinary
export, not a plain function relying on the global -> IIFE fallback; and
_handshapeSpanFrets is imported, not brace-matched out of main.js.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@byrongamatos

Copy link
Copy Markdown
Collaborator Author

All three are correct — the comments travelled verbatim with the code and were describing the IIFE they no longer live in. Fixed:

  • annotation-lanes.js header block: TONE_LANE_H now comes from geometry.js; only _TONE_SLOT_DEFAULTS/_TONE_SLOT_COLORS are declared locally.
  • _editorConfirmToneDefinitions: it is an ordinary export now, so the note about the browser's global → IIFE fallback is gone.
  • handshape_authoring.test.mjs: _handshapeSpanFrets is imported, not brace-matched out of main.js.

@byrongamatos
byrongamatos merged commit 557b895 into main Jul 9, 2026
4 checks passed
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