Skip to content

Route all doc mutations through one commit funnel with change notifications#345

Open
johannesmutter wants to merge 2 commits into
mainfrom
op-engine/1-commit-funnel
Open

Route all doc mutations through one commit funnel with change notifications#345
johannesmutter wants to merge 2 commits into
mainfrom
op-engine/1-commit-funnel

Conversation

@johannesmutter

Copy link
Copy Markdown
Collaborator

Part 1/4 of the op-engine series proposed in #335. Stacked on fix/batch-window-after-undo, so this PR's diff is only its own commit. Built directly on the TypeScript main.

  • apply(), undo() and redo() commit through one private _commit(); undo and redo previously mutated session.doc directly, invisible to any observer of document changes (autosave, op logs, sync)
  • session.on_change(listener) delivers { ops, inverse_ops, origin } after the doc swap; op arrays are copied so history batching can't mutate a delivered record; listener exceptions are isolated so a faulty observer can't desync history from the already-swapped doc
  • the ChangeEvent type is exported from the package index; shared test helpers for the series live in src/test/op_engine_helpers.ts

Tests: 8 for this PR; each PR in the series is green at its own layer.

🤖 Generated with Claude Code

johannesmutter and others added 2 commits July 18, 2026 10:56
A batched apply right after an undo read history[history_index] while
last_batch_started was still open. After an undo that slot is stale, or
gone entirely (history_index may be -1 once the redo tail is
truncated), which crashed with "TypeError: Cannot read properties of
undefined (reading 'ops')" — reproducible by typing, pressing undo, and
typing again within one second. When the slot still existed, new typing
was silently merged into a history entry it didn't belong to,
corrupting undo granularity.

Undo and redo now close the batch window, so typing after history
navigation always starts a fresh entry.

Also aligns the batching comments with BATCH_WINDOW_MS (1 second); they
still said 2 seconds.

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

apply(), undo() and redo() now commit through a single private _commit()
which swaps the doc and notifies subscribers registered via
session.on_change(). Undo and redo previously mutated session.doc
directly, so any observer of document changes (autosave, op logs,
sync) would silently miss them.

on_change listeners receive { ops, inverse_ops, origin } after the doc
swap; op arrays are shallow-copied so history batching cannot mutate a
delivered change record. Ops-less commits (selection-only transactions)
do not notify, matching the existing doc-swap gate. Listener exceptions
are caught and logged: a faulty observer cannot abort the commit and
desync the recorded history from the already-swapped doc.

The ChangeEvent type is exported from the package index. Test helpers
shared by the op-engine suites live in src/test/op_engine_helpers.ts.

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

vercel Bot commented Jul 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
svedit Ready Ready Preview, Comment Jul 18, 2026 9:16am

@michael
michael changed the base branch from fix/batch-window-after-undo to main July 19, 2026 11:38
@michael
michael marked this pull request as draft July 22, 2026 10:29
@michael
michael marked this pull request as ready for review July 22, 2026 10:30
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