Skip to content

feat(editor): heal uneven beat spacing — repair degenerate interior beats - #261

Merged
byrongamatos merged 3 commits into
mainfrom
feat/editor-grid-heal
Jul 14, 2026
Merged

byrongamatos merged 3 commits into
mainfrom
feat/editor-grid-heal

Conversation

@ChrisBeWithYou

@ChrisBeWithYou ChrisBeWithYou commented Jul 14, 2026

Copy link
Copy Markdown
Contributor

What

Motivated by a tester's real project (the same one behind #258): its grid carried measures whose interior sub-beats piled 5 ms apart right before the next barline, leaving a 2 s hole after it — the residue of hand re-syncs chasing tempo drift. That shape garbles the metronome, snapping, and every per-beat view even though the barlines themselves are where the charter put them.

New Tempo/Grid ▸ Heal uneven beat spacing:

  • _gridHealScanPure flags a measure when any interior gap is under 30% or over 300% of the measure's even spacing.
  • _gridHealPure re-spaces ONLY sick measures' interior beats evenly between their downbeats. Downbeats never move — barlines are the charter's authored truth; the beats between them are bookkeeping.
  • editorHealGrid commits it as one TempoGridCmd — notes keep their exact seconds (they were synced to the recording; the grid was the sick part) and re-lift their beat positions from the healed grid. Healthy grid → "nothing to heal", no command.

Tests

tests/grid_heal.test.mjs (5, fail on main): the field pile-up shape (real numbers from the reporting project) flags and heals to exact even quarters with downbeats pinned and neighbours untouched; a healthy grid scans clean and heals to null; an oversized hole flags without a tiny gap beside it; non-time fields and input immutability. Full suite green, lint 0 errors, routes.py untouched.

Live-verified on the reporting project

Insomnium — Where the Last Wave Broke: scan finds measures 48, 50, 76 (matching offline analysis of the shipped pak exactly); heal takes the grid's minimum beat gap from 5 ms → 231 ms and maximum from 2.078 s → 0.528 s, moves 0 of 1114 notes, lands as one undo step, and Ctrl+Z restores bit-exact. Zero page errors.

🤖 Generated with Claude Code

https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q

Summary by CodeRabbit

  • New Features
    • Added Tempo/Grid → Heal uneven beat spacing to detect and fix pathological interior beat spacing, re-spacing only interior beats while keeping downbeats fixed; applies as a single undoable step (updates up to 8 affected measures).
    • Added Scan for tempo zones (preview) to propose tempo “intents” from the recording without applying changes.
  • Documentation
    • Updated the changelog with both new Tempo/Grid actions.
  • Tests
    • Added automated tests validating scan/heal behavior, rounding and monotonicity, metadata preservation, non-mutation, and key edge cases.

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: b704a58f-644e-4cea-a717-bbc30a59ca83

📥 Commits

Reviewing files that changed from the base of the PR and between b868e00 and 49e9be6.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • src/main.js
  • src/menu-bar.js
  • src/tempo.js
  • tests/grid_heal.test.mjs

📝 Walkthrough

Walkthrough

Adds a Tempo/Grid action that detects uneven interior beat spacing, heals affected measures without moving barlines or note timing, records the change as one undoable step, and exposes the action through the editor menu and window API.

Changes

Grid healing

Layer / File(s) Summary
Grid scan, re-spacing, and editor command
src/tempo.js
Adds pure scanning and healing helpers for pathological interior gaps, then applies healed grids through TempoGridCmd as one undoable editor action.
Grid healing validation
tests/grid_heal.test.mjs
Tests pathological and oversized gaps, healthy and unhealable grids, preserved downbeats and properties, immutable inputs, strict ordering, and even interior spacing.
Menu, public API, and release notes
src/main.js, src/menu-bar.js, CHANGELOG.md
Exposes the command on window, adds the Tempo/Grid menu item, and documents the Tempo/Grid entries.

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

Sequence Diagram(s)

