feat(editor): undo to last checkpoint (Ctrl+Alt+Z) - #221
Conversation
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
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (2)
📝 WalkthroughWalkthroughUndo 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. ChangesCheckpoint-based undo
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
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (7)
CHANGELOG.mdsrc/history.jssrc/input.jssrc/main.jssrc/menu-bar.jssrc/tempo.jstests/undo_checkpoints.test.mjs
…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>
There was a problem hiding this comment.
🧹 Nitpick comments (1)
tests/undo_checkpoints.test.mjs (1)
68-86: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueOptional: assert final stack state after the third press.
After
r3(the degrade-to-plain-undo case), the test doesn't verifyh.undo.lengthafterward (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
📒 Files selected for processing (4)
src/history.jssrc/main.jssrc/tempo.jstests/undo_checkpoints.test.mjs
🚧 Files skipped from review as they are similar to previous changes (2)
- src/main.js
- src/history.js
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:MAX_UNDOor dropped byreset()).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.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.Tests & verification
tests/undo_checkpoints.test.mjs(realEditHistoryover the realSvia_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.npm test115 green,npm run lint0 errors (3 pre-existing warnings = main's ratchet).PR 4 of the tempo-mapping track. Independent (depends only on #210, merged) — unblocked now. ⚠
history.jsand 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