Skip to content

v2 editing model: optimistic commits + hold() gating + per-commit token#330

Merged
CarlosNZ merged 16 commits into
v2.0-devfrom
feat/v2.0-editing-model
Jun 8, 2026
Merged

v2 editing model: optimistic commits + hold() gating + per-commit token#330
CarlosNZ merged 16 commits into
v2.0-devfrom
feat/v2.0-editing-model

Conversation

@CarlosNZ

@CarlosNZ CarlosNZ commented Jun 7, 2026

Copy link
Copy Markdown
Owner

What & why

Reworks the editing/commit lifecycle so the window "submitted but not yet resolved" — while onUpdate runs (a confirm dialog, a slow remote save) — is a first-class state. Previously the model had only two states (editing | not); that gap was patched out-of-band in #325 (committingPaths) and produced ~5 bugs that only showed up with a slow/deferred onUpdate (commit-after-cancel, double events, mode-blind close, stale suppression, double-submit). That patch was abandoned; this fixes the class by construction.

Closes the design work tracked in #325.

The model

  • Optimistic by default. On submit the editor closes and the data updates immediately; onUpdate runs in the background; a rejection (false / { error } / throw / rejected promise) reverts the change and surfaces the error. A slow onUpdate never blocks the editor.
  • Optional gate via hold(). onUpdate gets a second argument, { hold }. Calling hold() (synchronously, before the first await) keeps the editor open and blocks the tree until the returned release() is called — the path for confirmation dialogs / pre-commit validation.
  • Per-commit token. Each in-flight commit is tracked independently, so a late failure reverts only its own node and a stale result can't clobber a newer edit (Tabbing fires several background commits; latest-wins).

Event model

onEditEvent is now start* → [submit*] → commit*, or start* → cancel*, plus updateSuccessful / updateError for the background settlement of any committed change whose onUpdate ran. Renames confirm*commit* and adds submit* + the two settlement events. A no-op confirm fires commitEdit (not cancelEdit).

Engine bugs found + fixed while migrating the tests

  1. dataRef staleness — optimistic apply calls setData, but dataRef.current only refreshed on the next render, so a synchronously-resolving onUpdate ran the revert against the pre-apply doc (add-revert threw). Fixed with a commitDocument helper that updates the ref synchronously.
  2. commit/settlement events rebuilt NodeData from the now-mutated live doc → delete/rename described a vanished node. Fixed with a frozen per-op BuiltCommit.nodeData snapshot.
  3. { value } override wrote to the node path instead of replacing the whole document.
  4. The node never reverted its local buffer on a rejected/cancelled settlement (the [data] effect can't fire when data didn't change). Added settleEdit, guarded against clobbering an in-progress re-edit.
  5. A superseded stale commit still triggered node UI — reconcile now returns undefined on token mismatch.
  6. Tab off an unedited field didn't advance — the no-op branch dropped onCommit.

Tests

Migrated the §17 lifecycle + result-protocol suites to the optimistic model (reject = apply-then-revert, asserted via the net setData; async paths use waitFor; primitive/enum type-change is now local-then-commit). Added 4 regression tests pinning the hard guarantees: hold() + tree-block → commit → updateSuccessful; concurrent optimistic commits where a late failure reverts only its node; re-edit-during-settlement; confirm-before-delete. Full suite green (364 passed, 2 todo); compile/lint/build clean.

Docs & demo

README gained an "Optimistic updates and gating (hold)" section + a rewritten onEditEvent; migration-guide §9/§10 updated; changeset added (json-edit-react: major). Demo: fixed the isEditing heuristic so submit* doesn't prematurely flip it.

Draft — still open

  • Bundle regression: the engine adds +627 B gzip (19,317 → 19,944 B), now ~677 B over the V1 1.30.1 baseline. A forensic breakdown + a micro-trim pass are next, before this is marked ready.
  • The temporary EditingModel-current.md / EditingModel-new.md working docs (at repo root) should be removed before merge.

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Jun 7, 2026

Copy link
Copy Markdown

Bundle size impact

json-edit-react

Format Base raw PR raw Δ raw Base gzip PR gzip Δ gzip
esm 53.82 KB 54.53 KB 🔺 +724 B (+1.31%) 18.86 KB 19.47 KB 🔺 +626 B (+3.24%)
cjs 55.32 KB 56.02 KB 🔺 +715 B (+1.26%) 18.94 KB 19.53 KB 🔺 +605 B (+3.12%)

Measured from build/index.{cjs,esm}.js. Gzip at level 9.

@CarlosNZ CarlosNZ marked this pull request as ready for review June 7, 2026 21:18
@CarlosNZ CarlosNZ added the V2 To include in Version 2 label Jun 7, 2026
@CarlosNZ CarlosNZ added this to the v2.0 milestone Jun 7, 2026
Comment thread src/contexts/EditingProvider.tsx Outdated
Comment thread src/contexts/EditingProvider.tsx Outdated
CarlosNZ added 3 commits June 8, 2026 22:26
- Move OPTIMISTIC_DELAY_MS to the top of the file (alongside the
  type aliases) so the constant is easy to find.
- Replace the if-cascade in eventForOp with a nested EVENT_FOR_OP
  map. Reads more cleanly and the event names become grep-able.
@CarlosNZ CarlosNZ merged commit b26c2cd into v2.0-dev Jun 8, 2026
2 checks passed
@CarlosNZ CarlosNZ deleted the feat/v2.0-editing-model branch June 8, 2026 20:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

V2 To include in Version 2

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant