Skip to content

Refactor: generalize CanvasHistory into BoundedHistory<T> for image undo#7

Merged
GamalAnwar merged 1 commit into
masterfrom
claude/solid-refactor-bounded-history
Jun 17, 2026
Merged

Refactor: generalize CanvasHistory into BoundedHistory<T> for image undo#7
GamalAnwar merged 1 commit into
masterfrom
claude/solid-refactor-bounded-history

Conversation

@GamalAnwar

Copy link
Copy Markdown
Contributor

Summary

Follow-up to #6. The annotation undo/redo now lives in CanvasHistory, but the image-level undo/redo was still two loose @State [NSImage] arrays (imageUndoStack/imageRedoStack) with inline push / cap-at-20 / pop logic in EditorView — duplicating the exact rules #6 just extracted.

This generalizes CanvasHistory into BoundedHistory<Snapshot> and uses it for both:

typealias CanvasHistory = BoundedHistory<CanvasState>          // unchanged usage/tests from #6
@State private var imageHistory = BoundedHistory<NSImage>(limit: 20)

EditorView's image stack handling collapses from inline array bookkeeping to:

private func pushUndoImage() { imageHistory.record(workingImage); pushUndo() }
// undo(): if let image = imageHistory.undo(current: workingImage) { workingImage = image }
// redo(): if let image = imageHistory.redo(current: workingImage) { workingImage = image }
// hasEdits: ... || imageHistory.canUndo

Scope / safety

  • Behavior-preserving. Image and canvas histories are still popped independently in undo()/redo() — image is best-effort (if let), canvas keeps its guarded return — matching the prior ordering exactly.
  • CanvasHistory is now a typealias, so Refactor: extract annotation undo/redo into CanvasHistory #6's call sites and CanvasHistoryTests are untouched.
  • One repository = one history type; no near-duplicate stack code remains.

Tests

BoundedHistoryTests (4 cases) pins the generic behavior with Int (mirroring the NSImage use): record/undo/redo round-trip, record-clears-redo, nil-on-empty, and cap-drops-oldest at limit 20. CanvasHistoryTests (7) continues to cover the CanvasState instantiation.

🤖 Generated with Claude Code


Generated by Claude Code

…ndo too

The image-level undo/redo was still two loose @State [NSImage] arrays with inline
push/cap-at-20/pop logic in EditorView, duplicating the rules just extracted for
the annotation canvas. Generalize CanvasHistory into BoundedHistory<Snapshot>
(CanvasHistory becomes a typealias, so #6's usage and tests are unchanged) and
back the image stack with BoundedHistory<NSImage>(limit: 20).

Behavior-preserving: image and canvas histories are still popped independently in
undo()/redo() (image best-effort, canvas guarded). Adds BoundedHistoryTests (4
cases) pinning the generic behavior with a non-CanvasState type.
@GamalAnwar GamalAnwar merged commit 04e1912 into master Jun 17, 2026
1 check passed
@GamalAnwar GamalAnwar deleted the claude/solid-refactor-bounded-history branch June 18, 2026 07:35
GamalAnwar added a commit that referenced this pull request Jun 18, 2026
…ndo (#7)

Generalize CanvasHistory into BoundedHistory<Snapshot> and back the image-undo
stack with it (BoundedHistory<NSImage>, limit 20) instead of inline @State arrays.
CanvasHistory becomes a typealias, so #6's usage/tests are unchanged. Behavior-
preserving: image and canvas histories pop independently. Adds BoundedHistoryTests
(4 cases). CI green.
GamalAnwar added a commit that referenced this pull request Jun 18, 2026
…ndo (#7)

Generalize CanvasHistory into BoundedHistory<Snapshot> and back the image-undo
stack with it (BoundedHistory<NSImage>, limit 20) instead of inline @State arrays.
CanvasHistory becomes a typealias, so #6's usage/tests are unchanged. Behavior-
preserving: image and canvas histories pop independently. Adds BoundedHistoryTests
(4 cases). CI green.
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