Skip to content

refactor(editor): move the drum editor to src/drum.js (R2, step 15) - #166

Merged
byrongamatos merged 1 commit into
mainfrom
refactor/r2-step15-drum
Jul 9, 2026
Merged

refactor(editor): move the drum editor to src/drum.js (R2, step 15)#166
byrongamatos merged 1 commit into
mainfrom
refactor/r2-step15-drum

Conversation

@byrongamatos

@byrongamatos byrongamatos commented Jul 9, 2026

Copy link
Copy Markdown
Collaborator

Eighteenth module. src/main.js 18,471 → 17,757 — 714 lines, the largest single lift of the split so far. The graph stays acyclic.

Chosen by measurement

Rather than guessing at the next cluster, I ran a free-identifier scan over every section banner in main.js and ranked them by how many main.js symbols each still reaches for:

section lines external symbols
Drum editor 714 3
Anchor lane 521 4
Tone lane 682 6
Inspector panel 884 11
Tempo Map editor 2418 23
Mouse interactions 598 49

The drum editor wins on size-to-coupling. (The 47 command classes remain the biggest single chunk, but they are interleaved with the feature code that constructs them and are still not a coherent lift.)

The split line

drum.js takes the lane/density model, the limb-lint memo, drum geometry + hit-test, _drumEditorDraw, and the @pure:drum-cmds undo commands.

main.js keeps what genuinely belongs to it: the mouse/drag handlers that construct those commands, the toolbar buttons, the GM-percussion name table, and the MIDI-tempo import flow — which is about tempo, not drums, and was only sharing the section banner. Splitting there is what dropped TempoGridCmd off the dependency list and got the count to three.

Those three (draw, drawWaveform, updateArrangementSelector) would close a cycle, so they arrive via setDrumHooks() — same shape as setHistoryHooks() and setCanvas(). window.editorToggleDrumDensity is re-attached in main.js, because a top-level window.x = throws when drum.js is imported under node, which its tests do.

The typeof S / typeof editGen guards in _drumLimbConflicts existed only to keep the block eval-able in a sliced sandbox. Real imports make them dead.

Tests

All four drum suites now import the real module instead of regex-slicing it; drum_density and drum_limb_lint were CJS and are now .mjs.

drum_limb_lint had two source-locks on the memo key — “the body must mention editGen”, “const key = ... gen”. They collapse into one behavioural assertion: bump editGen, the memo must recompute. Strictly stronger — it fails when the key stops keying on editGen, and it cannot be satisfied by an incidental substring match (editGen contains gen).

Why a headless harness, again

Unit tests import drum.js directly, so they are blind to the hook wiring. verify_drum.py enters drum mode on a pack with a real drum_tab.json:

check hooks wired hooks missing
Full density paints 18 lane rows PASS PASS
drawWaveform’s #08081a band appears in the drum frame PASS FAIL
density toggle repaints PASS FAIL
Compact collapses to the 7-row family shape PASS FAIL
the toggle reports through setStatus PASS PASS

Comment out setDrumHooks(...) and all 88 unit tests still pass while the harness fails three of seven. The status line still updates, so the buttons and messages look fine — only the canvas is stale.

Verification

node --test 88/88 · pytest 248/248 · npm run lint 0 errors (9 warnings, down from 10 — _drumConflictIndexSetPure is no longer an unused orphan) · Codex clean · all 11 headless harnesses pass.

The main.js diff is 729 deletions against 14 added lines: one import block, the hook call, and the re-attached window.* handler.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a drum editor density toggle, letting users switch between full and compact lane layouts.
    • Improved drum hit editing with better selection, drag, velocity, and articulation handling.
  • Bug Fixes

    • Updated drum conflict warnings to refresh more reliably during editing.
    • Improved undo/redo behavior for drum edits and restored values more consistently.
  • Tests

    • Updated drum-related tests to use the real editor modules for more reliable coverage.

src/main.js 18,471 -> 17,757. Eighteenth module; the graph stays acyclic.
714 lines, the largest single lift of the split so far.

