Back to Plate

Slate Yjs From-Scratch Operation Matrix Plan

docs/plans/2026-05-29-slate-yjs-from-scratch-operation-matrix.md

53.0.833.3 KB
Original Source

Slate Yjs From-Scratch Operation Matrix Plan

Date: 2026-05-29 Status: complete Owner skills: .agents/skills/task/SKILL.md, .agents/skills/slate-ralplan/SKILL.md Completion: .tmp/019e71a2-c1fd-7bf3-83ff-732f806062ee/completion-check.md

Supersedes for current execution:

  • docs/plans/2026-05-13-slate-v2-yjs-core-readiness-ralplan.md
  • docs/plans/2026-05-13-yjs-collaboration-harvest.md
  • docs/plans/2026-05-18-slate-yjs-package-readiness-ralplan.md
  • docs/plans/2026-05-28-slate-yjs-current-architecture-operation-matrix.md

Current Verdict

Build a new first-party @slate/yjs source package from scratch in ../slate-v2/packages/slate-yjs.

The current live tree has only package residue:

  • packages/slate-yjs/dist/**
  • packages/slate-yjs/node_modules/**
  • packages/slate-yjs/.turbo/**
  • no packages/slate-yjs/package.json
  • no packages/slate-yjs/src/**
  • no packages/slate-yjs/test/**
  • no site/examples/ts/yjs-collaboration.tsx

The previous staged docs and Playwright file in ../slate-v2 are source material, not implementation truth. Keep their behavioral specs, especially undo/reconnect/selection rows, but do not carry over missing or residue package code.

Strong call: do not resurrect the old monolithic package from dist. Dist is not source. Rebuild cleanly around current Slate v2 extension APIs.

Intent And Boundary

Intent: ship a first-party Yjs binding that makes collaboration correctness a protocol contract, not a demo accident.

Desired outcome:

  • source package @slate/yjs exists under packages/slate-yjs
  • public API is small and current-Slate-native
  • all Slate operations and named transform families are classified in an operation matrix
  • every supported operation has local-offline, concurrent-remote, reconnect/recovery, and local undo/redo proof
  • unsupported behavior fails explicitly or enters a named traceable fallback
  • undo only reverts local user intent while preserving concurrent remote edits
  • structural encoders preserve existing Y.XmlText and Y.XmlElement identities whenever possible
  • example is a four-peer collaboration simulator with real controls
  • Playwright covers selection and undo/reconnect regressions

In scope:

  • ../slate-v2/packages/slate-yjs/**
  • ../slate-v2/site/examples/ts/yjs-collaboration.tsx
  • ../slate-v2/site/constants/examples.ts
  • ../slate-v2/playwright/integration/examples/yjs-collaboration.test.ts
  • changeset and package wiring needed for the new package
  • docs/solutions and plan sync when implementation discovers reusable behavior

Non-goals:

  • no Plate product API in raw Slate
  • no provider hosting, auth, comments, suggestions, or permission policy
  • no compatibility promise for external @slate-yjs/* wrappers
  • no Yjs objects in serialized Slate values
  • no fixed issue claim for #5771 until package/browser proof passes
  • no stable moved-subtree collaboration claim for move_node until node identity is preserved or a logical identity layer is proven

Decision boundaries:

  • raw Slate owns document values, operations, transactions, history, refs, runtime ids, and React/browser runtime contracts
  • @slate/yjs owns Yjs schema, binding lifecycle, operation encoding, remote import, history/UndoManager bridge, selection/relative-position mapping, awareness, trace policy, and example controls
  • Plate owns product collaboration UI

Current Source Evidence

Live ../slate-v2 facts read this activation:

SurfaceEvidenceDecision
package residuefind packages/slate-yjs -maxdepth 4 -print lists only dist, node_modules, .turbohard-cut and recreate source
operation unionpackages/slate/src/interfaces/operation.ts includes insert_text, remove_text, insert_node, remove_node, split_node, merge_node, move_node, set_node, set_selection, replace_fragment, replace_childrenoperation registry must be exhaustive
extension APIpackages/slate/src/core/editor-extension.ts rejects register, commands, methods, operationMiddlewares, commitListeners; supports setup and onCommitpublic API must be extension/state/tx based
core collab proofpackages/slate/test/collab-adapter-extension-contract.ts proves commit export, remote import, pause, skip-collab, cleanup without monkey-patchespackage binding follows this shape
selection stresspackages/slate/test/collab-selection-stress-contract.ts proves high-QPS remote selection rebasing and remote-history skipport these scenarios into package/browser proof
bookmarkspackages/slate/test/collab-bookmark-position-contract.ts proves split, merge, move, remove, replace_children bookmark rebasingY relative positions must match this semantics
canonical reconcilepackages/slate/test/collab-canonical-reconcile-contract.ts proves remote full replace can skip local history and side effectsallowed only as explicit traceable fallback/reconcile mode
example registrysite/constants/examples.ts has no yjs-collaboration entryadd route registration
staged previous specsstaged playwright/integration/examples/yjs-collaboration.test.ts covers four peers, selection, offline reconnect, undo/redo, split/merge/remove/move/wrap/fragment rowspreserve as browser acceptance source

Staged ../slate-v2 source material inspected:

  • docs/plans/2026-05-25-yjs-demo-four-editors-marks.md
  • docs/plans/2026-05-25-yjs-offline-mixed-edits-reconnect.md
  • docs/plans/2026-05-26-yjs-history-split-fixes.md
  • docs/plans/2026-05-26-yjs-offline-mark-stale-undo.md
  • docs/plans/2026-05-26-yjs-offline-merge-undo-noop.md
  • docs/plans/2026-05-26-yjs-offline-move-undo-redo.md
  • docs/plans/2026-05-27-yjs-collaboration-soak.md
  • docs/plans/2026-05-27-yjs-merge-node-canonical-late-peer.md
  • docs/plans/2026-05-28-yjs-command-matrix-controls.md
  • docs/plans/2026-05-28-yjs-potion-parity-regressions.md
  • docs/plans/2026-05-28-yjs-public-split-api.md
  • docs/solutions/logic-errors/yjs-structural-wrap-fragment-parity-2026-05-28.md
  • docs/solutions/logic-errors/yjs-forward-move-history-fallback-2026-05-26.md
  • docs/solutions/logic-errors/yjs-offline-split-reconnect-merge-2026-05-25.md
  • docs/solutions/logic-errors/yjs-offline-merge-stale-undo-2026-05-26.md
  • docs/solutions/ui-bugs/slate-react-structural-text-dom-sync-2026-05-28.md
  • playwright/integration/examples/yjs-collaboration.test.ts

Ecosystem Strategy Synthesis

SystemSourceMechanismAvoidsStealRejectSlate targetVerdict
external slate-yjs../slate-yjs/packages/core/src/plugins/withYjs.ts, withYHistory.ts, applyToYjs/*editor wrapper stores local origins, observes Y events, maps Slate ops to Y edits, stores relative selections in UndoManager metadatapure snapshot sync for every editoperation mappers, relative-position conversion, origin discipline, Y.UndoManager metadatamutating editor.children, overriding apply/onChange, wrapper-first public APIextension-owned controller and operation registrypartial
Lexical Yjs../lexical/packages/lexical-yjs/src/Bindings.ts, SyncV2.tsbinding object owns Y doc, node mapping, default property filtering, cursor state, and XmlElement/XmlText mappingeditor core depending on Yjs classesbinding/controller object, default-property exclusion, in-place mapping, cursor state outside doc modelLexical class-node/node-key couplinginternal YjsController with maps hidden behind state.yjs/tx.yjspartial
y-prosemirror../y-prosemirror/src/sync-plugin.js, undo-plugin.jsplugin state binds Y type, uses transaction metadata, stores undo selection through relative bookmarks, reconfigures/pause via plugin statelocal undo deleting remote edits, stale selection after remote importrelative-selection undo restore, pause/reconfigure lifecycle, transaction metadata disciplineProseMirror plugin surface and schema fitting as raw Slate APItx.yjs.pause/resume/reconcile, UndoManager bridge, remote imports with history skipagree
current Slate v2packages/slate/test/collab-*.ts, editor-extension.tsextension setup/onCommit, transaction replay, skip-history metadata, bookmarks, runtime ids, canonical replaceadapter monkey-patches and remote side effectsstate/tx namespaces, commit export, remote import through editor.updateadding Yjs to raw slate corefirst-party package on current extension substrateagree

Accepted Public API Target

Keep the first public surface deliberately small:

ts
import { createEditor } from 'slate'
import { history } from 'slate-history'
import { createYjsExtension } from '@slate/yjs'

const editor = createEditor({
  extensions: [
    history(),
    createYjsExtension({
      awareness,
      doc,
      rootName: 'slate',
      clientId: 'peer-a',
    }),
  ],
})

editor.tx.yjs.connect()
editor.tx.yjs.disconnect()
editor.tx.yjs.pause()
editor.tx.yjs.resume()
editor.tx.yjs.reconcile()
editor.tx.yjs.undo()
editor.tx.yjs.redo()

editor.read((state) => state.yjs.connected())

Exports:

  • @slate/yjs
  • @slate/yjs/core
  • @slate/yjs/react
  • @slate/yjs/internal only for package-local tests and explicitly unstable test helpers

Hard cuts:

  • no withYjs(editor)
  • no public editor.apply/editor.onChange patching
  • no direct editor.children = ...
  • no hidden provider abstraction

Internal Runtime Target

Target file layout:

FileOwns
src/index.tsroot public exports
src/core/index.tsstable core exports
src/core/extension.tscreateYjsExtension and state/tx namespace registration
src/core/controller.tslifecycle, local export, remote import, transaction/origin discipline
src/core/document.tsSlate value <-> Yjs document model
src/core/operations.tsoperation encoder registry, exhaustiveness, trace policy
src/core/history.tsUndoManager bridge, local-intent undo/redo, history repair
src/core/selection.tsSlate point/range <-> Y.RelativePosition
src/core/awareness.tsawareness payloads and remote cursor projection
src/core/testing.tspackage-local harness only
src/react/index.tsxexternal-store cursor hooks and optional render helpers
src/internal/index.tsunstable test/debug exports

The controller is the package brain. index.ts must stay thin.

Operation Matrix

Every row needs four scenario lanes:

  • local offline operation
  • concurrent remote operation
  • reconnect / recovery through real Y.encodeStateAsUpdate / Y.applyUpdate
  • local undo / redo through Yjs local intent semantics
SurfaceDecisionIdentity ruleFallback ruleRequired proof
insert_textsupportmutate existing Y.XmlTextno snapshot4 lanes plus unicode/mark metadata
remove_textsupportmutate existing Y.XmlTextno snapshot4 lanes plus concurrent same-offset inserts
insert_nodesupportinsert new Y.XmlElement/Y.XmlText at parent slotno root snapshot4 lanes nested and top-level
remove_nodesupporthide/remove one target node, preserve siblingsno root snapshotfirst red test slice
split_nodesupportkeep original left shared type, create right siblingno root snapshotpublic tx.nodes.split({ at: Point }) and keyboard Enter
merge_nodesupportpreserve survivor shared type; absorbed children stay live through virtual merge refsno root snapshot; trace virtual merge as virtual-merge-refkeyboard Backspace, tx merge, and cross-block delete
move_nodeP1 support, P2 gatedP1 clone+hide only for outside-subtree convergence; P2 requires stable moved identityexplicit trace.mode: "clone-hide-move" until P24 lanes plus documented limitation
set_nodesupportset attributes/leaf metadata in placeno snapshotelement props, text marks
unset_nodetransform characterizationlower to set_node clearing propsno snapshotexact emitted op sequence then 4 lanes
replace_childrensupportscoped child-window edit; preserve unaffected childrenexplicit reconcile only for dev/test trace4 lanes
replace_fragmentsupportscoped fragment replace under operation pathno root snapshot4 lanes
insert_fragmenttransform characterizationlower to existing operation rowsno hidden private mutationexact emitted op sequence then 4 lanes
delete_fragmenttransform/browser characterizationlower to existing operation rows; preserve absorbed end-block text identity through virtual merge refsno hidden private mutationbrowser selection delete plus op sequence
wrapNodes / tx.nodes.wrapsupport through op sequencepreserve original child identity through virtual wrapper/source refno clone-only if concurrent inner edits matter4 lanes
unwrapNodes / tx.nodes.unwrapsupport through op sequencerestore original child identityno root snapshot4 lanes
liftNodes / tx.nodes.liftsupport through op sequencemove scoped child without root rewritenamed clone-hide trace if needed4 lanes

Completion rule: no matrix row can be silent. It is either supported, explicitly unsupported with thrown error, or traceable fallback with tests.

Undo Protocol

Undo is protocol, not polish.

Rules:

  • local undo/redo must route through Y.UndoManager tracked origins
  • remote imports must not enter Slate local history
  • undo stack metadata stores relative selection before/after where possible
  • undo must revert the local user's semantic intent, not replay inverse stale Slate operations against a changed document
  • after reconnect, if local Slate history and Yjs truth disagree, canonical Yjs truth wins and the local editor is repaired
  • no coarse document snapshot restore as undo implementation

Use staged solution notes as acceptance cases:

  • offline split plus concurrent text replacement
  • offline Backspace merge plus concurrent insert, then keyboard Undo
  • offline move reconnect, keyboard Undo, keyboard Redo
  • offline wrap / insert fragment preserving concurrent text in the edited subtree

Example Simulator Target

File: ../slate-v2/site/examples/ts/yjs-collaboration.tsx

Requirements:

  • four peers: A, B, C, D
  • deterministic Yjs clientID / displayed peer id mapping for tests
  • per-peer connect, disconnect, pause, resume, reconcile
  • per-peer undo/redo with visible enabled state
  • operation controls for every matrix family
  • selection controls: caret, word range, paragraph range, whole doc
  • concurrent scenario runners that queue offline edits then reconnect
  • visible debug rows: connected state, doc client id, export/import counts, trace counters, selection JSON, remote cursor rows, document JSON
  • controls use public Slate transaction APIs or browser editing paths, not private model mutation

The staged Playwright file expects data-test-id attributes such as:

  • yjs-peer-a-mark-bold
  • yjs-peer-a-select
  • yjs-peer-a-disconnect
  • yjs-peer-a-connect
  • yjs-peer-a-split-node
  • yjs-peer-a-wrap-node
  • yjs-peer-a-insert-fragment
  • yjs-peer-a-move

Test Harness Target

Add package-local tests under ../slate-v2/packages/slate-yjs/test/.

Harness responsibilities:

  • create three or four peers backed by separate Y.Doc instances
  • seed peers from one Yjs update
  • disconnect a peer without destroying its local doc
  • exchange updates only with Y.encodeStateAsUpdate / Y.applyUpdate
  • capture operation trace entries
  • expose helpers for local Slate transactions, public transforms, undo, redo, reconnect, convergence, and identity assertions
  • compare both Slate values and Yjs shared types

Do not test by comparing only local Slate values. That is how broken snapshot fallback looked correct.

Implementation Phases

  1. Hard-cut package residue and scaffold @slate/yjs.
    • Add package.json, tsconfig, tsdown config, source entries, test folder.
    • Add yjs dependency.
    • Add changeset.
  2. Add extension/controller skeleton.
    • createYjsExtension, state.yjs, tx.yjs.
    • Connect/disconnect/pause/resume/reconcile lifecycle.
  3. Add document serialization.
    • Y.XmlElement for elements.
    • Contiguous Slate text leaves in Y.XmlText with leaf metadata.
    • No Yjs types in Slate values.
  4. Add operation trace and exhaustive registry.
    • Test unsupported operations throw.
    • Test supported operations cannot hit root snapshot.
  5. First red/green operation slice: remove_node.
    • four scenario lanes.
    • prove no root rewrite and sibling identity preserved.
  6. Complete structural rows in this order:
    • split_node
    • merge_node
    • insert_fragment
    • wrapNodes
    • unwrapNodes
    • move_node P1
  7. Backfill existing simple rows:
    • insert_text
    • remove_text
    • insert_node
    • set_node / unset_node
    • replace_children
  8. Add UndoManager bridge and history repair.
    • prove local intent undo with concurrent remote edits.
  9. Add awareness/selection conversion and React hooks.
    • external-store style, no document commits for cursor movement.
  10. Build example simulator and register route.
  11. Port staged Playwright specs and add missing selection rows.
  12. Run gates and sync issue/reference docs conservatively.

Do not implement the whole matrix in one huge diff. Start with scaffold, trace/exhaustiveness, and remove_node four-lane proof.

Issue Accounting

Current issue posture:

IssueClaimWhyProof needed
#5771Improves onlycore selection stress exists; real @slate/yjs adapter/browser proof missingpackage + Playwright selection rows before Fixes
#3715Relatedpackage/example work answers collaboration example pressure but exact docs/examples closure must wait for final docsexample route + docs
#3741Relatedmove_node moved-node payload/identity pressure remains realstable moved-subtree identity design before stronger claim
#4178Relatedadapter origins and commit metadata help source tracking, but operations still do not expose public source fieldsno fixed claim in this package
#5533RelatedYjs package does not provide non-Yjs collaboration protocolno fixed claim

No new fixed issue claim in this activation.

Implementation-Skill Review Matrix

LensStatusFindingPlan delta
vercel-react-best-practicesapplied by ruleReact cursor hooks must be external-store projection, not document statekeep awareness outside commits
performance-oracleapplied by ruleoperation encoding is a hot path; root snapshots create O(document) damagetrace and block silent root fallback
tddappliedfirst implementation slice starts with failing remove_node four-lane package testno broad horizontal test dump
testingappliedpackage tests must assert public behavior and Yjs updates, not private statepackage harness through public APIs
browser-usequeuedexample/browser surface requires Browser Use proof after implementationfinal closure cannot skip browser

High-Risk Pre-Mortem

  1. Silent fallback deletes live Yjs nodes and loses remote edits.
    • Guard: trace registry, throw unsupported operations, no supported row can root-snapshot.
  2. Undo passes convergence but deletes remote intent.
    • Guard: every operation row includes local undo/redo after concurrent remote edit.
  3. Example controls bypass real editor APIs and give fake browser confidence.
    • Guard: controls must call public Slate transforms or real browser paths.
  4. move_node overclaims.
    • Guard: current P1 is a narrow virtual wrapper move only; broader moved-subtree identity remains separate.

Fast Driver Gates

Run from cwd=/Users/felixfeng/Desktop/repos/slate-v2:

sh
bun test ./packages/slate-yjs/test
bun --filter @slate/yjs build
bun --filter @slate/yjs typecheck
bun typecheck:root
PLAYWRIGHT_BASE_URL=http://localhost:3100 PLAYWRIGHT_RETRIES=0 PLAYWRIGHT_WORKERS=1 bun playwright playwright/integration/examples/yjs-collaboration.test.ts --project=chromium
bun lint:fix
bun check

During iteration, use focused package tests first. Browser proof is mandatory once the example exists.

Pass-State Ledger

PassStatusEvidence addedPlan deltaOpen issuesNext owner
current-state readcompletelive ../slate-v2 package residue, staged docs/Playwright specs, external source reads, core collab testsplan reset to from-scratch package; May 28 "current source exists" claim marked stale for this checkoutnoneimplementation
related issue discoveryreusedexisting ledgers classify #5771, #3715, #3741, #4178, #5533no new fixed claimupdate after proofimplementation
implementation slice 1completebun test ./packages/slate-yjs/test/remove-node-contract.spec.ts -> 4 pass; bun --filter @slate/yjs typecheck -> pass; bun --filter @slate/yjs build -> pass; bun lint:fix -> passscaffolded @slate/yjs, added extension/controller/document/operation source, added remove_node four-scenario test, added changesetremaining operation matrix rowstask/implementation
implementation slice 2completebun test ./packages/slate-yjs/test -> 8 pass; bun --filter @slate/yjs typecheck -> pass; bun --filter @slate/yjs build -> pass; bun lint:fix -> passadded split_node operation encoder, made package tests Bun-discoverable, added four-lane public split package proof at the split boundary, preserved left Y.XmlText identity, no root snapshot tracestaged append-style split undo parity still open (alphabeta! concurrent append undo must converge to alph!abeta)task/implementation
implementation slice 3completebun test ./packages/slate-yjs/test -> 9 pass; bun --filter @slate/yjs typecheck -> pass; bun --filter @slate/yjs build -> pass; bun lint:fix -> passadded append-style split undo regression and semantic split undo/redo bridge backed by Yjs UndoManager stack metadata; no stale inverse Slate replay and no coarse snapshot restorenon-text split undo shapes still throw explicitly; merge history repair nexttask/implementation
implementation slice 4completebun test ./packages/slate-yjs/test -> 14 pass; bun --filter @slate/yjs typecheck -> pass; bun --filter @slate/yjs build -> pass; bun lint:fix -> passadded merge_node operation encoder for public block merges, four-lane merge package proof, and raw text merge traceable fallback to avoid unsafe same-offset Yjs reorderingtext merge fallback is explicit, not a full support claim; replace_fragment nexttask/implementation
implementation slice 5completebun test ./packages/slate-yjs/test -> 19 pass; bun --filter @slate/yjs typecheck -> pass; bun --filter @slate/yjs build -> pass; bun lint:fix -> passadded replace_fragment package proof for single-text diff, concurrent remote append, reconnect, undo/redo, plus traceable broad replacement fallbackbroad replace fallback is explicit; insert_fragment nexttask/implementation
implementation slice 6completebun test ./packages/slate-yjs/test -> 24 pass; bun --filter @slate/yjs typecheck -> pass; bun --filter @slate/yjs build -> pass; bun lint:fix -> passadded public insert_fragment operation-sequence characterization and four-lane parity proof through Slate's emitted insert_node + text merge_node fallback path; started shared test harness extractioncomposed wrap/unwrap/lift rows remaintask/implementation
implementation slice 7completebun test ./packages/slate-yjs/test -> 29 pass; bun --filter @slate/yjs typecheck -> pass; bun --filter @slate/yjs build -> pass; bun lint:fix -> pass after one formatting passadded public wrapNodes operation-sequence characterization and four-lane proof; introduced hidden source nodes plus virtual wrapper refs so wrapped paths read through the original Y.XmlElement and concurrent remote text survives reconnect/undo/redo; refreshed ../slate-v2/docs/solutions/logic-errors/yjs-structural-wrap-fragment-parity-2026-05-28.md with current attribute names and trace policyvirtual move is intentionally narrow to wrapper first-child moves; unwrap/lift rows remaintask/implementation
implementation slice 8completebun test ./packages/slate-yjs/test -> 34 pass; bun --filter @slate/yjs typecheck -> pass; bun --filter @slate/yjs build -> pass; bun lint:fix -> pass after one formatting passadded public unwrapNodes operation-sequence characterization and four-lane proof from a virtual-wrapped starting state; restored the hidden source node, deleted the hidden wrapper shell, and kept concurrent remote text through reconnect/undo/redo; refreshed ../slate-v2/docs/solutions/logic-errors/yjs-structural-wrap-fragment-parity-2026-05-28.md with unwrap trace policybroader move_node P1 and liftNodes rows remaintask/implementation
implementation slice 9completebun test ./packages/slate-yjs/test -> 39 pass; bun --filter @slate/yjs typecheck -> pass; bun --filter @slate/yjs build -> pass; bun lint:fix -> pass after one formatting passadded public move_node characterization, same-parent move four-lane proof, cross-parent local identity proof, and virtual move placeholders that preserve the original Y.XmlElement; refreshed ../slate-v2/docs/solutions/logic-errors/yjs-forward-move-history-fallback-2026-05-26.md away from clone+hide index guidancecross-parent move full lanes next; liftNodes row remainstask/implementation
implementation slice 10completebun test ./packages/slate-yjs/test -> 41 pass; bun --filter @slate/yjs typecheck -> pass; bun --filter @slate/yjs build -> pass; bun lint:fix -> passcompleted cross-parent move_node concurrent remote, reconnect, and undo/redo lanes on the same virtual placeholder pathliftNodes row remainstask/implementation
implementation slice 11completebun test ./packages/slate-yjs/test -> 46 pass; bun --filter @slate/yjs typecheck -> pass; bun --filter @slate/yjs build -> pass; bun lint:fix -> passadded first-child liftNodes operation-sequence characterization and four-lane proof on the generic virtual-move-placeholder pathonly-child and middle/last lift shapes still need explicit coveragetask/implementation
implementation slice 12completebun test ./packages/slate-yjs/test -> 54 pass; bun --filter @slate/yjs typecheck -> pass; bun --filter @slate/yjs build -> pass; bun lint:fix -> pass after one formatting passadded only-child, last-child, and middle-child liftNodes characterization/proof; parent-shell removal with hidden moved descendants now uses virtual-move-parent-remove instead of deleting the hidden sourcesimple op backfill remainstask/implementation
implementation slice 13completebun test ./packages/slate-yjs/test -> 70 pass; bun --filter @slate/yjs typecheck -> pass; bun --filter @slate/yjs build -> pass; bun lint:fix -> pass after one formatting pass, then no-op passbackfilled insert_text, remove_text, insert_node, set_node / unset_node, and replace_children; added text-mark set_node delta formatting and plain-text Yjs delta reads so formatted text does not import XML markupselection/awareness APIs and browser simulator remaintask/implementation
implementation slice 14completebun test ./packages/slate-yjs/test -> 76 pass; bun --filter @slate/yjs typecheck -> pass; bun --filter @slate/yjs build -> pass; bun lint:fix -> pass after one formatting pass, then no-op passadded public Slate point/range to Yjs relative-position conversion, visible-path resolution through virtual moved nodes, text-insert rebasing proof, moved-node selection proof, and removed-node null resolutionawareness APIs, React hooks, and browser simulator remaintask/implementation
implementation slice 15completebun install --lockfile-only; bun test ./packages/slate-yjs/test -> 85 pass; bun --filter @slate/yjs typecheck -> pass; bun --filter @slate/yjs build -> pass; bun lint:fix -> pass after one style fix pass, then no-op pass after docs captureadded awareness payload helpers, state.yjs.remoteCursor(s) / awarenessRevision / subscribeAwareness, tx.yjs.sendSelection / sendCursorData / clearSelection, local-selection auto-publish, fake-awareness package harness, @slate/yjs/react external-store hooks, remote-wrap selection sanitization, and refreshed docs/solutions/developer-experience/yjs-awareness-react-hooks-2026-05-29.md plus docs/solutions/ui-bugs/slate-react-structural-text-dom-sync-2026-05-28.mdbroader package hardening remainstask/implementation
implementation slice 16completered: bun test ./packages/slate-yjs/test/delete-fragment-contract.spec.ts exposed absorbed end-block text loss; green: bun test ./packages/slate-yjs/test -> 90 pass; bun --filter @slate/yjs typecheck -> pass; bun typecheck:site -> pass; bun --filter @slate/yjs build -> pass; bun lint:fix -> pass; PLAYWRIGHT_BASE_URL=http://localhost:3100 PLAYWRIGHT_RETRIES=0 PLAYWRIGHT_WORKERS=1 bun test:integration-local --project=chromium --grep yjs-collaboration -> 29 passadded delete_fragment package contract, cross-block public deleteFragment browser row, virtual-merge-ref element merge encoding, visible-child merge resolution, changeset note, and refreshed docs/solutions/logic-errors/yjs-backspace-merge-normalization-reconnect-2026-05-25.mdfull repo check still not claimedtask/implementation
implementation slice 17completebun test ./packages/slate-yjs/test/lift-nodes-contract.spec.ts -> 19 pass; bun test ./packages/slate-yjs/test -> 96 pass; bun --filter @slate/yjs typecheck -> pass through bun check; bun lint:fix -> passclosed the remaining explicit liftNodes shape gaps: only-child, last-child, and middle-child lifts now each have local-offline, concurrent remote, reconnect/recovery, and undo/redo coverage; only-child recovery now has its own real Yjs update rownoneclosure
build-fix passcompletefocused slate-react Vitest contracts -> 39 pass; bun install --lockfile-only; bun --filter slate-react typecheck; bun typecheck:site; bun --filter @slate/yjs typecheck; bun check -> passrepaired stale slate-react static inventories, aligned sibling peer floors to the live 0.124.1 packages, and changed the pagination stress control from whole-root tx.value.replace() to scoped remove/insert node transformsnoneclosure
browser proofcompletePLAYWRIGHT_BASE_URL=http://localhost:3100 PLAYWRIGHT_RETRIES=0 PLAYWRIGHT_WORKERS=1 bun test:integration-local --project=chromium --grep yjs-collaboration -> 29 pass; curl -I http://localhost:3100/examples/yjs-collaboration -> 200verified the four-peer simulator route, split button DOM remount path, operation controls, selection rows, cross-block deleteFragment reconnect/undo row, reconnect rows, undo/redo rows, move undo/redo rows, awareness selection, and expanded browser selection deletionnonetask/browser-use
closure gatescompletebun install --lockfile-only; bun lint:fix; bun --filter slate-react typecheck; bun --filter @slate/yjs typecheck; bun typecheck:site; bun --filter @slate/yjs build; bun test ./packages/slate-yjs/test -> 96 pass; bun check -> pass; focused Playwright -> 29 passfirst-party @slate/yjs package, operation matrix tests, simulator, Playwright coverage, package metadata, changeset, and reusable solution notes are all currentno fixed upstream issue claim madedone

Current Score

DimensionScoreEvidence
React/runtime performance0.86awareness external-store target, no doc commits for cursors, source-backed selection-side-effect-policy requirement
Slate-close unopinionated DX0.90extension/state/tx API, no provider/product policy
Plate and slate-yjs migration backbone0.88external slate-yjs, Lexical, y-prosemirror synthesis; no compatibility shims
Regression-proof testing strategy0.91four-lane operation matrix, public deleteFragment proof, and focused Playwright selection specs
Research evidence completeness0.91local source reads and compiled yjs-collaboration-bindings.md
shadcn-style composability / minimal hooks0.86example controls and React hooks target minimal external-store projection

Total: 0.90

Status is complete: the package, operation-matrix tests, simulator, Playwright selection proof, package metadata, changeset, and closure gates are current. No upstream issue is claimed fixed by this plan.

Closeout

Completed in ../slate-v2:

  1. @slate/yjs source package exists with operation encoders, controller, selection, awareness, React hooks, and package-local harnesses.
  2. Every operation matrix row is either supported, explicitly characterized through public transform operation sequences, or traceable fallback with named trace modes.
  3. Each supported operation family has local-offline, concurrent remote, reconnect/recovery, and local undo/redo coverage.
  4. The four-peer simulator route is registered and covered by focused Playwright selection/collaboration rows.
  5. Trace names stay precise: virtual-merge-ref for absorbed block merge refs, virtual-move-ref for wrapper-child refs, virtual-move-placeholder for generic moved-slot refs, and virtual-move-parent-remove for parent shells that must stay hidden to keep moved descendants alive.