Skip to content

feat(sheet): kollaboratives Undo/Redo - #377

Merged
SamTV12345 merged 1 commit into
mainfrom
feat/sheet-undo-redo
Jul 28, 2026
Merged

feat(sheet): kollaboratives Undo/Redo#377
SamTV12345 merged 1 commit into
mainfrom
feat/sheet-undo-redo

Conversation

@SamTV12345

Copy link
Copy Markdown
Member

Ansatz

Undo rollt keinen Zustand zurück, sondern spielt inverse Ops durch die normale Kollaborations-Pipeline. Das ist der Punkt, an dem es unter Nebenläufigkeit korrekt bleibt:

  1. Die Inverse wird gegen den Zustand vor der Op berechnet (in applyLocal, bevor sie angewandt wird).
  2. Sie landet in einem clientlokalen Stack — nur eigene Ops, nie fremde.
  3. Jede eintreffende Remote-Op transformiert den Stack genauso wie die pending Ops. Fügt jemand zwei Zeilen über meiner Zelle ein, löscht mein Undo danach Zeile 5 statt Zeile 3.
  4. Beim Undo gehen die Inversen als ganz normale lokale Ops raus — Transformation, Ack und Broadcast wie bei jeder Bearbeitung.

Dadurch macht Undo nie die Arbeit eines anderen rückgängig, auch wenn sie zeitlich dazwischen liegt.

invertOp

Deckt das gesamte Op-Vokabular ab:

  • Zellen und Stile — mit props, nicht mit styleId: die Style-Id ist ein clientlokaler Pool-Index, über die Leitung gehen nur Props.
  • clearRange — jede gelöschte Zelle inklusive Formatierung zurück.
  • Zeilen/Spalten einfügen und löschen — beim Löschen werden Zellen, Größen und die vom Shift verworfenen bzw. gestauchten Merges wiederhergestellt.
  • Merges — inklusive der Merges, die ein neuer Merge geschluckt hat.
  • Dimensionen, Freeze, Sheet-Liste — ein gelöschtes Sheet kommt mit Inhalt, Größen, Merges und Freeze zurück.

Gruppierung

Ops desselben Ticks bilden einen History-Eintrag. Paste, Ausfüllen oder das Formatieren eines Bereichs schreiben je eine Op pro Zelle und werden trotzdem in einem Schritt rückgängig gemacht — ohne Änderung an einer einzigen Aufrufstelle. History bei 100 Einträgen gedeckelt; eine neue Bearbeitung verwirft den Redo-Zweig, ein Redo behält ihn.

UI

Undo/Redo im Home-Tab, ausgegraut wenn der jeweilige Stack leer ist, plus Ctrl+Z, Ctrl+Y und Ctrl+Shift+Z — nur außerhalb der Zellbearbeitung, drinnen gehört der Tastendruck dem Text-Undo des Browsers.

Tests

17 neue Unit-Tests: pro Op-Typ ein Roundtrip gegen einen strukturellen State-Fingerprint (Zellen + aufgelöste Style-Props, Größen, Freeze, Merges), dazu Client-Tests für Gruppierung pro Tick, getrennte Ticks, Redo-Verwerfung, Rebase gegen eine fremde Zeileneinfügung und „Remote-Ops landen nicht in der History". Plus zwei Playwright-Tests (Button- und Tastaturpfad, Mehrzellen-Aktion in einem Schritt). Insgesamt 166 Vitest-Tests grün.

Bekannte Grenze

Das Rückgängigmachen der allerersten Größenänderung einer Zeile/Spalte stellt den Grid-Default (80/22 px) her statt den Eintrag zu entfernen — das Op-Vokabular kennt kein „Dimension zurücksetzen". Optisch identisch, im State nicht bytegleich; im Test explizit festgehalten.

🤖 Generated with Claude Code

@qodo-free-for-open-source-projects

qodo-free-for-open-source-projects Bot commented Jul 28, 2026

Copy link
Copy Markdown

Code Review by Qodo