Chosen by measurement, not feel: a free-identifier scan over every section
banner in main.js ranked candidate clusters by how many main.js symbols they
still reach for. The drum editor came out at three (draw, drawWaveform,
updateArrangementSelector) for 714 lines — the best size-to-coupling ratio
available. The Tempo Map editor is bigger but needs 23.

drum.js carries the lane/density model, the limb-lint memo, drum geometry +
hit-test, _drumEditorDraw, and the @pure:drum-cmds undo commands. main.js keeps
the mouse/drag handlers that construct those commands, the toolbar buttons, the
GM-percussion name table, and the MIDI-tempo import flow — which is about tempo,
not drums, and was only sharing the section banner. Splitting there is what
dropped TempoGridCmd off the dependency list.

The three main.js symbols would close a cycle, so they arrive through
setDrumHooks(), the same shape as setHistoryHooks() and setCanvas().
window.editorToggleDrumDensity is re-attached in main.js: a top-level
`window.x =` throws when drum.js is imported under node, which its tests do.

The typeof S / typeof editGen guards in _drumLimbConflicts existed only to keep
the block eval-able in a sliced sandbox. Real imports make them dead.

Tests: all four drum suites now import the real module. drum_density and
drum_limb_lint were CJS and are now .mjs. drum_limb_lint's two source-locks on
the memo key ("the body must mention editGen", "const key = ... gen") collapse
into one behavioural assertion — bump editGen, the memo must recompute — which
is strictly stronger: it fails when the key stops keying on editGen, and it
cannot be satisfied by an incidental substring match.

Verified beyond the unit tests, because they cannot see the hook wiring.
verify_drum.py enters drum mode on a pack with a real drum_tab, and asserts Full
density paints 18 lane rows, Compact paints 7, the toggle repaints, and
drawWaveform's signature '#08081a' band appears inside the drum frame. Comment
out setDrumHooks() and all 88 unit tests still pass while that harness fails on
three of its seven checks.

node --test 88/88, pytest 248/248, npm run lint 0 errors (9 warnings, was 10),
Codex clean, all 11 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 19:37
@coderabbitai

coderabbitai Bot commented Jul 9, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The drum editor's lane/density model, playability lint, rendering, hit-testing, and undo command logic are extracted from src/main.js into a new src/drum.js module. main.js now imports these via setDrumHooks to avoid an import cycle. Test suites for drum density, limb lint, undo, and velocity switch from eval-based extraction to direct ES module imports. CHANGELOG documents the refactor.

Changes

Drum editor module extraction

Layer / File(s) Summary
Lane geometry and density model
src/drum.js, tests/drum_density.test.mjs
Adds setDrumHooks, DRUM_PIECE_ORDER, pure lane-table helpers, density toggle with localStorage persistence, piece metadata, and lane geometry helpers; density test imports these directly.
Advisory limb/playability lint
src/drum.js, tests/drum_limb_lint.test.mjs
Implements conflict clustering over time-sorted hits and cross-frame memoization keyed by editGen/hits identity, suppressed during drum-move drag; tests verify memoization, invalidation, and drag suppression via real state.
Rendering and hit-testing
src/drum.js
Adds _drumHitAtPoint and _drumEditorDraw for lane grid, beat lines, hit shapes, selection halo, cursor, marquee, and lint overlay rendering.
Drum hit undo commands
src/drum.js, tests/drum_undo.test.mjs
Adds sort/remap helpers plus AddDrumHitCmd, DeleteDrumHitsCmd, MoveDrumHitsCmd, ToggleDrumArticulationCmd with exec/rollback updating hits, selection, and dirty flags; test harness imports these classes directly.
Velocity and ghost editing
src/drum.js, tests/drum_velocity.test.mjs
Adds DRUM_GHOST_VELOCITY, pure velocity/clamp/import helpers, and SetDrumVelocityCmd with exec/rollback syncing velocity and ghost flags; test harness imports these directly.
main.js wiring and cleanup
src/main.js, CHANGELOG.md
main.js imports drum module exports and wires them via setDrumHooks/window.editorToggleDrumDensity, removing the previously inline drum editor implementation; CHANGELOG documents the refactor.

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

Sequence Diagram(s)

