Skip to content

feat(editor): undo to last checkpoint (Ctrl+Alt+Z) - #221

Merged
byrongamatos merged 3 commits into
mainfrom
feat/editor-undo-checkpoints
Jul 12, 2026
Merged

byrongamatos merged 3 commits into
mainfrom
feat/editor-undo-checkpoints

Conversation

@ChrisBeWithYou

@ChrisBeWithYou ChrisBeWithYou commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Charrette arch 7. Coarse rewind points so a whole tempo-mapping session can be undone in one keystroke instead of tapping Ctrl+Z through every barline move.

What

  • EditHistory.checkpoint(label) — stamps the top-of-undo command (_checkpoint); no-op on an empty stack. The stamp rides the command object, so it survives redo.
  • EditHistory.undoToCheckpoint() — undoes through (and including) the nearest stamped command; returns { undone, label, foundCheckpoint }. Two graceful degradations:
    • No checkpoint in the stack ⇒ a single plain undo (never a silent whole-session rewind — a checkpoint can be shifted off by MAX_UNDO or dropped by reset()).
    • No-progress guard: doUndo() can refuse without popping (ensureArr switch-away, read-only-roll lock), so it stops the instant the stack stops shrinking — a refusal can't spin.
  • Surface: Ctrl+Alt+Z (added ahead of the plain Ctrl+Z handler, which doesn't exclude Alt, so it would otherwise swallow it) + an Edit-menu row. The status line names what it unwound to.
  • Checkpoints stamped at three milestones: Tempo Map entry, feat(editor): assisted tempo mapping — suggest a barline fit from onsets #215's suggest-fit accept, and the barline lock toggle (lock toggles aren't history events, so the stamp records the moment on the current top-of-undo).

Tests & verification

  • tests/undo_checkpoints.test.mjs (real EditHistory over the real S via _history_env): rewind-through-the-stamp, single-undo fallback, stamp survives redo, no-progress guard, empty-stack, checkpoint-on-empty-stack — all fail on main.
  • Verified live on the testbed: Ctrl+Alt+Z routes to the checkpoint undo and plain Ctrl+Z still routes to normal undo (no collision); a real accent → vibrato → enter-Tempo-Map → hammer flow, then Ctrl+Alt+Z, reports "Undid 2 steps back to checkpoint: Tempo Map session." No page errors.
  • npm test 115 green, npm run lint 0 errors (3 pre-existing warnings = main's ratchet).

PR 4 of the tempo-mapping track. Independent (depends only on #210, merged) — unblocked now. ⚠ history.js and the barline lock-toggle line are shared seams with #220 (lock copy) and #210; expect the usual keep-both on the shared lines.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added “Undo to last checkpoint” to the Edit menu and bound it to Ctrl/⌘+Alt+Z.
    • Implemented checkpoint-based undo for key Tempo Map actions (entering Tempo Map, accepting suggested fits, and locking/unlocking barlines).
  • Bug Fixes
    • Improved safety and behavior when undo can’t proceed, including graceful handling when no checkpoint is available (with clear status messaging).
  • Tests
    • Added automated coverage for checkpoint stamping and undo-to-checkpoint behavior, including edge cases and no-progress protection.

Charrette arch 7. Coarse rewind points so a whole tempo-mapping session can be
undone in one keystroke instead of tapping Ctrl+Z through every barline move.

- EditHistory.checkpoint(label) stamps the top-of-undo command (no-op on an
  empty stack); the stamp rides the command object, so it survives redo.
- EditHistory.undoToCheckpoint() undoes through (and including) the nearest
  stamped command and returns { undone, label, foundCheckpoint }. Two graceful
  degradations: no checkpoint in the stack falls back to a single plain undo
  (never a silent whole-session rewind — a checkpoint can be shifted off by
  MAX_UNDO or dropped by reset()); a no-progress guard stops the instant the
  stack stops shrinking, so a refused doUndo (ensureArr / roll-lock) can't spin.
- Surface: Ctrl+Alt+Z (added ahead of the plain Ctrl+Z handler, which doesn't
  exclude Alt) + an Edit-menu row; the status line names what it unwound to.
- Checkpoints stamped at three milestones: Tempo Map entry, #215's suggest-fit
  accept, and the barline lock toggle (lock toggles aren't history events, so
  the stamp records the moment on the current top-of-undo).

tests/undo_checkpoints.test.mjs (real EditHistory over the real S): rewind-
through, single-undo fallback, stamp-survives-redo, no-progress guard, empty
stack — all fail on main. Verified live: Ctrl+Alt+Z routes to the checkpoint
undo (no collision with Ctrl+Z) and a real accent→vibrato→enter-TempoMap→hammer
flow reports "Undid 2 steps back to checkpoint: Tempo Map session." npm test
115 green, lint 0 errors.

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

coderabbitai Bot commented Jul 12, 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: d76be43e-6de6-4ba6-99c5-5662577a597d

📥 Commits

Reviewing files that changed from the base of the PR and between 646fb82 and dd02ec4.

📒 Files selected for processing (2)
  • CHANGELOG.md
  • src/tempo.js

📝 Walkthrough

Walkthrough

Undo history now supports labeled checkpoints and rewind-to-checkpoint behavior. The command is available through the Edit menu and Ctrl/⌘+Alt+Z, while Tempo Map actions create labeled checkpoints. Tests cover fallback, persistence, refusal handling, and empty histories.

Changes

Checkpoint-based undo

Layer / File(s) Summary
History checkpoint and rewind behavior
src/history.js, tests/undo_checkpoints.test.mjs
EditHistory stamps checkpoint labels and rewinds through the latest checkpoint with structured results, fallback undo, and no-progress protection; tests cover these flows.
Editor command and shortcut wiring
src/main.js, src/menu-bar.js, src/input.js
The new checkpoint undo action is exposed through the global editor handler, Edit menu, and Ctrl/⌘+Alt+Z shortcut.
Tempo Map checkpoint stamping
src/tempo.js, CHANGELOG.md
Tempo Map session entry, suggested-fit acceptance, and beat-lock toggles add labeled checkpoints, with the behavior documented in the changelog.

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

Sequence Diagram(s)

sequenceDiagram
  participant EditorUser
  participant onKeyDown
  participant editorUndoToCheckpoint
  participant EditHistory
  EditorUser->>onKeyDown: press Ctrl/⌘+Alt+Z
  onKeyDown->>editorUndoToCheckpoint: invoke command
  editorUndoToCheckpoint->>EditHistory: undoToCheckpoint()
  EditHistory-->>editorUndoToCheckpoint: return undo result
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 summarizes the main change: adding undo-to-checkpoint support with the shortcut.
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-undo-checkpoints

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

🤖 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/history.js`:
- Around line 121-139: The undoToCheckpoint() method currently returns
foundCheckpoint: true whenever any checkpoint exists, even if doUndo() refuses
before reaching it. Set foundCheckpoint only after a checkpoint is successfully
undone and label is assigned; return false when the loop stops without reaching
a checkpoint, while preserving the undone count and existing no-checkpoint
behavior.
🪄 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: fd94a743-9913-4354-b5f4-b679b97deaaf

📥 Commits

Reviewing files that changed from the base of the PR and between 2a764a1 and 3417b77.

📒 Files selected for processing (7)
  • CHANGELOG.md
  • src/history.js
  • src/input.js
  • src/main.js
  • src/menu-bar.js
  • src/tempo.js
  • tests/undo_checkpoints.test.mjs

Comment thread src/history.js
…ne edit past it

undoToCheckpoint() rolled back the stamped command too, so a Tempo-Map-entry
or barline-lock checkpoint (stamped on the last edit BEFORE the milestone)
silently undid one unrelated pre-session edit while the status line claimed a
clean return to the checkpoint.

- checkpoint() now means: the state as of this call. undoToCheckpoint() undoes
  everything above the stamp and stops with the stamped command still applied.
- Pressing while already at a checkpoint walks to the PREVIOUS one, so
  repeated Ctrl+Alt+Z steps back boundary by boundary instead of going inert.
- Suggest-fit stamps BEFORE exec'ing the accept, preserving its intended
  rewind-the-accept-too behaviour under the corrected semantics.
- Status line: a refused first undo no longer gets stomped by 'Nothing to
  undo.', and a partial (refused mid-way) rewind reports the real step count.

Regression tests fail on the previous behaviour (verified by revert).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

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

68-86: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Optional: assert final stack state after the third press.

After r3 (the degrade-to-plain-undo case), the test doesn't verify h.undo.length afterward (expected 0, since 'a' is undone). Would tighten the regression coverage for this edge case slightly.

Optional addition
     const r3 = h.undoToCheckpoint();
     assert.strictEqual(r3.foundCheckpoint, false, 'at A with nothing earlier: degrade to one undo');
     assert.strictEqual(r3.undone, 1, 'single plain undo, not inert');
+    assert.strictEqual(h.undo.length, 0, 'the last remaining command was undone');
🤖 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/undo_checkpoints.test.mjs` around lines 68 - 86, Extend the
repeated-press test after the r3 assertions to verify the final undo stack
state, asserting that h.undo.length is 0 after the degraded plain undo consumes
the remaining command.
🤖 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/undo_checkpoints.test.mjs`:
- Around line 68-86: Extend the repeated-press test after the r3 assertions to
verify the final undo stack state, asserting that h.undo.length is 0 after the
degraded plain undo consumes the remaining command.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: caa2ef83-9d92-43d6-b3a3-01e244fc4208

📥 Commits

Reviewing files that changed from the base of the PR and between 3417b77 and 646fb82.

📒 Files selected for processing (4)
  • src/history.js
  • src/main.js
  • src/tempo.js
  • tests/undo_checkpoints.test.mjs
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/main.js
  • src/history.js

@byrongamatos
byrongamatos merged commit b802902 into main Jul 12, 2026
3 checks passed
@byrongamatos
byrongamatos deleted the feat/editor-undo-checkpoints branch July 12, 2026 19:23
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