Grey Divider

New Review Started

This review has been superseded by a new analysis

Grey Divider

Qodo Logo

@SamTV12345
SamTV12345 enabled auto-merge (squash) July 28, 2026 19:30
@qodo-code-review

qodo-code-review Bot commented Jul 28, 2026

Copy link
Copy Markdown

PR Summary by Qodo

Add Collaborative Undo and Redo for Sheets

✨ Enhancement 🧪 Tests 🕐 40+ Minutes

Grey Divider

AI Description

• Adds collaboration-safe undo/redo by replaying inverse operations through the normal pipeline.
• Covers all sheet operations, grouping same-tick edits and rebasing history against remote changes.
• Adds toolbar controls, keyboard shortcuts, and comprehensive unit and browser coverage.
Diagram

sequenceDiagram
  actor User
  participant Editor as Sheet Editor
  participant Client as Collab Client
  participant Inverter as Op Inverter
  participant Workbook as Workbook State
  participant Server as Collab Server
  User->>Editor: Edit action
  Editor->>Client: applyLocal op
  Client->>Inverter: Invert pre-state
  Inverter-->>Client: Inverse ops
  Client->>Workbook: Optimistic apply
  Client->>Server: Send operation
  Server-->>Client: Ack or remote op
  Client->>Client: Rebase history
  User->>Editor: Undo or redo
  Editor->>Client: Replay history
  Client->>Workbook: Apply inverse ops
  Client->>Server: Send inverse ops
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Conditional inverse operations
  • ➕ Prevents undo from overwriting a collaborator's newer same-cell or style edit.
  • ➕ Makes the guarantee of reverting only the initiating user's work causally enforceable.
  • ➖ Requires new protocol fields and server-side validation semantics.
  • ➖ Adds conflict outcomes the UI must communicate to users.
2. Server-authoritative per-user history
  • ➕ Centralizes causal ordering and history validation.
  • ➕ Can coordinate undo consistently across reconnects and multiple client sessions.
  • ➖ Requires persistent server-side history and user identity tracking.
  • ➖ Increases protocol, storage, and operational complexity.
  • ➖ Moves latency-sensitive undo away from optimistic client execution.

Recommendation: Replaying inverses through the existing operation pipeline is the best baseline because it reuses transformation, acknowledgement, and broadcast behavior while keeping history client-local. However, structural rebasing alone does not protect a stored inverse from overwriting a newer remote edit to the same cell or style under last-writer-wins semantics; if the stated guarantee is strict, conditional or causally guarded inverse operations should be considered, otherwise that concurrency behavior should be documented and tested.

Files changed (6) +557 / -3

Enhancement (4) +307 / -3
sheetCollabClient.tsManage collaborative undo and redo history +76/-0

Manage collaborative undo and redo history

• Captures inverse operations before local edits, groups same-tick operations, and maintains bounded undo and redo stacks. Replays history through the normal pending-operation pipeline and transforms stored entries against remote structural changes.

ui/src/js/sheet/sheetCollabClient.ts

sheetEditor.tsConnect history actions to the sheet editor +31/-1

Connect history actions to the sheet editor

• Wires undo and redo into toolbar callbacks and refreshes their availability after workbook changes. Adds Ctrl+Z, Ctrl+Y, and Ctrl+Shift+Z handling outside active cell editing while preserving browser text undo.

ui/src/js/sheet/sheetEditor.ts

sheetToolbar.tsAdd undo and redo toolbar controls +29/-2

Add undo and redo toolbar controls

• Extends toolbar callbacks with history actions and availability state. Adds undo and redo icons, disabled styling, and a refresh method for synchronizing button state.

ui/src/js/sheet/sheetToolbar.ts

undo.tsImplement inverses for the complete sheet operation vocabulary +171/-0

Implement inverses for the complete sheet operation vocabulary

• Introduces pre-state operation inversion for cell, style, range, structural, merge, dimension, freeze, and sheet-list changes. Restores deleted content and metadata using transport-safe style properties rather than client-local style identifiers.