sequenceDiagram
  participant MainJS as main.js
  participant DrumJS as drum.js
  participant State as S (shared state)
  MainJS->>DrumJS: import commands, draw, hit-test, density helpers
  MainJS->>DrumJS: setDrumHooks({draw, drawWaveform, updateArrangementSelector})
  DrumJS->>State: read/update S.drumTab.hits, S.drumSel, S.drumTabDirty
  DrumJS->>DrumJS: _drumEditorDraw renders lanes, hits, lint overlay
  DrumJS->>MainJS: invoke updateArrangementSelector hook after edits
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% 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 concisely summarizes the main refactor: moving the drum editor into src/drum.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-step15-drum

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ast-grep (0.44.1)
src/main.js

ast-grep timed out on this file


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

This PR continues the src/main.js modularization by extracting the full “Drum editor” cluster into a dedicated module (src/drum.js) while keeping main.js as the wiring/orchestration layer (mouse/drag handlers, toolbar, and tempo/MIDI import flow). To avoid introducing import cycles, the extracted module receives a small set of callbacks via setDrumHooks() (mirroring the existing setHistoryHooks() / setCanvas() pattern).

Changes:

  • Moved the drum editor implementation (lane/density model, limb-lint memoization, geometry + hit-testing, draw routine, and drum undo commands) from src/main.js into new src/drum.js.
  • Added hook wiring in src/main.js (setDrumHooks({ draw, drawWaveform, updateArrangementSelector })) and re-attached window.editorToggleDrumDensity in main.js to keep src/drum.js importable under Node test runs.
  • Updated drum-related tests to import the real module instead of regex-slicing main.js, and converted the remaining CJS drum tests to ESM.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated no comments.

Show a summary per file
File Description
src/drum.js New module containing the extracted drum editor implementation and hook-based cycle breaks.
src/main.js Imports drum exports, wires hooks via setDrumHooks(), and re-attaches the window handler.
tests/drum_velocity.test.mjs Switches from source-slicing to direct imports; seeds real S and installs drum hooks.
tests/drum_undo.test.mjs Same shift to real imports + setDrumHooks stubbing for DOM callback.
tests/drum_limb_lint.test.mjs Drives real S.drag and editGen invalidation; source-lock reduced to draw-path assertion.
tests/drum_density.test.mjs Imports density helpers/constants directly from src/drum.js (no slicing).
CHANGELOG.md Documents the drum-editor extraction and the updated testing approach.

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

@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 (2)
tests/drum_undo.test.mjs (1)

39-52: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider extracting shared makeEnv pattern.

This makeEnv() (seedState + setDrumHooks + trackHooks + command re-export) is nearly identical to the one in tests/drum_velocity.test.mjs, and likely drum_density/drum_limb_lint per the PR stack. Could live in a shared test helper to reduce duplication.