sequenceDiagram
  participant Menu as Tempo/Grid menu
  participant Editor as editorHealGrid
  participant Grid as _gridHealScanPure and _gridHealPure
  participant History as TempoGridCmd
  Menu->>Editor: invoke editorHealGrid
  Editor->>Grid: scan and heal current beat grid
  Grid-->>Editor: healed grid or null
  Editor->>History: commit one undoable grid update
  History-->>Editor: redraw and update status
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the main editor feature added and matches the changeset.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 feat/editor-grid-heal

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.

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/tempo.js (1)

1000-1006: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Redundant re-scan just to get a count.

_gridHealScanPure already ran once inside _gridHealPure; calling it again on Line 1005 just for .length duplicates the O(beats) scan. Harmless at typical grid sizes, but could be avoided by having the heal helper return the sick-measure list alongside the healed grid.

🤖 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/tempo.js` around lines 1000 - 1006, Update _gridHealPure and its caller
to return or expose the sick-measure list alongside the healed grid, then use
that result to obtain the count instead of calling _gridHealScanPure again.
Preserve the existing no-healing behavior and TempoGridCmd execution in the
surrounding heal flow.
🤖 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.

Inline comments:
In `@src/tempo.js`:
- Around line 992-999: Reorder the guard clauses in the healing flow so the
!S.sessionId || !S.history check runs before the !S.beats || S.beats.length < 2
check. Preserve both existing status messages and return behavior, ensuring no
open song reports the song-open message first.

---

Nitpick comments:
In `@src/tempo.js`:
- Around line 1000-1006: Update _gridHealPure and its caller to return or expose
the sick-measure list alongside the healed grid, then use that result to obtain
the count instead of calling _gridHealScanPure again. Preserve the existing
no-healing behavior and TempoGridCmd execution in the surrounding heal flow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 06bbc48c-c976-4c88-a37e-efdd0985b4f3

📥 Commits

Reviewing files that changed from the base of the PR and between a866705 and 597f13f.

📒 Files selected for processing (5)
  • CHANGELOG.md
  • src/main.js
  • src/menu-bar.js
  • src/tempo.js
  • tests/grid_heal.test.mjs

Comment thread src/tempo.js Outdated
@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.

@byrongamatos

Copy link
Copy Markdown
Collaborator

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown
✅ Action performed

Full review finished.


Your plan includes PR reviews subject to rate limits. More reviews will be available in 9 minutes.

ChrisBeWithYou and others added 3 commits July 14, 2026 21:04
Field projects carry measures whose sub-beats pile milliseconds apart
beside a seconds-wide hole (hand re-syncs / old imports). New pures scan
(gap <30% or >300% of even spacing) and re-space sick measures' interiors
evenly; downbeats never move; one TempoGridCmd so notes keep seconds.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EBQCHCNA81E9tHmSDHSe2Q
Two holes in the heal, both on the data-loss side:

- A measure whose even spacing lands under the grid's millisecond
  resolution was re-spaced with _r3, collapsing two beats onto the SAME
  time — a duplicate-time grid is worse than the sickness it replaced
  (beatOf/timeOf stop being inverses across a zero-width gap). Reachable
  in exactly the pathological grids this action targets. The scan now
  skips those measures: corruption past what an even re-space can fix.

- The scan cannot tell a corrupt pile-up from a deliberate grand pause
  held ~9x its neighbours — both are a wildly uneven interior gap, and
  the heal flattens either. Undo restores, but only if the charter can
  SEE which bars moved, so the status line now names them instead of
  only counting them.

Also: check the session before the grid (the row is ungated, so "no song
open" must say so, not blame a grid that cannot exist yet) and scan once
instead of twice.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
An agent worktree symlinked node_modules; .gitignore lists only
node_modules/ (trailing slash), which matches a directory but not a
symlink, so git add -A tracked it. The symlink points at a local
absolute path and would break any other checkout.
@byrongamatos
byrongamatos force-pushed the feat/editor-grid-heal branch from b868e00 to 49e9be6 Compare July 14, 2026 19:04
@byrongamatos
byrongamatos merged commit b05f071 into main Jul 14, 2026
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