ui/src/js/sheet/undo.ts

Tests (2) +250 / -0
sheet_excel_chrome.spec.tsCover toolbar, shortcut, and grouped undo workflows +37/-0

Cover toolbar, shortcut, and grouped undo workflows

• Adds browser tests for undoing and redoing cell edits through toolbar and keyboard controls. Verifies that a multi-cell clear operation is restored as one history step.

playwright/specs/sheet_excel_chrome.spec.ts

undo.test.tsTest operation inversion and collaborative history behavior +213/-0

Test operation inversion and collaborative history behavior

• Adds structural round-trip tests across cells, styles, ranges, dimensions, merges, and sheet operations. Also verifies grouping, redo invalidation, remote rebasing, and exclusion of remote operations from local history.

ui/src/js/sheet/undo.test.ts

@qodo-code-review

qodo-code-review Bot commented Jul 28, 2026

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (5) 📘 Rule violations (0) 📎 Requirement gaps (0) 🎨 UX issues (0) 🔗 Cross-repo conflicts (0) 📜 Skill insights (0)

Grey Divider


Action required

1. Style undo overwrites remote content 🐞 Bug ≡ Correctness
Description
A setStyle operation is inverted as a setCell containing the previous raw value. If a
collaborator edits the content afterward, undoing the local formatting restores stale text and
erases that edit.
Code

ui/src/js/sheet/undo.ts[R114-116]

+    case 'setCell':
+    case 'setStyle':
+      return [cellRestore(wb, op, sheet, row, col)];
Relevance

⭐⭐⭐ High

PR #324 fixed collaborative data loss; PR #352 accepted closely related transform correctness
feedback.

PR-#324
PR-#352

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
cellRestore always emits setCell with both raw and props, even though the original
setStyle path changes only the style. Applying the inverse therefore invokes last-writer-wins raw
replacement, while non-structural remote writes receive no conflict transformation.

ui/src/js/sheet/undo.ts[24-35]
ui/src/js/sheet/undo.ts[113-116]
ui/src/js/sheet/workbookState.ts[247-268]
ui/src/js/sheet/transform.ts[8-10]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Formatting-only operations currently receive a full-cell inverse, allowing style undo to overwrite a collaborator's later content edit.

## Issue Context
Generate a `setStyle` inverse containing only the previous style properties for `setStyle`. Add a two-client test where one client formats a cell, another changes its raw content, and the first client undoes formatting without changing the content.

## Fix Focus Areas
- ui/src/js/sheet/undo.ts[24-35]
- ui/src/js/sheet/undo.ts[113-116]
- ui/src/js/sheet/undo.test.ts[56-60]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Insert undo deletes remote edits 🐞 Bug ≡ Correctness
Description
Undoing an inserted row or column unconditionally deletes the entire inserted band. Any cells
another collaborator added to that band after the insertion are deleted with it.
Code

ui/src/js/sheet/undo.ts[R160-163]

+    case 'insertRows':
+      return [{ ...base(op), type: 'deleteRows', index, count }];
+    case 'insertCols':
+      return [{ ...base(op), type: 'deleteCols', index, count }];
Relevance

⭐⭐⭐ High

PRs #324 and #352 show acceptance of collaborative data-loss and structural-convergence fixes.

PR-#324
PR-#352

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The new inverse converts inserts directly to delete operations. Remote setCell operations are
returned unchanged by transform, while deleteRows and deleteCols remove every cell in their
bands, including later collaborative writes.

ui/src/js/sheet/undo.ts[160-163]
ui/src/js/sheet/transform.ts[8-23]
ui/src/js/sheet/workbookState.ts[312-338]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Undoing `insertRows` or `insertCols` emits an unconditional band deletion, which also deletes cells collaborators subsequently placed in that band.

## Issue Context
Remote non-structural operations do not modify the stored inverse. Implement conflict-aware structural history that either preserves/migrates subsequent remote content or prevents a destructive undo, and add a two-client regression test.

