app: fix round 1 follow-up — a dropped batch also poisoned every later edit
Found by this task's own required verification step ("paste over 1MB,
confirm a failure, confirm typing afterward still saves and survives
a reload") after the F1/F2/F3 fixes — not by the reviewer's own
findings, but squarely inside what that step exists to catch: F1's
fix (drop a permanently-rejected batch rather than requeue it) turned
out not to be sufficient on its own.
ySyncPlugin mirrors every local ProseMirror transaction into the
Y.Doc immediately and optimistically, before the network call even
starts. Once a batch is dropped, the local doc already holds content
the server never received, and every Yjs update produced afterwards
is encoded relative to that now-fictional history. core.Handle.Apply
does not error on such an update — it has nowhere to attach it, so it
is accepted (a real 204) but never actually integrated, and is
silently absent from every later replay. Confirmed both against real
yjs in isolation and against the running app: a 413'd paste followed
by a small, separately-POSTed, 204'd edit still vanished on reload.
Fixed with resync() in fixtures/editor-entry.js: on a permanent
rejection, discard the diverged local Y.Doc/EditorView entirely and
rebuild both from a fresh fetch of the server's own log, the same
truth a manual reload already reconstructs correctly. This
necessarily also discards whatever was typed locally in the interim —
it was built on the same poisoned history and could never have been
saved either — which is exactly why docs.editor.status.failed's own
copy tells a person to copy anything important out first.
One implementation bug caught along the way by the same manual
check: the replacement EditorView was originally created before the
old one was destroyed, so two views briefly held {mount: el} on the
same DOM node at once — {mount: el} assumes exclusive ownership, and
sharing it corrupted focus so the very next keystroke landed nowhere.
Fixed by destroying first, then mounting.
Verified live in a browser: pasted ~1.4MB into a seeded document (a
real 413), confirmed the failed indicator, typed a follow-up
sentence (a real 204), reloaded, and confirmed the paste was gone but
the follow-up sentence was present — full evidence in this task's
report.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011F5CivVm4CmL9ByfSSAaXA
2 files changed,
+179
−80
fixtures/editor-entry.js+166 −67internal/docs/static/{editor.cd82277ea6dc20a0.js => editor.40cce03a9468a798.js}+13 −13
Intent
fixtures/editor-entry.jsLoading diff…
internal/docs/static/{editor.cd82277ea6dc20a0.js => editor.40cce03a9468a798.js}Loading diff…