🤖 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/drum_undo.test.mjs` around lines 39 - 52, The makeEnv() setup is
duplicated across multiple drum tests, so extract the shared
seedState/setDrumHooks/trackHooks command wiring into a common test helper and
reuse it here. Move the repeated environment construction for AddDrumHitCmd,
DeleteDrumHitsCmd, MoveDrumHitsCmd, ToggleDrumArticulationCmd,
_drumSortAndRemapSel, and EditHistory into a helper, then update this test to
import and call that helper instead of defining its own makeEnv().
src/drum.js (1)

154-161: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Minor duplication: conflict-index flattening logic exists twice.

_drumConflictIndexSetPure (Lines 154-160) and the inline loop in _drumEditorDraw (Lines 352-354) both flatten conflicts[].indices into a Set. The draw path can't call _drumConflictIndexSetPure directly since it needs the memoized _lintConflicts result rather than re-running the pure clusterer, but the flatten step itself could be factored into a small shared helper (e.g. _flattenConflictIndices(conflicts)) used by both.

♻️ Proposed refactor
+export function _flattenConflictIndices(conflicts) {
+    const set = new Set();
+    for (const c of conflicts) for (const idx of c.indices) set.add(idx);
+    return set;
+}
+
 export function _drumConflictIndexSetPure(hits, epsilon) {
-    const set = new Set();
-    for (const c of _drumLimbConflictsPure(hits, epsilon)) {
-        for (const idx of c.indices) set.add(idx);
-    }
-    return set;
+    return _flattenConflictIndices(_drumLimbConflictsPure(hits, epsilon));
 }
     const _lintConflicts = _drumLimbConflicts(hits);
-    const _conflictIdx = new Set();
-    for (const c of _lintConflicts) for (const idx of c.indices) _conflictIdx.add(idx);
+    const _conflictIdx = _flattenConflictIndices(_lintConflicts);

Also applies to: 352-354

🤖 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/drum.js` around lines 154 - 161, There is duplicated logic for flattening
conflict indices into a Set in _drumConflictIndexSetPure and the inline loop
inside _drumEditorDraw. Extract that shared flattening step into a small helper
such as _flattenConflictIndices(conflicts), then use it both from
_drumConflictIndexSetPure and the draw path so the memoized _lintConflicts
result can be reused without rerunning the pure clusterer.
🤖 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 `@src/drum.js`:
- Around line 154-161: There is duplicated logic for flattening conflict indices
into a Set in _drumConflictIndexSetPure and the inline loop inside
_drumEditorDraw. Extract that shared flattening step into a small helper such as
_flattenConflictIndices(conflicts), then use it both from
_drumConflictIndexSetPure and the draw path so the memoized _lintConflicts
result can be reused without rerunning the pure clusterer.

In `@tests/drum_undo.test.mjs`:
- Around line 39-52: The makeEnv() setup is duplicated across multiple drum
tests, so extract the shared seedState/setDrumHooks/trackHooks command wiring
into a common test helper and reuse it here. Move the repeated environment
construction for AddDrumHitCmd, DeleteDrumHitsCmd, MoveDrumHitsCmd,
ToggleDrumArticulationCmd, _drumSortAndRemapSel, and EditHistory into a helper,
then update this test to import and call that helper instead of defining its own
makeEnv().

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 3c32b658-0da9-4f87-936c-e6ce0ce5259a

📥 Commits

Reviewing files that changed from the base of the PR and between 2e54d43 and 935a81d.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • src/drum.js
  • src/main.js
  • tests/drum_density.test.mjs
  • tests/drum_limb_lint.test.mjs
  • tests/drum_undo.test.mjs
  • tests/drum_velocity.test.mjs