## Fix Focus Areas
- ui/src/js/sheet/undo.ts[160-163]
- ui/src/js/sheet/sheetCollabClient.ts[143-156]
- ui/src/js/sheet/transform.ts[8-23]
- ui/src/js/sheet/undo.test.ts[194-206]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Deleted-row undo clobbers cells 🐞 Bug ≡ Correctness
Description
Rebasing a stored cell inverse through a remote row or column deletion clamps its target to the
deletion index. Undo then writes the old value into the unrelated cell that shifted into that
position.
Code

ui/src/js/sheet/sheetCollabClient.ts[R151-153]

+    const rebase = (stack: Op[][]): Op[][] => stack.map((entry) => entry.map((o) => transform(o, remoteOp)));
+    this.undoStack = rebase(this.undoStack);
+    this.redoStack = rebase(this.redoStack);
Relevance

⭐⭐⭐ High

PR #352 accepted an analogous structural-transform coordinate bug affecting collaborative
correctness.

PR-#352

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
History entries are passed through transform; its deletion logic maps coordinates inside the
deleted band to the band's start. Workbook deletion then shifts later cells into that coordinate,
and the transformed setCell inverse overwrites the shifted cell.

ui/src/js/sheet/sheetCollabClient.ts[143-156]
ui/src/js/sheet/transform.ts[60-69]
ui/src/js/sheet/workbookState.ts[247-261]
ui/src/js/sheet/workbookState.ts[317-338]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
History rebasing uses the normal pending-operation transform, which clamps inverse cell coordinates inside a remotely deleted band and redirects undo onto unrelated shifted cells.

## Issue Context
Introduce history-specific transformation semantics that can remove obsolete point inverses rather than clamping them. Cover both row and column deletion with collaborative regression tests.

## Fix Focus Areas
- ui/src/js/sheet/sheetCollabClient.ts[143-156]
- ui/src/js/sheet/transform.ts[30-69]
- ui/src/js/sheet/undo.test.ts[194-206]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Remediation recommended

4. Formula undo triggers sheet history 🐞 Bug ≡ Correctness
Description
The global undo/redo shortcut only checks whether the grid is editing, not whether the formula-bar
input is focused. Pressing Ctrl/Meta+Z while editing a formula therefore invokes workbook history
instead of the input's native text undo.
Code

ui/src/js/sheet/sheetEditor.ts[R691-692]

