- Document: Untitled Google Doc, shared "Anyone with the link can edit"
- Tab 0 (User A): javagrant@gmail.com
- Tab 1 (User B): Same account (javagrant@gmail.com), second browser tab
- Key finding: Undo stacks are per-session/tab, not per-account
Action: Type "Hello" → Press Undo Result: ✅ Text removed. Document returns to empty.
Action: Type "HelloWorld" (contiguous burst) → Press Undo once Result: ✅ Entire "HelloWorld" removed in one undo. Consecutive typing actions are grouped as one logical undo unit.
Action: Type "Hello" → Select All + Delete → Press Undo Result: ✅
- Undo 1: Restored "Hello" (undid the deletion action, which has inverse "Insert Hello")
- Undo 2: Removed "Hello" (undid the original typing action)
Action: Type "Hello" → Select All + Bold → Select All + Delete → Press Undo repeatedly Result: ✅ Three separate undo steps:
- Undo 1: Restored bold "Hello" (undid deletion)
- Undo 2: Removed bold formatting → "Hello" in normal text
- Undo 3: Removed "Hello" → empty
Action: Type "Hello" → Bold → Press Undo Result: ✅ (same pattern as Test 4 — formatting action is separate from typing on the undo stack)
Action: Type "Hello" → Bold → Type " World" → Press Undo Result: ✅ Only the last typing burst (" World") removed. Left bold "Hello".
- Confirms each action type (typing group, formatting change) creates a separate undoable entry.
Action: Tab 0 types "AAA" → Tab 1 types "BBB" → Tab 0 presses Undo Result: ✅ Only "AAA" removed. "BBB" preserved.
- Confirms each tab has its own undo stack scoped to that session.
Action: Tab 0 types "The cat sat." → Tab 1 inserts "Black " inside → Tab 0 presses Undo Result: ✅ A's undo removed only A's contribution ("the cat sat"). B's "Black " preserved.
- Undo operation was OT-transformed against B's concurrent edit.
- Result: "Black."
Action: Tab 0 types "Hello" → Tab 1 Select All + Delete → Tab 0 presses Undo Result: ✅ No-op. Document stays empty.
- Inverse operation (Delete Hello) targets text that no longer exists. OT transforms it to Delete(0,0) — inert.
Action: Tab 0 and Tab 1 alternate typing characters (H, W, e, orld, llo) Result: "WorldHello" in document. Tab 0 presses Undo.
- Undo 1: Removed "llo"
- Undo 2: Removed "e"
- Undo 3: Removed "H"
- Result: "World" (selected)
- Key finding: Remote operations that arrive DURING a typing burst BREAK the typing group. Each character becomes a separate undoable action.
Action: Tab 0 types "AAAAA" as one completed burst → Tab 1 inserts "X" at position 2 → Tab 0 presses Undo once Result: ✅ All 5 A's removed in one undo. The burst was committed as one group before the remote op arrived. "X" remains.
- Distinction from Test 10: if the burst finishes BEFORE the remote edit arrives, the group stays intact.
- Remote edits in the middle of existing text do not retroactively break completed typing groups.
Action: Tab 0 types "Hello" → Tab 0 undoes → Tab 1 types "World" → Tab 0 redoes (⌘Y) Result: ✅ Redo survives, OT-transformed. "WorldHello" produced.
- Tab 0's redo (Insert "Hello" at 0) was OT-transformed against Tab 1's Insert "World" at 0 → Insert "Hello" at position 5.
- Redo button
aria-disabled="false"throughout — the redo stack is NOT cleared by remote edits. - Initial test was a false negative due to keyboard focus not being on the text entry iframe.
Action: Tab 0 types "Hello" → Tab 0 undoes → Tab 0 types "World" → Tab 0 redoes (⌘Y) Result: ✅ Redo was a no-op. New local edit ("World") after undo cleared the redo stack.
- Standard single-user redo semantics hold: any new edit (local or remote) clears the redo stack.
Action: Tab 0 pastes "Hello World" (via ⌘V) → Tab 0 presses Undo Result: ✅ Entire paste removed in one undo. Paste is treated as a single undoable action, not broken into per-character groups.
- Google Docs coalesces paste into one logical undo entry (unlike interleaved typing which breaks groups).
Action: Tab 0 types "AAA" → Tab 1 types "BBB" → Tab 0 deletes "AAA" → Tab 0 presses Undo Result: ✅ Undo correctly scoped to A's actions across the chain.
- Undo 1: Restored "AAABBB" (undid the deletion of "AAA")
- Undo 2: Removed "AAA" (undid original typing by A)
- B's "BBB" remained intact throughout.
- OT correctly transforms undo operations across a three-way interleaved chain of operations (insert → insert → delete → undo)
Action: Tab 0 types "AAA" → Tab 1 types "b" → Tab 0 presses Undo (while B's typing is in-flight) → Tab 1 types "BB" Result: ✅ "BBB" remains, "AAA" gone. Undo was correctly OT-transformed against B's concurrent "b" insertion.
- Concurrent typing by another user does not break or corrupt A's undo.
- OT transformation correctly handles the race condition between B's ops and A's undo.
Action: Tab 0 types "AAAAA" → Tab 1 types "BB" (partial burst) → Tab 0 undoes → Tab 1 types "BBB" (completes burst) Result: ✅ "BBBBB" remains, "AAAAA" gone. Undo correctly removed A's text while preserving B's partial burst ("BB") that arrived before the undo, and B's continuation ("BBB") that arrived after.
- Typing group boundaries are respected across undo: one side's partial burst isn't corrupted by the other's undo.
- No data loss or duplication.
All high-risk tests re-run with explicit doc.body.focus() before keyboard input to eliminate potential focus false negatives:
| Test | Original | v2 Result | Status |
|---|---|---|---|
| 7 (per-session undo) | ✅ | ✅ "AAA" removed, "BBB" intact | Confirmed |
| 8 (OT-transformed undo) | ✅ | ✅ "Black." remains | Confirmed |
| 9 (no-op undo) | ✅ | ✅ Doc stays empty, undo consumed | Confirmed |
| 10 (interleaved typing) | ✅ | ✅ 3 undos: "llo"→"e"→"H", "World" remains | Confirmed |
| 10b (completed burst intact) | ✅ | ✅ All 5 A's removed in one undo | Confirmed |
| 11 (redo after remote edit) | ❌ false negative | ✅ Redo survives, OT-transformed to "WorldHello" | Corrected |
| 15b (undo mid-burst) | ✅ | ✅ "BBBBB" intact, no corruption | Confirmed |
Key lesson: Always explicitly focus the text entry iframe (docs-texteventtarget-iframe) before issuing keyboard commands. The tab switch alone does not transfer keyboard focus to the editor.
- Per-session/tab undo stacks, not per-account
- OT transforms undo operations — positions shift as other users edit
- User-scoped undo — A's undo never removes B's content
- Remote ops arriving DURING a burst break typing groups — each character becomes a separate undo entry (Test 10)
- Completed bursts stay intact — if the burst finishes before the remote op arrives, the group is NOT retroactively broken (Test 10b)
- Deletion is its own undoable action — format/delete/type are all separate entries
- No-op on phantom text — undo of already-deleted content is inert
- Undo = apply inverse op through OT — not "restore previous state"
- Redo survives remote edits — redo stack is NOT cleared by remote edits; redo actions are OT-transformed (Test 11, corrected)
- Local new edit clears redo stack — standard single-user redo semantics (Test 12)
- Paste is one undo group — pasted content coalesces into a single undoable action (Test 13)
- Undo survives concurrent editing — pressing undo while another user is actively typing does not corrupt the result (Test 15)
- Undo mid-burst preserves context — undo correctly handles the case where a collaborator has partial typing before the undo and continues after (Test 15b)