docs/plans/2026-05-29-slate-yjs-from-scratch-operation-matrix.md
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.mddocs/plans/2026-05-13-yjs-collaboration-harvest.mddocs/plans/2026-05-18-slate-yjs-package-readiness-ralplan.mddocs/plans/2026-05-28-slate-yjs-current-architecture-operation-matrix.mdBuild 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/**packages/slate-yjs/package.jsonpackages/slate-yjs/src/**packages/slate-yjs/test/**site/examples/ts/yjs-collaboration.tsxThe 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: ship a first-party Yjs binding that makes collaboration correctness a protocol contract, not a demo accident.
Desired outcome:
@slate/yjs exists under packages/slate-yjsY.XmlText and Y.XmlElement
identities whenever possibleIn 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.tsNon-goals:
@slate-yjs/* wrappers#5771 until package/browser proof passesmove_node until node
identity is preserved or a logical identity layer is provenDecision boundaries:
@slate/yjs owns Yjs schema, binding lifecycle, operation encoding, remote
import, history/UndoManager bridge, selection/relative-position mapping,
awareness, trace policy, and example controlsLive ../slate-v2 facts read this activation:
| Surface | Evidence | Decision |
|---|---|---|
| package residue | find packages/slate-yjs -maxdepth 4 -print lists only dist, node_modules, .turbo | hard-cut and recreate source |
| operation union | packages/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_children | operation registry must be exhaustive |
| extension API | packages/slate/src/core/editor-extension.ts rejects register, commands, methods, operationMiddlewares, commitListeners; supports setup and onCommit | public API must be extension/state/tx based |
| core collab proof | packages/slate/test/collab-adapter-extension-contract.ts proves commit export, remote import, pause, skip-collab, cleanup without monkey-patches | package binding follows this shape |
| selection stress | packages/slate/test/collab-selection-stress-contract.ts proves high-QPS remote selection rebasing and remote-history skip | port these scenarios into package/browser proof |
| bookmarks | packages/slate/test/collab-bookmark-position-contract.ts proves split, merge, move, remove, replace_children bookmark rebasing | Y relative positions must match this semantics |
| canonical reconcile | packages/slate/test/collab-canonical-reconcile-contract.ts proves remote full replace can skip local history and side effects | allowed only as explicit traceable fallback/reconcile mode |
| example registry | site/constants/examples.ts has no yjs-collaboration entry | add route registration |
| staged previous specs | staged playwright/integration/examples/yjs-collaboration.test.ts covers four peers, selection, offline reconnect, undo/redo, split/merge/remove/move/wrap/fragment rows | preserve as browser acceptance source |
Staged ../slate-v2 source material inspected:
docs/plans/2026-05-25-yjs-demo-four-editors-marks.mddocs/plans/2026-05-25-yjs-offline-mixed-edits-reconnect.mddocs/plans/2026-05-26-yjs-history-split-fixes.mddocs/plans/2026-05-26-yjs-offline-mark-stale-undo.mddocs/plans/2026-05-26-yjs-offline-merge-undo-noop.mddocs/plans/2026-05-26-yjs-offline-move-undo-redo.mddocs/plans/2026-05-27-yjs-collaboration-soak.mddocs/plans/2026-05-27-yjs-merge-node-canonical-late-peer.mddocs/plans/2026-05-28-yjs-command-matrix-controls.mddocs/plans/2026-05-28-yjs-potion-parity-regressions.mddocs/plans/2026-05-28-yjs-public-split-api.mddocs/solutions/logic-errors/yjs-structural-wrap-fragment-parity-2026-05-28.mddocs/solutions/logic-errors/yjs-forward-move-history-fallback-2026-05-26.mddocs/solutions/logic-errors/yjs-offline-split-reconnect-merge-2026-05-25.mddocs/solutions/logic-errors/yjs-offline-merge-stale-undo-2026-05-26.mddocs/solutions/ui-bugs/slate-react-structural-text-dom-sync-2026-05-28.mdplaywright/integration/examples/yjs-collaboration.test.ts| System | Source | Mechanism | Avoids | Steal | Reject | Slate target | Verdict |
|---|---|---|---|---|---|---|---|
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 metadata | pure snapshot sync for every edit | operation mappers, relative-position conversion, origin discipline, Y.UndoManager metadata | mutating editor.children, overriding apply/onChange, wrapper-first public API | extension-owned controller and operation registry | partial |
| Lexical Yjs | ../lexical/packages/lexical-yjs/src/Bindings.ts, SyncV2.ts | binding object owns Y doc, node mapping, default property filtering, cursor state, and XmlElement/XmlText mapping | editor core depending on Yjs classes | binding/controller object, default-property exclusion, in-place mapping, cursor state outside doc model | Lexical class-node/node-key coupling | internal YjsController with maps hidden behind state.yjs/tx.yjs | partial |
| y-prosemirror | ../y-prosemirror/src/sync-plugin.js, undo-plugin.js | plugin state binds Y type, uses transaction metadata, stores undo selection through relative bookmarks, reconfigures/pause via plugin state | local undo deleting remote edits, stale selection after remote import | relative-selection undo restore, pause/reconfigure lifecycle, transaction metadata discipline | ProseMirror plugin surface and schema fitting as raw Slate API | tx.yjs.pause/resume/reconcile, UndoManager bridge, remote imports with history skip | agree |
| current Slate v2 | packages/slate/test/collab-*.ts, editor-extension.ts | extension setup/onCommit, transaction replay, skip-history metadata, bookmarks, runtime ids, canonical replace | adapter monkey-patches and remote side effects | state/tx namespaces, commit export, remote import through editor.update | adding Yjs to raw slate core | first-party package on current extension substrate | agree |
Keep the first public surface deliberately small:
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 helpersHard cuts:
withYjs(editor)editor.apply/editor.onChange patchingeditor.children = ...Target file layout:
| File | Owns |
|---|---|
src/index.ts | root public exports |
src/core/index.ts | stable core exports |
src/core/extension.ts | createYjsExtension and state/tx namespace registration |
src/core/controller.ts | lifecycle, local export, remote import, transaction/origin discipline |
src/core/document.ts | Slate value <-> Yjs document model |
src/core/operations.ts | operation encoder registry, exhaustiveness, trace policy |
src/core/history.ts | UndoManager bridge, local-intent undo/redo, history repair |
src/core/selection.ts | Slate point/range <-> Y.RelativePosition |
src/core/awareness.ts | awareness payloads and remote cursor projection |
src/core/testing.ts | package-local harness only |
src/react/index.tsx | external-store cursor hooks and optional render helpers |
src/internal/index.ts | unstable test/debug exports |
The controller is the package brain. index.ts must stay thin.
Every row needs four scenario lanes:
Y.encodeStateAsUpdate /
Y.applyUpdate| Surface | Decision | Identity rule | Fallback rule | Required proof |
|---|---|---|---|---|
insert_text | support | mutate existing Y.XmlText | no snapshot | 4 lanes plus unicode/mark metadata |
remove_text | support | mutate existing Y.XmlText | no snapshot | 4 lanes plus concurrent same-offset inserts |
insert_node | support | insert new Y.XmlElement/Y.XmlText at parent slot | no root snapshot | 4 lanes nested and top-level |
remove_node | support | hide/remove one target node, preserve siblings | no root snapshot | first red test slice |
split_node | support | keep original left shared type, create right sibling | no root snapshot | public tx.nodes.split({ at: Point }) and keyboard Enter |
merge_node | support | preserve survivor shared type; absorbed children stay live through virtual merge refs | no root snapshot; trace virtual merge as virtual-merge-ref | keyboard Backspace, tx merge, and cross-block delete |
move_node | P1 support, P2 gated | P1 clone+hide only for outside-subtree convergence; P2 requires stable moved identity | explicit trace.mode: "clone-hide-move" until P2 | 4 lanes plus documented limitation |
set_node | support | set attributes/leaf metadata in place | no snapshot | element props, text marks |
unset_node | transform characterization | lower to set_node clearing props | no snapshot | exact emitted op sequence then 4 lanes |
replace_children | support | scoped child-window edit; preserve unaffected children | explicit reconcile only for dev/test trace | 4 lanes |
replace_fragment | support | scoped fragment replace under operation path | no root snapshot | 4 lanes |
insert_fragment | transform characterization | lower to existing operation rows | no hidden private mutation | exact emitted op sequence then 4 lanes |
delete_fragment | transform/browser characterization | lower to existing operation rows; preserve absorbed end-block text identity through virtual merge refs | no hidden private mutation | browser selection delete plus op sequence |
wrapNodes / tx.nodes.wrap | support through op sequence | preserve original child identity through virtual wrapper/source ref | no clone-only if concurrent inner edits matter | 4 lanes |
unwrapNodes / tx.nodes.unwrap | support through op sequence | restore original child identity | no root snapshot | 4 lanes |
liftNodes / tx.nodes.lift | support through op sequence | move scoped child without root rewrite | named clone-hide trace if needed | 4 lanes |
Completion rule: no matrix row can be silent. It is either supported, explicitly unsupported with thrown error, or traceable fallback with tests.
Undo is protocol, not polish.
Rules:
Y.UndoManager tracked originsUse staged solution notes as acceptance cases:
File: ../slate-v2/site/examples/ts/yjs-collaboration.tsx
Requirements:
clientID / displayed peer id mapping for testsThe staged Playwright file expects data-test-id attributes such as:
yjs-peer-a-mark-boldyjs-peer-a-selectyjs-peer-a-disconnectyjs-peer-a-connectyjs-peer-a-split-nodeyjs-peer-a-wrap-nodeyjs-peer-a-insert-fragmentyjs-peer-a-moveAdd package-local tests under ../slate-v2/packages/slate-yjs/test/.
Harness responsibilities:
Y.Doc instancesY.encodeStateAsUpdate / Y.applyUpdateDo not test by comparing only local Slate values. That is how broken snapshot fallback looked correct.
@slate/yjs.
package.json, tsconfig, tsdown config, source entries, test folder.yjs dependency.createYjsExtension, state.yjs, tx.yjs.Y.XmlElement for elements.Y.XmlText with leaf metadata.remove_node.
split_nodemerge_nodeinsert_fragmentwrapNodesunwrapNodesmove_node P1insert_textremove_textinsert_nodeset_node / unset_nodereplace_childrenDo not implement the whole matrix in one huge diff. Start with scaffold,
trace/exhaustiveness, and remove_node four-lane proof.
Current issue posture:
| Issue | Claim | Why | Proof needed |
|---|---|---|---|
#5771 | Improves only | core selection stress exists; real @slate/yjs adapter/browser proof missing | package + Playwright selection rows before Fixes |
#3715 | Related | package/example work answers collaboration example pressure but exact docs/examples closure must wait for final docs | example route + docs |
#3741 | Related | move_node moved-node payload/identity pressure remains real | stable moved-subtree identity design before stronger claim |
#4178 | Related | adapter origins and commit metadata help source tracking, but operations still do not expose public source fields | no fixed claim in this package |
#5533 | Related | Yjs package does not provide non-Yjs collaboration protocol | no fixed claim |
No new fixed issue claim in this activation.
| Lens | Status | Finding | Plan delta |
|---|---|---|---|
vercel-react-best-practices | applied by rule | React cursor hooks must be external-store projection, not document state | keep awareness outside commits |
performance-oracle | applied by rule | operation encoding is a hot path; root snapshots create O(document) damage | trace and block silent root fallback |
tdd | applied | first implementation slice starts with failing remove_node four-lane package test | no broad horizontal test dump |
testing | applied | package tests must assert public behavior and Yjs updates, not private state | package harness through public APIs |
browser-use | queued | example/browser surface requires Browser Use proof after implementation | final closure cannot skip browser |
move_node overclaims.
Run from cwd=/Users/felixfeng/Desktop/repos/slate-v2:
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 | Status | Evidence added | Plan delta | Open issues | Next owner |
|---|---|---|---|---|---|
| current-state read | complete | live ../slate-v2 package residue, staged docs/Playwright specs, external source reads, core collab tests | plan reset to from-scratch package; May 28 "current source exists" claim marked stale for this checkout | none | implementation |
| related issue discovery | reused | existing ledgers classify #5771, #3715, #3741, #4178, #5533 | no new fixed claim | update after proof | implementation |
| implementation slice 1 | complete | bun 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 -> pass | scaffolded @slate/yjs, added extension/controller/document/operation source, added remove_node four-scenario test, added changeset | remaining operation matrix rows | task/implementation |
| implementation slice 2 | complete | bun test ./packages/slate-yjs/test -> 8 pass; bun --filter @slate/yjs typecheck -> pass; bun --filter @slate/yjs build -> pass; bun lint:fix -> pass | added 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 trace | staged append-style split undo parity still open (alphabeta! concurrent append undo must converge to alph!abeta) | task/implementation |
| implementation slice 3 | complete | bun test ./packages/slate-yjs/test -> 9 pass; bun --filter @slate/yjs typecheck -> pass; bun --filter @slate/yjs build -> pass; bun lint:fix -> pass | added 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 restore | non-text split undo shapes still throw explicitly; merge history repair next | task/implementation |
| implementation slice 4 | complete | bun test ./packages/slate-yjs/test -> 14 pass; bun --filter @slate/yjs typecheck -> pass; bun --filter @slate/yjs build -> pass; bun lint:fix -> pass | added 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 reordering | text merge fallback is explicit, not a full support claim; replace_fragment next | task/implementation |
| implementation slice 5 | complete | bun test ./packages/slate-yjs/test -> 19 pass; bun --filter @slate/yjs typecheck -> pass; bun --filter @slate/yjs build -> pass; bun lint:fix -> pass | added replace_fragment package proof for single-text diff, concurrent remote append, reconnect, undo/redo, plus traceable broad replacement fallback | broad replace fallback is explicit; insert_fragment next | task/implementation |
| implementation slice 6 | complete | bun test ./packages/slate-yjs/test -> 24 pass; bun --filter @slate/yjs typecheck -> pass; bun --filter @slate/yjs build -> pass; bun lint:fix -> pass | added 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 extraction | composed wrap/unwrap/lift rows remain | task/implementation |
| implementation slice 7 | complete | bun 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 pass | added 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 policy | virtual move is intentionally narrow to wrapper first-child moves; unwrap/lift rows remain | task/implementation |
| implementation slice 8 | complete | bun 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 pass | added 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 policy | broader move_node P1 and liftNodes rows remain | task/implementation |
| implementation slice 9 | complete | bun 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 pass | added 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 guidance | cross-parent move full lanes next; liftNodes row remains | task/implementation |
| implementation slice 10 | complete | bun test ./packages/slate-yjs/test -> 41 pass; bun --filter @slate/yjs typecheck -> pass; bun --filter @slate/yjs build -> pass; bun lint:fix -> pass | completed cross-parent move_node concurrent remote, reconnect, and undo/redo lanes on the same virtual placeholder path | liftNodes row remains | task/implementation |
| implementation slice 11 | complete | bun test ./packages/slate-yjs/test -> 46 pass; bun --filter @slate/yjs typecheck -> pass; bun --filter @slate/yjs build -> pass; bun lint:fix -> pass | added first-child liftNodes operation-sequence characterization and four-lane proof on the generic virtual-move-placeholder path | only-child and middle/last lift shapes still need explicit coverage | task/implementation |
| implementation slice 12 | complete | bun 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 pass | added 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 source | simple op backfill remains | task/implementation |
| implementation slice 13 | complete | bun 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 pass | backfilled 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 markup | selection/awareness APIs and browser simulator remain | task/implementation |
| implementation slice 14 | complete | bun 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 pass | added 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 resolution | awareness APIs, React hooks, and browser simulator remain | task/implementation |
| implementation slice 15 | complete | bun 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 capture | added 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.md | broader package hardening remains | task/implementation |
| implementation slice 16 | complete | red: 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 pass | added 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.md | full repo check still not claimed | task/implementation |
| implementation slice 17 | complete | bun 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 -> pass | closed 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 row | none | closure |
| build-fix pass | complete | focused 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 -> pass | repaired 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 transforms | none | closure |
| browser proof | complete | PLAYWRIGHT_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 -> 200 | verified 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 deletion | none | task/browser-use |
| closure gates | complete | bun 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 pass | first-party @slate/yjs package, operation matrix tests, simulator, Playwright coverage, package metadata, changeset, and reusable solution notes are all current | no fixed upstream issue claim made | done |
| Dimension | Score | Evidence |
|---|---|---|
| React/runtime performance | 0.86 | awareness external-store target, no doc commits for cursors, source-backed selection-side-effect-policy requirement |
| Slate-close unopinionated DX | 0.90 | extension/state/tx API, no provider/product policy |
| Plate and slate-yjs migration backbone | 0.88 | external slate-yjs, Lexical, y-prosemirror synthesis; no compatibility shims |
| Regression-proof testing strategy | 0.91 | four-lane operation matrix, public deleteFragment proof, and focused Playwright selection specs |
| Research evidence completeness | 0.91 | local source reads and compiled yjs-collaboration-bindings.md |
| shadcn-style composability / minimal hooks | 0.86 | example 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.
Completed in ../slate-v2:
@slate/yjs source package exists with operation encoders, controller,
selection, awareness, React hooks, and package-local harnesses.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.