+    if (mod && !editingNow() && !readOnly) {
+      const k = e.key.toLowerCase();
Relevance

⭐⭐⭐ High

PRs #364 and #365 explicitly preserve formula-bar native keyboard behavior from document shortcuts.

PR-#364
PR-#365

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
editingNow() only delegates to the grid view, while the formula bar uses a separate text input
whose keydown handler does not consume undo or redo. Its keyboard events consequently bubble to the
newly added document-level history handler.

ui/src/js/sheet/sheetEditor.ts[245-245]
ui/src/js/sheet/sheetEditor.ts[689-702]
ui/src/js/sheet/sheetFormulaBar.ts[71-80]
ui/src/js/sheet/sheetFormulaBar.ts[119-130]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Workbook undo and redo intercept native text-history shortcuts while the user is typing in the formula bar.

## Issue Context
Guard global history shortcuts when the event target is an input, textarea, or editable element, or expose formula-bar editing state. Add browser coverage proving formula text changes locally without changing workbook history.

## Fix Focus Areas
- ui/src/js/sheet/sheetEditor.ts[689-702]
- ui/src/js/sheet/sheetFormulaBar.ts[71-80]
- ui/src/js/sheet/sheetFormulaBar.ts[119-130]
- playwright/specs/sheet_excel_chrome.spec.ts[162-180]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


5. Undo leaves invalid active sheet 🐞 Bug ≡ Correctness
Description
Undoing an added sheet deletes it without updating activeSheetId. The editor remains pointed at a
nonexistent sheet, producing no active tab and an empty grid until another sheet is selected.
Code

ui/src/js/sheet/sheetEditor.ts[R658-659]

+    if (which === 'undo') collab.undo();
+    else collab.redo();
Relevance

⭐⭐ Medium

PR #326 established active-sheet deletion handling, but no historical undo-specific acceptance
evidence exists.

PR-#326

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Adding a sheet explicitly makes its ID active, and normal deletion explicitly switches away from an
active deleted sheet. The new history path bypasses that fallback even though the inverse of
addSheet is deleteSheet; tab refresh only compares IDs and does not repair the invalid active
ID.

ui/src/js/sheet/sheetEditor.ts[508-539]
ui/src/js/sheet/sheetEditor.ts[653-660]
ui/src/js/sheet/undo.ts[69-80]
ui/src/js/sheet/sheetTabs.ts[37-45]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
History replay can delete the currently active sheet without selecting a surviving sheet, leaving editor state inconsistent.

## Issue Context
After undo or redo, verify that `activeSheetId` still exists and select a valid fallback when it does not. Add coverage for adding and activating a sheet, undoing the addition, and verifying that another sheet becomes active.

## Fix Focus Areas
- ui/src/js/sheet/sheetEditor.ts[508-539]
- ui/src/js/sheet/sheetEditor.ts[653-660]
- ui/src/js/sheet/undo.ts[69-80]
- ui/src/js/sheet/sheetTabs.ts[37-45]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread ui/src/js/sheet/undo.ts
Comment on lines +160 to +163
case 'insertRows':
return [{ ...base(op), type: 'deleteRows', index, count }];
case 'insertCols':
return [{ ...base(op), type: 'deleteCols', index, count }];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

1. Insert undo deletes remote edits 🐞 Bug ≡ Correctness

Undoing an inserted row or column unconditionally deletes the entire inserted band. Any cells
another collaborator added to that band after the insertion are deleted with it.
Agent Prompt
## Issue description
Undoing `insertRows` or `insertCols` emits an unconditional band deletion, which also deletes cells collaborators subsequently placed in that band.

## Issue Context
Remote non-structural operations do not modify the stored inverse. Implement conflict-aware structural history that either preserves/migrates subsequent remote content or prevents a destructive undo, and add a two-client regression test.

## Fix Focus Areas
- ui/src/js/sheet/undo.ts[160-163]
- ui/src/js/sheet/sheetCollabClient.ts[143-156]
- ui/src/js/sheet/transform.ts[8-23]
- ui/src/js/sheet/undo.test.ts[194-206]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +151 to +153
const rebase = (stack: Op[][]): Op[][] => stack.map((entry) => entry.map((o) => transform(o, remoteOp)));
this.undoStack = rebase(this.undoStack);
this.redoStack = rebase(this.redoStack);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

2. Deleted-row undo clobbers cells 🐞 Bug ≡ Correctness

Rebasing a stored cell inverse through a remote row or column deletion clamps its target to the
deletion index. Undo then writes the old value into the unrelated cell that shifted into that
position.
Agent Prompt
## Issue description
History rebasing uses the normal pending-operation transform, which clamps inverse cell coordinates inside a remotely deleted band and redirects undo onto unrelated shifted cells.

## Issue Context
Introduce history-specific transformation semantics that can remove obsolete point inverses rather than clamping them. Cover both row and column deletion with collaborative regression tests.

## Fix Focus Areas
- ui/src/js/sheet/sheetCollabClient.ts[143-156]
- ui/src/js/sheet/transform.ts[30-69]
- ui/src/js/sheet/undo.test.ts[194-206]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread ui/src/js/sheet/undo.ts
Comment on lines +114 to +116
case 'setCell':
case 'setStyle':
return [cellRestore(wb, op, sheet, row, col)];

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Action required

3. Style undo overwrites remote content 🐞 Bug ≡ Correctness

A setStyle operation is inverted as a setCell containing the previous raw value. If a
collaborator edits the content afterward, undoing the local formatting restores stale text and
erases that edit.
Agent Prompt
## Issue description
Formatting-only operations currently receive a full-cell inverse, allowing style undo to overwrite a collaborator's later content edit.

## Issue Context
Generate a `setStyle` inverse containing only the previous style properties for `setStyle`. Add a two-client test where one client formats a cell, another changes its raw content, and the first client undoes formatting without changing the content.

## Fix Focus Areas
- ui/src/js/sheet/undo.ts[24-35]
- ui/src/js/sheet/undo.ts[113-116]
- ui/src/js/sheet/undo.test.ts[56-60]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +691 to +692
if (mod && !editingNow() && !readOnly) {
const k = e.key.toLowerCase();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

4. Formula undo triggers sheet history 🐞 Bug ≡ Correctness

The global undo/redo shortcut only checks whether the grid is editing, not whether the formula-bar
input is focused. Pressing Ctrl/Meta+Z while editing a formula therefore invokes workbook history
instead of the input's native text undo.
Agent Prompt
## Issue description
Workbook undo and redo intercept native text-history shortcuts while the user is typing in the formula bar.

## Issue Context
Guard global history shortcuts when the event target is an input, textarea, or editable element, or expose formula-bar editing state. Add browser coverage proving formula text changes locally without changing workbook history.

## Fix Focus Areas
- ui/src/js/sheet/sheetEditor.ts[689-702]
- ui/src/js/sheet/sheetFormulaBar.ts[71-80]
- ui/src/js/sheet/sheetFormulaBar.ts[119-130]
- playwright/specs/sheet_excel_chrome.spec.ts[162-180]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +658 to +659
if (which === 'undo') collab.undo();
else collab.redo();

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

5. Undo leaves invalid active sheet 🐞 Bug ≡ Correctness

Undoing an added sheet deletes it without updating activeSheetId. The editor remains pointed at a
nonexistent sheet, producing no active tab and an empty grid until another sheet is selected.
Agent Prompt
## Issue description
History replay can delete the currently active sheet without selecting a surviving sheet, leaving editor state inconsistent.

## Issue Context
After undo or redo, verify that `activeSheetId` still exists and select a valid fallback when it does not. Add coverage for adding and activating a sheet, undoing the addition, and verifying that another sheet becomes active.

## Fix Focus Areas
- ui/src/js/sheet/sheetEditor.ts[508-539]
- ui/src/js/sheet/sheetEditor.ts[653-660]
- ui/src/js/sheet/undo.ts[69-80]
- ui/src/js/sheet/sheetTabs.ts[37-45]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Undo replays inverse ops through the normal collaborative pipeline instead of
rolling state back, so it stays safe under concurrency: the inverse is computed
against the state before the op, recorded per client, transformed against every
remote op that arrives, and then sent like any other edit. Undo therefore only
ever reverts your own work, never a collaborator's.

invertOp covers the whole op vocabulary: cells and styles (carrying props, not
the client-local style id), clearRange, row/column inserts and deletes
including the cells, sizes and merges a deletion destroys, merges and the
merges they absorbed, dimensions, freeze and the sheet-list ops - deleting a
sheet restores its contents.

Ops applied within one tick form one history entry, so a paste, a fill or
styling a range undoes in a single step without any call-site changes. History
is capped at 100 entries; a fresh edit drops the redo branch, a redo keeps it.

UI: Undo/Redo buttons in the Home tab that grey out when a stack is empty,
plus Ctrl+Z, Ctrl+Y and Ctrl+Shift+Z outside cell editing (inside a cell the
browser text undo keeps the keystroke).

Known limit: undoing the first-ever resize of a row or column restores the
grid default, because the op vocabulary has no way to unset a dimension.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@SamTV12345
SamTV12345 force-pushed the feat/sheet-undo-redo branch from 9dc7a64 to 89d8886 Compare July 28, 2026 19:45
@SamTV12345
SamTV12345 merged commit c1d9d6d into main Jul 28, 2026
11 checks passed
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.

1 participant