@byrongamatos
byrongamatos merged commit 098ca5f into main Jul 9, 2026
5 checks passed
byrongamatos added a commit that referenced this pull request Jul 9, 2026
…s (R2, step 16) (#167)

* refactor(editor): move the annotation lanes to src/annotation-lanes.js (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>

* docs(editor): correct stale IIFE / brace-matching comments in the moved 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>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
byrongamatos added a commit that referenced this pull request Jul 9, 2026
… step 17) (#168)

* refactor(editor): collapse the per-module hooks into src/host.js (R2, step 17)

history.js, drum.js and annotation-lanes.js each grew a setXHooks() for the
same reason: they need a few main.js symbols that cannot be imported back
without closing a cycle. By the fourth module the SAME four callbacks — draw,
hideContextMenu, snapTime, editorPromptText — were being threaded through three
separate hook objects, and the next extraction (the note command classes) needs
nine. That is the moment to stop duplicating.

One `host` object, wired once. A new module imports `host` and calls
`host.draw()`; no new plumbing, no fourth setter.

No behaviour change: every former _hooks.X() call site resolves to the same
callback (verified by Codex, one-for-one, including the two renamed keys
_editorPromptText -> editorPromptText and ensureArr).

The draw thunk stays, and host.js's header now carries the warning where the
next person will actually read it: `draw` is reassigned near the bottom of
main.js to a button-refreshing wrapper, so passing the bare identifier captures
the original function and the refreshes silently stop. That shipped in
#165/#166. The header says: pass a thunk, and check `grep -n '^\s*<name> = '`
before wiring anything.

The inert defaults are type-honest rather than uniformly no-op — snapTime is
the identity, editorPromptText resolves to null (a cancelled prompt) — so a
module imported under node with no host wired degrades instead of crashing.
That is exactly how the unit tests exercise them, which is also why the unit
tests cannot see the wiring: comment out setHostHooks() and all 88 still pass,
while verify_history.py, verify_drum.py and verify_lanes.py all 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>

* docs(editor): make the host.js grep portable and drop stale setXHooks references

Copilot, on #168.

- host.js suggested `grep -n '^\s*<name> = '`. GNU grep accepts `\s` as an
  extension so it works here, but BSD/macOS grep treats it as a literal 's' and
  returns a silent zero match — the worst possible answer for a check whose
  whole job is to catch a reassignment. Switched to a POSIX class and said why.
- drum_undo.test.mjs comment still named setDrumHooks.
- Three CHANGELOG entries in the same Unreleased block still described the
  per-module setters that this PR removes, so the release notes contradicted
  the code they ship with.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
byrongamatos added a commit that referenced this pull request Jul 9, 2026
… 19) (#170)

* refactor(editor): move the Tempo Map editor to src/tempo.js (R2, step 19)

src/main.js 15,042 -> 13,347. Twenty-second module; the graph stays acyclic.
1,720 lines: the measure model, _tempoMapDraw, the mouse handlers, the sync
inspector, tap-tempo, beat-lock respacing, and the two undo commands
(TempoGridCmd, TempoMapCmd).

main.js is now 37% smaller than the 21,176 lines this refactor started from.

The banner said 2,179 lines, but ~440 of those are the drum editor's mouse
handlers and toolbar buttons, which have no banner of their own and were left
behind by the drum.js lift. The real tempo region ends at TempoMapCmd. Cutting
there is what kept the dependency surface honest.

main.js keeps _finalizeActiveDrag. It dispatches whatever canvas drag is in
flight — tempo, drum, handshape, pan — before a mode switch, so it belongs to
none of them; it reaches back as host.finalizeActiveDrag().

Fifteen main.js symbols travel the other way: the transport (startPlayback /
stopPlayback), the A/B loop strip (four callbacks), the toolbar readouts, and
getMousePos. `_recState` is a REASSIGNED module scalar rather than a function,
so it cannot cross as a value at all — it is wired as the predicate
`host.isRecording: () => _recState === 'recording'`, a closure that reads the
live binding. Same class of trap as `draw` in #165/#166, caught this time by
looking for it.

Tests: 15 suites stopped slicing @pure: blocks and command classes out of
main.js. Ten were CJS and are now .mjs. A few now rely on host's inert defaults
where they used to inject no-op stubs — equivalent, and Codex confirmed it.

Verified beyond the unit tests, which cannot see host wiring: verify_tempo.py
arms drum-edit mode, enters Tempo Map, and asserts the canvas repaints, that
_tempoMapDraw paints its HUD line, and that the drum button relabels itself from
"🎸 Back to Notes" back to "🥁 Edit Drums" — the only visible proof that
host.refreshDrumEditButton fired when tempo mode kicked drum mode out. Comment
out `draw` and `refreshDrumEditButton` and all 89 unit tests still pass while
four of the harness's nine checks fail.

node --test 89/89, pytest 248/248, npm run lint 0 errors (7 warnings, was 8),
Codex clean, all 14 headless harnesses pass.

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

* docs(editor): drop the dead sync-inspector memo and correct stale headers

Copilot, on #170.

- _tempoSyncInspectorState was computed, assigned, and never compared: dead
  code, and a lint warning. Removed rather than 'completed' into an early
  return, because that early return would be WRONG. The DOM this function
  writes is not a pure function of the signature: the BPM field is deliberately
  left alone while it has focus, so skipping the writes on an unchanged
  signature would strand whatever the user typed and abandoned, with nothing
  else to restore it. The comment now says so.
- tempo.js's header hard-coded 'Fourteen main.js symbols'. The number was
  already wrong (fifteen) and would rot again. Removed.
- Five test headers still said the helpers they import live in src/main.js.
  Copilot named two; the other three had the same defect. The five suites that
  genuinely still slice main.js keep their references, which are accurate.

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

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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