Back to Plate

Slate Yjs Package Readiness Ralplan

docs/plans/2026-05-18-slate-yjs-package-readiness-ralplan.md

53.0.888.8 KB
Original Source

Slate Yjs Package Readiness Ralplan

Sync note, 2026-05-24: current ../slate-v2 still has no source packages/slate-yjs package, no source yjs-collaboration example, and no package Playwright suite. The core extension API remains setup(...) / onCommit(...), and the collab-readiness benchmark is no longer stale: it now uses history() plus setup(...) / onCommit(...) and passes as a calibration benchmark. The remaining execution gates are package source, full simulation example, package tests, Playwright selection proof, and final public package identity.

Sync note, 2026-05-28: stale for package existence. Live ../slate-v2 has @slate/yjs source, package metadata, a core contract test, an example, and Playwright rows. Current planning target: docs/plans/2026-05-28-slate-yjs-current-architecture-operation-matrix.md.

Date: 2026-05-18 Status: pending user review / Ralph implementation Owner skill: .agents/skills/slate-ralplan/SKILL.md Previous plans:

  • docs/plans/2026-05-13-slate-v2-yjs-core-readiness-ralplan.md
  • docs/plans/2026-05-13-yjs-collaboration-harvest.md

Institutional learnings checked:

  • docs/solutions/developer-experience/2026-05-13-slate-v2-yjs-readiness-needs-core-contracts-before-package-work.md
  • docs/solutions/documentation-gaps/2026-04-09-slate-collaboration-docs-must-mark-the-external-adapter-boundary.md
  • docs/solutions/test-failures/2026-03-22-yjs-slow-tests-need-explicit-bun-paths-and-bootstrapped-shared-types.md
  • docs/solutions/performance-issues/2026-05-08-dom-selection-bridges-must-stay-cheap-on-selectionchange.md

Current Verdict

Create the first-party slate-yjs package, but do it as a clean source package. Do not patch the current ../slate-v2/packages/slate-yjs folder as if it were real source. It is build residue: dist/, empty src/ and test/ directories, no package.json, and no current source package contract.

The 2026-05-13 core-readiness plan is superseded for package timing. Its original "do not create slate-yjs yet" verdict is stale because the core readiness contracts now exist and pass focused tests. Its API examples are also partly stale: live Slate v2 rejects extension register and commitListeners; the current adapter contract uses setup(...) and onCommit(...).

Strong call: hard-cut the residue and recreate packages/slate-yjs as a first-party package folder with ./core, ./react, and ./internal subpaths. Do not assume the public npm name is slate-yjs: npm currently has [email protected] published from BitPhinix/slate-yjs. The preferred public import path is @slate/yjs if the Slate npm scope can publish it; otherwise the implementation must keep the package private or revise the import path before docs ship. The package must speak current Slate v2:

  • mount with editor.extend(createYjsExtension(...))
  • observe local commits through extension onCommit
  • import remote changes through editor.update((tx) => ...)
  • expose state/tx namespace helpers, not editor monkey-patches
  • keep provider, awareness, cursors, and selection state adapter-owned
  • prove selection bugs in Playwright, not just Node tests

Intent And Boundary Record

Intent: turn the old Yjs plan and harvest into a current, executable package plan after several core APIs changed.

Desired outcome: ../slate-v2 has a real slate-yjs workspace package, docs, a full simulation example, unit coverage comparable to the reference packages, and Playwright coverage for selection/collaboration bugs.

In scope:

  • ../slate-v2/packages/slate-yjs source package shape
  • slate-yjs public/core/react/internal exports
  • Yjs document binding, operation conversion, canonical reconcile, relative position conversion, undo/history, awareness, remote cursors
  • full example with controls to simulate peers, network lifecycle, remote updates, undo/redo, cursor/selection behavior, and failure cases
  • Playwright tests for selection and cross-editor browser regressions
  • docs and current PR/reference accounting in later passes

Non-goals:

  • no Plate-specific API inside raw Slate
  • no current external @slate-yjs/* compatibility promise
  • no first-party OT protocol or Yjs-free collaboration package in this slice
  • no provider hosting story; use in-memory/local test providers for proof
  • no browser/mobile claim without matching Playwright/device evidence

Decision boundaries:

  • Raw Slate owns document values, operations, transactions, commits, metadata, history hooks, bookmarks/range refs, and runtime ids.
  • slate-yjs owns Yjs schema, shared root lifecycle, Y event translation, relative-position mapping, undo-manager integration, awareness, cursor state, loop suppression, and adapter lifecycle.
  • React owns rendering and external-store projection.
  • Plate owns product collaboration UI, comments, suggestions, permissions, and app/provider policy.

Acceptance boundary:

  • The first public package must include a working binding, simulation example, package tests, and Playwright selection proof before it is called ready.
  • A provider component is not required for readiness. Provider policy can stay inline in the example unless a thin provider-agnostic component proves its value.
  • Undo/redo is a required behavior lane, but public tx.yjs.undo() / tx.yjs.redo() commands ship only if relative-selection restoration is proven. Do not publish commands that mostly work but corrupt selection.
  • Origin/source metadata belongs in commit metadata and adapter origins. This plan does not add a durable public source field to every Slate operation.

Unresolved user-decision points: none for planning. The package implementation may choose clean architecture over compatibility shims, and that includes breaking from external @slate-yjs/* wrapper naming.

Decision Brief

Principles:

  • Current Slate v2 source wins over old plans and external adapters.
  • First-party package code must not revive legacy mutable editor extension hooks.
  • Collaboration correctness is a state-machine and browser-selection problem, not just a serializer problem.
  • Provider/awareness policy belongs in the adapter, not raw Slate.
  • Test coverage should track the reference packages' behavior breadth, then add Slate-specific browser proof where those packages are blind.

Top drivers:

  • live Slate v2 extension API changed to setup / onCommit
  • live package folder is not a source package
  • #5771-class selection bugs need real adapter/browser proof before Fixes language
  • old external slate-yjs has valuable behavior fixtures but the architecture is the wrong shape for v2

Viable options:

OptionProsConsVerdict
Recreate first-party package around current Slate extension/state/tx APIsclean v2 shape, no monkey-patches, best migration backbonerequires more package/test workchosen
Port external slate-yjs wrappers mostly as-isfastest apparent API familiaritymutates apply, onChange, and children; fights v2rejected
Keep only raw collaboration substrate docs, no packageavoids package riskdoes not satisfy user goal or real ecosystem needrejected
Put Yjs concepts in raw slate coreconvenient accessmakes core Yjs-opinionated and bloats non-Yjs usersrejected
Expose a public binding/controller object firstmirrors Lexical's binding shapeprematurely freezes internals before Slate package tests prove the lifecyclerejected for first API; keep binding internal
Ship provider UI/component as part of readinesslooks complete to app authorsdrags room/auth/provider policy into raw packagerejected unless it stays thin and provider-agnostic

Consequences:

  • Package public API should prefer extension factories and helper functions over withYjs(...) editor wrappers.
  • The old withYjs, withYHistory, and withCursors names can inspire docs but should not force the v2 API.
  • The internal binding/controller should exist, but public users should meet it through createYjsExtension(...), state.yjs, tx.yjs, and pure conversion helpers first.
  • Full package readiness needs both Node/package tests and Playwright tests.
  • The benchmark lane is current and useful as core calibration, but it is not package/browser proof and still cannot carry a production performance claim.

Follow-ups:

  • related-issue discovery pass
  • issue-ledger sync pass after claims are finalized
  • separate Ralph implementation after user accepts this plan

Intent-Boundary And Decision-Brief Pressure Pass

Pass date: 2026-05-18 Status: complete Skill: .agents/skills/intent-boundary-pass/SKILL.md

Evidence used:

  • current intent/boundary record in this plan
  • current decision brief in this plan
  • live source evidence table above
  • related issue discovery and issue-ledger passes

Pressure result:

SurfaceWeak interpretationFinal boundary
"new slate-yjs package"copy external @slate-yjs/core / @slate-yjs/react wrappers or claim the occupied npm nameone first-party packages/slate-yjs package folder with current Slate extension/state/tx APIs; public npm name resolves before docs
"full example"docs-style page or happy-path two editorssimulation tool surface with peer controls, event log, failure controls, selection JSON, and Playwright assertions
"provider support"first-party room/auth/provider abstractionpackage accepts Yjs doc/shared root/awareness; provider hosting policy stays out
"undo/redo controls"publish commands before selection restore is provenundo/redo is required proof, but public commands ship only with relative-selection tests
"source metadata"add source fields to every Slate operationuse commit metadata and adapter origins; keep #4178 as related/non-closure
"migration support"support current Plate or current external slate-yjs public APIsprovide migration backbone and docs mapping, not compatibility shims

No user question is needed. The repo and user request already answer the only real ambiguity: clean rearchitecture is allowed, so the plan should optimize for current Slate v2 correctness over wrapper compatibility.

Current Live Source Evidence

SurfaceCurrent ownerEvidencePlan impact
slate-yjs package../slate-v2/packages/slate-yjsls -la shows only dist/, .turbo, node_modules, empty src/, empty test; test -f packages/slate-yjs/package.json failedrecreate source package; do not patch residue
examples../slate-v2/site/pages/examples/[example].tsx:14-51, ../slate-v2/site/constants/examples.ts:1-31no yjs-collaboration importer or example list entryadd example route registration and source
docs../slate-v2/docs/walkthroughs/07-enabling-collaborative-editing.mddocs describe raw adapter substrate, not a package recipeadd package docs after package API exists
extension API../slate-v2/packages/slate/src/core/editor-extension.ts:162-203, :205-233, :566-581live code rejects register, commitListeners, and setup-output commitListeners; supports onCommitold plan examples must be rewritten
state/tx namespaces../slate-v2/packages/slate/test/extension-namespaces-contract.ts:28-70, :72-127extension groups install on state/tx without mutating editor objectslate-yjs should expose state/tx groups
document replace../slate-v2/packages/slate/test/state-tx-public-api-contract.ts:93-122tx.value.replace(...) exists and commits full-document replacecanonical Y snapshot reconcile should use it
current collab core proof../slate-v2/packages/slate/test/collab-adapter-extension-contract.ts:65-145, :171-232fake adapter uses setup, runtime state, onCommit, remote replay, loop suppression, cleanuppackage should follow this shape
selection stress../slate-v2/packages/slate/test/collab-selection-stress-contract.ts:99-220high-QPS remote prefix, same-offset, suffix, split/merge, removal, history skip pass in focused testspackage/browser tests must reuse these scenarios
bookmarks../slate-v2/packages/slate/test/collab-bookmark-position-contract.ts:79-254bookmarks rebase/null through remote text, split, merge, move, replace_childrenY relative positions should map to these semantics
canonical reconcile../slate-v2/packages/slate/test/collab-canonical-reconcile-contract.ts:51-150remote canonical replace publishes one commit, skips history, preserves policypackage needs incremental import plus canonical fallback
React side effects../slate-v2/packages/slate-react/test/selection-side-effect-policy-contract.ts:13-97remote selection metadata can suppress scroll/focus while still syncing selectionadapter remote imports must carry this metadata
collab benchmark../slate-v2/scripts/benchmarks/core/current/collab-readiness.mjs:4-10, :112-145, :272-283current file imports history as historyExtension, mounts fake collaboration through setup(...), observes local commits through onCommit(...), and passes bun run bench:core:collab-readiness:localkeep as core calibration proof; still require real package and browser proof before performance or issue claims

Fresh command evidence:

  • bun test ./packages/slate/test/collab-adapter-extension-contract.ts ./packages/slate/test/collab-selection-stress-contract.ts ./packages/slate/test/collab-bookmark-position-contract.ts ./packages/slate/test/collab-canonical-reconcile-contract.ts ./packages/slate-react/test/selection-side-effect-policy-contract.ts from ../slate-v2: 16 pass, 0 fail.
  • bun run bench:core:collab-readiness:local from ../slate-v2 on 2026-05-24: passed. It emitted normal, large, stress, and pathological cohort calibration rows, and all red flags were false.
  • test -f packages/slate-yjs/package.json from ../slate-v2: failed.
  • find packages/slate-yjs/src packages/slate-yjs/test -type f -maxdepth 5 from ../slate-v2: no source/test files.
  • bun run completion-check -- --id 019e3967-668f-7f20-89e9-c6d3be500b9a from plate-copy after passes 1-8: correctly kept the staged Ralplan loop open while later scheduled passes remained.
  • npm view slate-yjs name version description repository --json from plate-copy: [email protected] exists and points at BitPhinix/slate-yjs.
  • npm view @slate/yjs name version description repository --json from plate-copy: returned npm E404; candidate name is not published but still needs Slate scope/publish access proof.
  • find ../slate-yjs -maxdepth 3 -name package.json ... from plate-copy: external repo root is slate-yjs, with workspaces @slate-yjs/core and @slate-yjs/react.
  • sed -n '1,180p' packages/yjs/package.json from plate-copy: Plate package is @platejs/yjs and depends on @slate-yjs/core.
  • bun run completion-check -- --id 019e3967-668f-7f20-89e9-c6d3be500b9a from plate-copy after passes 9-11: correctly kept the staged Ralplan loop open while later scheduled passes remained.
  • bun run completion-check -- --id 019e3967-668f-7f20-89e9-c6d3be500b9a from plate-copy after pass 12: passed; the Slate Ralplan planning lane is complete.

Goal-continuation audit, 2026-05-18:

  • bun test ./packages/slate/test/collab-adapter-extension-contract.ts ./packages/slate/test/collab-selection-stress-contract.ts ./packages/slate/test/collab-bookmark-position-contract.ts ./packages/slate/test/collab-canonical-reconcile-contract.ts ./packages/slate-react/test/selection-side-effect-policy-contract.ts from ../slate-v2: 16 pass, 0 fail.
  • bun run bench:core:collab-readiness:local from ../slate-v2 on 2026-05-24: passes after the live benchmark moved to history as historyExtension plus setup(...) / onCommit(...).
  • test -f packages/slate-yjs/package.json from ../slate-v2: missing.
  • find packages/slate-yjs/src packages/slate-yjs/test -maxdepth 5 -type f from ../slate-v2: 0 source/test files.
  • npm view slate-yjs name version description repository --json from plate-copy: [email protected] still points at BitPhinix/slate-yjs.
  • npm view @slate/yjs name version description repository --json from plate-copy: still returns npm E404.
  • Local reference package identities remain @slate-yjs/[email protected], @slate-yjs/[email protected], @lexical/[email protected], @y/[email protected], and @platejs/[email protected].
  • Current Slate v2 extension API still rejects commitListeners and register, and accepts setup / onCommit as the collaboration adapter shape.
  • Current source site/constants/examples.ts and site/pages/examples/[example].tsx still have no yjs-collaboration source example entry. The generated site/out route is stale build output, not source evidence.

2026-05-24 Current-State Sync Pass

Status: complete for this activation. Overall plan status remains pending for Ralph implementation.

Fresh source facts:

  • ../slate-v2/packages/slate-yjs: still no package.json, no source files, and no test files. Current files are generated/build residue under dist/, .turbo, and package-local node_modules.
  • ../slate-v2/packages/slate/src/core/editor-extension.ts:179-248, :575-584: current extension API still rejects methods, commands, operationMiddlewares, commitListeners, and register; onCommit is the current commit-observation slot.
  • ../slate-v2/scripts/benchmarks/core/current/collab-readiness.mjs:1-10, :112-145, :272-283: benchmark now imports history as historyExtension, uses setup(...), uses onCommit(...), and reads history through state.history.get().
  • ../slate-v2/site/constants/examples.ts:11-43 and ../slate-v2/site/pages/examples/[example].tsx:14-51: no source yjs-collaboration example registration.
  • ../slate-v2/site/out/** contains generated yjs-collaboration output, but generated output is not source evidence and should not be used as proof that the example exists.

Fresh command evidence:

  • cwd: /Users/felixfeng/Desktop/repos/slate-v2
  • bun test ./packages/slate/test/collab-adapter-extension-contract.ts ./packages/slate/test/collab-selection-stress-contract.ts ./packages/slate/test/collab-bookmark-position-contract.ts ./packages/slate/test/collab-canonical-reconcile-contract.ts ./packages/slate-react/test/selection-side-effect-policy-contract.ts: 16 pass, 0 fail.
  • bun run bench:core:collab-readiness:local: passed and emitted normal, large, stress, and pathological cohort rows with all red flags false.
  • test -f packages/slate-yjs/package.json: failed; package source still absent.
  • find packages/slate-yjs -maxdepth 3 -type f: only generated/build residue files under dist/ and .turbo.

Plan delta:

  • keep the first-party package verdict;
  • keep @slate/yjs as preferred public identity pending publish access;
  • remove the stale benchmark-repair blocker from execution gates;
  • keep benchmark proof classified as core calibration only;
  • keep package source, full simulation example, package tests, Playwright selection proof, and public package identity as the next execution gates.
  • pr-description updated: issue coverage summary now records the 2026-05-24 benchmark refresh and keeps issue claims unchanged.
  • issue coverage matrix unchanged: no fixed/improved/related claim changed.

Next owner:

  • ralph implementation should start with package scaffold and source-first package tests, then example controls, then Playwright selection proof.

Ecosystem Strategy Synthesis

Pass 5 compiled the reusable source summary: docs/research/sources/editor-architecture/yjs-collaboration-bindings.md. The table below keeps row-level live anchors, but that page is the research entrypoint for future slate-yjs package work.

SystemSourceMechanismAvoidsStealRejectSlate targetVerdict
Slate v2 corecollab-adapter-extension-contract.ts; editor-extension.tsextension setup owns runtime state, onCommit observes commits, remote import goes through tx.operations.replayeditor monkey-patching and re-export loopsadapter extension factory, runtime state, onCommit filteringregister, commitListeners, editor.apply, editor.onChange hookscreateYjsExtension(options) mounted by editor.extend(...)agree
Slate v2 state/txextension-namespaces-contract.ts; state-tx-public-api-contract.tsextension state/tx groups expose capability without editor mutationleaking package methods onto editor objectstate.yjs.*, tx.yjs.* for connect/pause/reconcile/inspectflat editor.yjs or wrapper-only APInamespace helpers plus standalone pure convertersagree
External slate-yjs../slate-yjs/packages/core/src/plugins/withYjs.ts:156-283; withYHistory.ts:58-182; withCursors.ts:160-269; utils/position.ts:10-80editor wrapper observes Y events, stores local changes, converts relative positions, tracks undo/cursorsmany Yjs binding details already solvedY.XmlText representation, op fixture breadth, relative position helpers, grouped origins, undo metadata, awareness hooksdirect children assignment, apply/onChange override, Transforms.select restoration, wrapper API as core shapetranslate into extension-owned binding/controller and testspartial
Lexical Yjs../lexical/packages/lexical-yjs/src/Bindings.ts:25-127; SyncEditorStates.ts:134-174; index.ts:90-150; SyncCursors.ts:168-310binding object owns editor/doc/provider/mapping/cursors; Y events precompute delta; collaboration tags suppress scroll; cursor DOM owned outside modelmixing CRDT state into editor values and broad React rerendersbinding object shape, precomputed YEvent delta, explicit update tags, ensure-non-empty repair, cursor lifecycle cleanupclass-node coupling and DOM-owned cursor rendering as raw Slate corepackage-local binding state and React external-store cursor projectionsagree
y-prosemirror../y-prosemirror/src/commands.js:12-66; undo-plugin.js:23-67, :98-227; cursor-plugin.js:95-152, :169-296sync can pause/reconfigure Y type, canonical replace from Y content, undo selection bookmarks use relative positions, awareness identity comes from awareness docstale cursor identity, undo cursor jumps, history pollutionpause/reconfigure lifecycle, relative-selection undo metadata, awareness-doc identity, canonical replace fallbackProseMirror transaction/plugin complexity and integer positionstx.yjs.pause/resume/connect, Y relative positions mapped to Slate bookmarksagree
Yjsharvest report and test indexrelative positions, text/delta, updates, snapshots, UndoManager, XML/text containers have deep fixture coverageCRDT edge cases missed by Slate-only testsselected portable rows for relative positions, delta, undo, snapshots, updateslow-level ID/encoding internals as Slate package testspackage unit tests backed by selected Yjs fixturespartial
React 19.2docs/research/sources/editor-architecture/react-19-2-external-store-and-background-ui.mduseSyncExternalStore is the subscription primitive; Activity/deferred work do not replace core invalidationcursor/awareness rerender breadthexternal-store cursor hooks with selectorsstuffing transient cursor state into editor render props@slate/yjs/react selector hooksagree
Tiptapdocs/research/sources/editor-architecture/tiptap-extension-command-react-dx.mdproduct DX packages extension config, commands, UI, React selectorsscattered adapter setupreadable extension entrypoint and example call sitechain-first product API as raw core requirementsimple extension factory plus explicit controls examplepartial

Research/Ecosystem/Live-Source Refresh Pass

Pass date: 2026-05-18 Status: complete Mode: research-wiki maintain over the editor-architecture lane.

Source refresh:

  • ../slate-v2/packages/slate/src/core/editor-extension.ts:162-233, :566-581
  • ../slate-v2/packages/slate/test/collab-adapter-extension-contract.ts:65-145, :171-232
  • ../slate-v2/packages/slate/test/collab-selection-stress-contract.ts:99-220
  • ../slate-v2/packages/slate/test/collab-bookmark-position-contract.ts:79-254
  • ../slate-v2/packages/slate/test/collab-canonical-reconcile-contract.ts:51-150
  • ../slate-v2/packages/slate-react/test/selection-side-effect-policy-contract.ts:13-97
  • ../slate-yjs/packages/core/src/plugins/withYjs.ts:156-283
  • ../slate-yjs/packages/core/src/plugins/withYHistory.ts:58-182
  • ../slate-yjs/packages/core/src/plugins/withCursors.ts:160-269
  • ../slate-yjs/packages/core/src/utils/position.ts:10-80
  • ../lexical/packages/lexical-yjs/src/Bindings.ts:25-127
  • ../lexical/packages/lexical-yjs/src/SyncEditorStates.ts:134-174
  • ../lexical/packages/lexical-yjs/src/index.ts:90-150
  • ../lexical/packages/lexical-yjs/src/SyncCursors.ts:168-325
  • ../y-prosemirror/src/commands.js:12-66
  • ../y-prosemirror/src/undo-plugin.js:23-227
  • ../y-prosemirror/src/cursor-plugin.js:95-296

Compiled research edits:

  • Added docs/research/sources/editor-architecture/yjs-collaboration-bindings.md.
  • Linked it from docs/research/sources/editor-architecture/README.md.
  • Linked it from docs/research/index.md.
  • Appended a 2026-05-18 maintain entry to docs/research/log.md.

Disposition:

CorpusStatusPlan result
Current Slate v2 sourceevidencedcurrent setup/onCommit and state/tx APIs remain the package target
Current packages/slate-yjs foldersource package gaphard-cut residue and recreate the package
External slate-yjsevidenced, mechanism-onlysteal conversion, origin, undo, awareness, and relative-position mechanics; reject wrapper mutation
Lexical Yjsevidencedsteal binding-object ownership, delta precompute, update tags, cursor cleanup
y-prosemirrorevidencedsteal pause/reconfigure, canonical replace fallback, relative-selection undo, awareness identity
Research layercompile gap closedfuture agents should read the new source page before rebuilding this synthesis

Pass conclusion: the architecture call is stronger, not different. The package should be a clean Slate v2 extension/state/tx binding. No current evidence rescues the old wrapper API, and no implementation readiness claim is allowed until the package and Playwright gates exist. The core benchmark exists and passes as calibration, but it does not prove the package adapter or browser selection behavior.

Public API Target

Package identity:

txt
folder: packages/slate-yjs
preferred package name: @slate/yjs
fallback: private workspace package until publish identity is resolved
blocked name: slate-yjs

Package subpaths, using the preferred public package name:

txt
@slate/yjs
@slate/yjs/core
@slate/yjs/react
@slate/yjs/internal

Core API target:

ts
import * as Y from 'yjs'
import { createEditor } from 'slate'
import { createYjsExtension } from '@slate/yjs/core'

const doc = new Y.Doc()
const sharedRoot = doc.get('content', Y.XmlText)
const editor = createEditor({
  extensions: [
    createYjsExtension({
      awareness,
      autoConnect: false,
      clientId: 'writer',
      sharedRoot,
    }),
  ],
})

editor.update((tx) => {
  tx.yjs.connect()
})

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

Named exports:

  • createYjsExtension(options)
  • slateNodesToYDelta(nodes)
  • yDeltaToSlateNodes(delta)
  • slatePointToYRelativePosition(binding, point)
  • yRelativePositionToSlatePoint(binding, position)
  • slateRangeToYRelativeRange(binding, range)
  • yRelativeRangeToSlateRange(binding, relativeRange)

Release-gated/internal first:

  • encodeYjsSnapshot(binding) / applyYjsSnapshot(binding, snapshot) stay internal until a public snapshot restore behavior row needs them.
  • isYjsOrigin(origin) / createYjsOrigin(label) stay internal unless third-party origin integration tests prove public users need them.

State namespace:

  • state.yjs.connected()
  • state.yjs.paused()
  • state.yjs.clientId()
  • state.yjs.sharedRoot()
  • state.yjs.remoteCursors()
  • state.yjs.pendingLocalOperations()
  • state.yjs.lastRemoteRevision()

Tx namespace:

  • tx.yjs.connect()
  • tx.yjs.disconnect()
  • tx.yjs.pause()
  • tx.yjs.resume()
  • tx.yjs.flushLocal()
  • tx.yjs.applyRemoteEvents(events, origin)
  • tx.yjs.reconcileFromSharedRoot(options?)
  • tx.yjs.sendCursor(range?)
  • tx.yjs.clearCursor()

React API target:

  • useRemoteCursorStates(editor, selector?, isEqual?)
  • useRemoteCursorDecorations(editor, options?)
  • no required public YjsProvider in the first package API; keep provider wiring inline in the example unless a later pass proves a thin provider-agnostic component earns its weight

Hard rule: no withYjs(editor), withYHistory(editor), or withCursors(editor) wrapper as the primary v2 API. Those names can appear in migration docs only as old-reference concepts.

Internal Runtime Target

Binding state:

  • stored in extension runtime state / WeakMaps
  • owns Y.Doc, Y.XmlText, awareness, origin symbols, connect state, paused state, current revision, local export queue, remote import depth, cursor revision
  • never stored in Slate document values
  • never exposed through public editor object mutation

Local export:

  1. extension onCommit receives Slate commit
  2. skip if disconnected, paused, skip-collab, collaboration, remote origin, selection-only awareness update, or no snapshot change
  3. convert operations to Yjs changes inside one Y transaction with a package local origin
  4. group commits by origin where useful
  5. update awareness separately for local cursor/selection

Remote import:

  1. observe Y events and precompute event deltas during the event callback
  2. if incremental conversion is safe, replay Slate operations in one editor.update
  3. if conversion is ambiguous, use canonical tx.value.replace(...) from shared root
  4. always tag remote imports with collaboration, remote-import, skip-scroll-into-view, and skip-selection-focus
  5. set metadata { collab: { origin: 'remote', saveToHistory: false }, history: { mode: 'skip' }, selection: { dom: 'preserve', focus: false, scroll: false } }
  6. clamp/null selections and cursor ranges explicitly after remote shrink/repair

Undo/history:

  • use Y.UndoManager for shared root changes with tracked local origins
  • store selection metadata as Y relative ranges
  • do not pollute slate-history undo stacks on remote import
  • expose undo/redo through tx.yjs.undo() / tx.yjs.redo() only if the first implementation proves selection restoration

Awareness/cursors:

  • awareness lives outside document updates
  • remote cursor state uses awareness doc client id, not bound root doc id
  • cursor updates increment a separate revision/external-store signal
  • React hooks subscribe narrowly; no document commit needed for awareness-only updates

Hook, Component, And Example DX Target

The example should demonstrate the actual call-site API first. Helpers are fine only after the API is visible.

Required example:

  • ../slate-v2/site/examples/ts/yjs-collaboration.tsx
  • route importer in ../slate-v2/site/pages/examples/[example].tsx
  • label in ../slate-v2/site/constants/examples.ts

Controls:

  • two editors plus optional third peer
  • connect / disconnect / pause / resume per peer
  • seed/reset shared document
  • local type into focused peer
  • remote prefix burst
  • same-offset contention
  • remote suffix insert
  • remote split/merge
  • remote move/remove selected node
  • canonical reconcile
  • undo / redo
  • send cursor / clear cursor
  • toggle network lag, dropped update, reorder queue
  • show event log, commit tags, local export count, remote import count, awareness revision, selection JSON, shared Yjs text snapshot

Example UI rule: build a useful tool surface, not a docs lecture. The controls are part of the proof, not decorative chrome.

Simplicity rule: the example may use local helpers for repeated UI controls and scenario dispatch, but the first screen must show the actual Slate/Yjs call site. Do not hide createYjsExtension(...), state.yjs, or tx.yjs behind an example-only abstraction.

Plate Migration Backbone Target

Plate should be able to adopt the package without wrapping every core call:

  • Plate can install createYjsExtension(...) during editor construction.
  • Plate can layer provider/auth/room/presence policy above package options.
  • Plate can use state.yjs and tx.yjs from product commands.
  • Plate-specific comments/suggestions stay in Plate packages.
  • Plate can keep its existing Yjs fixtures as downstream integration proof.

No current-version Plate adapter promise is required in this package slice.

Slate-Yjs Migration Backbone Target

External slate-yjs concepts map as follows:

Old conceptV2 target
withYjs(editor, sharedRoot)createYjsExtension({ sharedRoot })
YjsEditor.connect(editor)editor.update((tx) => tx.yjs.connect())
YjsEditor.disconnect(editor)editor.update((tx) => tx.yjs.disconnect())
withYHistory(editor)package undo manager integration, gated by tests
withCursors(editor, awareness)extension awareness option plus @slate/yjs/react hooks
direct editor.children = ...tx.value.replace(...)
override editor.applyextension onCommit local export
override editor.onChangecommit/awareness listeners and React external store
Transforms.select restoretx.selection.set(...) or explicit skip when relative range cannot resolve

Issue-Ledger Accounting

Current-state pass result: issue surface is real and cannot be skipped. The related-issue discovery pass is next.

Read evidence this pass:

  • docs/slate-issues/gitcrawl-live-open-ledger.md:91 lists live open #5771.
  • docs/slate-issues/gitcrawl-live-open-ledger.md:176 lists live open #5533.
  • docs/slate-issues/gitcrawl-v2-sync-ledger.md:93 currently marks #5771 as improves-claimed, not fixed.
  • docs/slate-v2/ledgers/issue-coverage-matrix.md:211 keeps #5771 as Improves because exact provider/browser closure is unclaimed.
  • docs/slate-v2/ledgers/issue-coverage-matrix.md:212 keeps #5533 as Related.
  • docs/slate-v2/ledgers/issue-coverage-matrix.md:98-99 keeps #1770 and #3741 related to collaboration op metadata/transaction boundaries.

Current issue matrix:

IssueClusterClaimWhyProof routeV2 sync ledgerPR line
#5771collaboration-selection-anchor-rebaseImproves now; possible Fixes only after package/browser proofcore proves selection stress; package/browser exact repro still missingpackage unit + Playwright yjs-collaboration selection suiteexisting improves-claimed; update later only if browser proof passespending
#5533collaboration-without-yjsRelateda Yjs package does not answer Yjs-free collaborationno fixed claimunchanged unless issue pass finds new wordingrelated matrix only
#1770collaboration-op-metadata-and-transaction-boundariesRelatedpackage may reduce op chatter but does not add a general op merge utilitypackage convergence testsunchanged unless issue pass revisesrelated matrix only
#3741collaboration-op-metadata-and-transaction-boundariesRelatedpackage can reconstruct moved-node state in Yjs, but does not change core move_node payloadpackage move_node conversion testsunchanged unless issue pass revisesrelated matrix only
#4178collaboration-op-metadata-and-transaction-boundariesRelatedcommit tags/metadata help source tracking, but operations themselves are not new source-tag payloadspackage origin testsv2 sync remains cluster-synced; coverage matrix and fork dossier now carry explicit Related rowsrelated matrix only

ClawSweeper related-issue discovery status: complete. Trigger: public package API, runtime behavior, browser behavior, and issue claims are in scope. Existing ledgers were enough; no broad live GitHub discovery was needed.

PR reference status: unchanged in current-state pass; update in issue-sync pass if accepted API shape, proof rows, or issue claims change.

Pass date: 2026-05-18 Status: complete Mode: ledger/cache-first ClawSweeper pass; no broad live GitHub discovery.

Source ledgers checked:

  • docs/slate-issues/gitcrawl-live-open-ledger.md
  • docs/slate-issues/gitcrawl-v2-sync-ledger.md
  • docs/slate-issues/open-issues-ledger.md
  • docs/slate-issues/gitcrawl-clusters.md
  • docs/slate-v2/ledgers/issue-coverage-matrix.md
  • docs/slate-v2/ledgers/fork-issue-dossier.md
  • docs/slate-v2/references/pr-description.md
  • docs/slate-issues/test-candidate-map/5912-5771.md
  • docs/slate-issues/requirements-from-issues.md
  • docs/slate-issues/package-impact-matrix.md

Discovery findings:

IssueLive/local statusCurrent claim posturePackage-plan decisionPass-3 ledger action
#5771live open singleton; test candidate is high-QPS remote insert_text vs local selectionimproves-claimed in v2 sync and Improves in coverage matrixcentral package browser-selection proof row; no Fixes until real slate-yjs adapter + Playwright repro passeskeep Improves; later add package proof only after implementation
#5533live open singleton feature request for collaboration without YjsRelated in coverage matrixexplicit non-claim; this package is Yjs-specific and should not pretend to answer Yjs-free collaborationkeep related/non-claim wording
#1770frozen corpus/v2 sync collaboration operation-composition pressureRelated in coverage matrixpackage may reduce Yjs transport noise but does not ship general op merge APIkeep related
#3741v2 sync says PM-08 covers remote move_node replay but no moved-node payload claimRelated in coverage matrixYjs package can store reconstructed shared content but must not redesign Slate move_node payloadkeep related
#4178live open singleton; v2 sync has cluster-synced; no explicit coverage-matrix row foundcluster-synced onlypackage origin tags help adapter provenance, but operations still do not carry durable public source metadatapass 3 should add or explicitly skip a coverage-matrix related row
#2288existing Improves row for range-capable child-window operationImproves unchangedadjacent operation-range pressure; not a package claimno change unless package tests add range replay proof
#4477existing Improves row for selection-anchored annotations/widgetsImproves unchangedadjacent collaborative comments product request; raw slate-yjs should not own commentsno change
#3715v2 sync says not claimed/docs-example/support lanenot claimedold core-readiness adjacency, not relevant to package APIno package-plan claim
#3482v2 sync says cluster-synced under transaction/op/history pressurecluster-syncedadjacent historical pressure only; no slate-yjs package claimno package-plan claim

Pass conclusion: the current plan's conservative issue posture is right. The only discovered sync gap is #4178: it is relevant enough to mention in this package plan, but it lacks an explicit coverage-matrix row. Do that in pass 3, not in this discovery pass.

Issue-Ledger Pass

Pass date: 2026-05-18 Status: complete

Ledger edits:

  • Added #4178 to docs/slate-v2/ledgers/issue-coverage-matrix.md as Related, not Fixes or Improves.
  • Added a fork-local #4178 section to docs/slate-v2/ledgers/fork-issue-dossier.md.
  • Left docs/slate-issues/gitcrawl-v2-sync-ledger.md at cluster-synced because the current sync row is still accurate; the new coverage-matrix row carries the package-plan claim boundary.
  • Left docs/slate-v2/references/pr-description.md unchanged. No fixed-issue count changes, and no PR-facing Fixes claim exists.

Final claim wording:

  • #5771: Improves, still gated on real slate-yjs adapter plus Playwright repro before any Fixes claim.
  • #5533: Related; a Yjs package does not answer Yjs-free collaboration.
  • #1770: Related; no general operation-composition utility.
  • #3741: Related; no moved-node payload redesign.
  • #4178: Related; adapter provenance via commit metadata/origins, no durable public source field on operations.

Pass conclusion: issue-ledger accounting is synced for planning. Later issue-sync accounting still has to revisit PR-reference text after the accepted API shape and proof rows are final.

Issue Sync Accounting Pass

Pass date: 2026-05-18 Status: complete Mode: ClawSweeper ledger/reference sync after package identity revision.

Files synced:

  • docs/slate-v2/ledgers/issue-coverage-matrix.md
  • docs/slate-issues/gitcrawl-v2-sync-ledger.md
  • docs/slate-v2/ledgers/fork-issue-dossier.md
  • docs/slate-v2/references/pr-description.md

Claim decisions:

IssueFinal pass-11 claimSync action
#5771Improveskept exact Fixes blocked until real @slate/yjs adapter/browser repro passes; added May 18 package plan to proof refs
#5533Relatedclarified that planned @slate/yjs is Yjs-specific and does not answer Yjs-free collaboration
#1770Relatedfixed fork-dossier status wording to match the existing related decision; no operation-composition closure
#3741Relatedfixed fork-dossier status wording to match the existing related decision; no moved-node payload closure
#4178Relatedclarified adapter-local origins/commit metadata as the package answer; no durable public source field on operations

PR reference result:

  • Added a Slate-Yjs package readiness summary line.
  • Fixed issue count unchanged.
  • Related matrix count unchanged.
  • No new exact fixed or improved claims beyond the already-recorded #5771 Improves posture.
  • PR/import path remains a non-claim until @slate/yjs publish access or another final public package identity is resolved.

Pass conclusion: issue/reference sync is complete for the planning lane. The next pass is closure/final-gate review only.

Legacy Regression Proof Matrix

RiskRequired proof
local text/mark/node op does not converge through Yjspackage fixture matrix from external slate-yjs op fixtures
remove/split/merge/move corrupts relative positionsunit tests using Y relative positions and Slate bookmarks
remote import exports back to YjsonCommit loop suppression test
remote import pollutes local undoslate-history + Y.UndoManager tests
undo restores wrong cursor after remote changerelative-range metadata tests inspired by y-prosemirror undo
awareness-only cursor update rerenders document or changes commit countReact external-store test
selected node removed remotely leaves stale selection pathunit plus Playwright browser repro
same-offset remote insert breaks local follow-up typingunit plus Playwright
select-all/Delete regression returnsPlaywright on example
left-editor type then right-editor Enter crash returnsPlaywright on example
canonical reconcile steals focus or scrollReact policy unit plus Playwright focus check

Browser Stress And Parity Strategy

Playwright file:

  • ../slate-v2/playwright/integration/examples/yjs-collaboration.test.ts

Required browser rows:

  • initial two-peer convergence after local typing
  • disconnect peer, edit local, reconnect, reconcile
  • pause peer, local edits do not export, resume exports after explicit flush
  • same-offset remote insert while local collapsed selection is active
  • remote prefix burst then local typing lands at transformed point
  • remote remove selected node then local typing does not throw
  • select-all/Delete leaves valid empty paragraph and converges
  • type in left editor, press Enter in right editor, assert no pageerror
  • remote cursor awareness update changes cursor UI without document writes
  • canonical reconcile does not focus/scroll-steal active peer
  • undo/redo after remote insert restores expected selection

Browser proof must assert:

  • model text
  • model selection when observable
  • visible DOM text
  • event log / commit tags
  • no pageerror
  • follow-up typing after the stress action

Performance/DX/Migration/Regression/Simplicity Pressure Pass

Pass date: 2026-05-18 Status: complete Skills applied:

  • .agents/skills/performance/SKILL.md
  • .agents/skills/regression-lock-pass/SKILL.md
  • .agents/skills/tdd/SKILL.md
  • .agents/skills/code-simplicity-reviewer/SKILL.md

Source evidence:

  • ../slate-v2/scripts/benchmarks/core/current/collab-readiness.mjs:21-26, :110-157, :216-340, :351-378
  • ../slate-v2/packages/slate-history/src/history-extension.ts:177-210, :211-270
  • ../slate-v2/playwright/integration/examples/plaintext.test.ts:14-84, :271-320
  • ../slate-v2/playwright/stress/stress-utils.ts:37-67, :94-130
  • ../slate-v2/site/pages/examples/[example].tsx:14-58
  • ../slate-v2/site/constants/examples.ts:1-31
  • docs/solutions/performance-issues/2026-05-08-dom-selection-bridges-must-stay-cheap-on-selectionchange.md
  • .agents/skills/performance/rules/cohort-segmentation.md
  • .agents/skills/performance/rules/repeated-unit-budget.md
  • .agents/skills/performance/rules/effect-subscription-budget.md
  • .agents/skills/performance/rules/interaction-inp-matrix.md
  • .agents/skills/performance/rules/memory-dom-tagging.md
  • .agents/skills/performance/rules/editor-native-behavior-proof.md
  • .agents/skills/performance/rules/degradation-contract.md
  • .agents/skills/performance/rules/react-19-runtime-proof.md
  • .agents/skills/performance/rules/browser-trace-cwv-proof.md
  • .agents/skills/performance/rules/production-rum-dashboard.md

Performance

Current benchmark verdict: useful shape, current implementation. The benchmark has normal/large/stress/pathological cohorts and lanes for local export, remote replay batch/separate, bookmark rebase, canonical replace, history skip, connect/disconnect heap, convergence, and red flags. On 2026-05-24 it passes through the current API: history() plus extension setup(...) / onCommit(...).

Required benchmark follow-up:

  • keep the calibration-only threshold until three comparable runs exist
  • run one five-iteration calibration before package release
  • add package-adapter rows after real packages/slate-yjs source exists
  • keep browser interaction traces separate from core benchmark proof

Performance release rows:

SurfaceRequirement
Cohortsnormal, medium, large, stress, pathological; add complexity tags for collaboration activity, selection span length, cursor count, annotations/comments, custom renderers, mobile/IME
Repeated unitdocument block plus peer cursor projection; track DOM nodes, components, handlers, effects, subscriptions, allocations, dirty-id sizes
Local exportone commit -> one Y transaction; no whole-document scan for selection-only awareness
Remote importbatch remote operations should not be slower than separate replay; red flag stays until repeated runs prove the ratio
Canonical reconcilefallback only; benchmark it separately and never hide it inside incremental import averages
Awarenesscursor-only update must not create Slate document commits and must wake only cursor subscribers
Selectionchangehot path stays primitive and model-owned; no public raw DOM range bridge for collaboration
React 19.2use external-store selectors for cursor state; keep visible typing/selection/IME urgent; Activity/deferred work can apply to logs and hidden panels, not editable content
Trace/RUMPlaywright/browser trace for selection bugs; RUM tags for interaction, cohort, doc size, visible DOM count, cursor count, mode, browser/mobile/IME

Explicit non-claim: no production performance claim from the core benchmark alone. It is a lab calibration gate. Browser interaction rows and memory/DOM tags are still required.

DX

DX pressure result: keep the API smaller than the first draft.

  • Keep public first API to extension factory, conversion helpers, relative position helpers, state.yjs, tx.yjs, and React cursor hooks.
  • Keep snapshot encode/apply helpers internal until a public snapshot restore behavior row exists.
  • Keep origin helpers internal unless a third-party origin integration test proves public users need them.
  • Do not add a public provider component for readiness.
  • Do not add compatibility aliases for withYjs, withYHistory, or withCursors.
  • Do not hide the package API behind example-local helpers.

Package shape:

File/surfaceDecision
package.jsonfolder packages/slate-yjs; preferred name @slate/yjs; do not use published slate-yjs unless ownership is secured; type: module, sideEffects: false, files: ["dist/"], subpath exports for ., ./core, ./react, ./internal
peersslate, yjs; awareness protocol dependency for cursor support; react / react-dom optional peers for ./react only
buildmatch current package tsdown / tsconfig patterns; do not hand-edit dist
source entrypointsroot reexports safe core surface; ./core has no React import; ./react owns hooks/decorations; ./internal owns binding/controller test hooks

Migration

Migration story is mapping, not shim compatibility.

Source userPath
external slate-yjs wrapper useruse migration docs table from withYjs / YjsEditor.connect / withCursors to createYjsExtension and tx.yjs
Plate userinstall extension at editor creation; keep room/auth/provider UI in Plate
raw Slate userpass Y.Doc, shared root, optional awareness; control lifecycle through tx.yjs
package maintainerrun old fixture ideas through current state/tx contracts; do not port wrapper mutation

Regression Lock And TDD

TDD strategy: vertical slices, not a giant imagined suite.

First red rows:

  1. package mounts createYjsExtension(...) and exposes state.yjs.connected() / tx.yjs.connect() without mutating the editor object
  2. local text commit exports exactly one Y transaction and does not export skipped/remote commits
  3. remote Y event imports through editor.update with collaboration metadata and no history pollution
  4. relative range survives remote prefix/suffix/same-offset operations
  5. awareness cursor update changes React cursor state without document commit
  6. Playwright reproduces #5771-class selected-node-delete/follow-up typing

Regression gates reuse current proof instead of inventing a parallel harness:

  • focused package tests for core binding/conversion/positions/undo/cursors
  • existing Slate collaboration tests for adapter, selection stress, bookmarks, canonical reconcile, React side-effect policy
  • Playwright example tests modeled after existing example and stress helpers, including pageerror, model text, model selection, visible DOM text, native selection/copy/paste/select-all, undo/history, IME where applicable, and follow-up typing

Native behavior contract:

BehaviorDefault package target
browser findnative, because editor content stays DOM-present
native selectionnative model-owned bridge; no raw DOM range public API
copy/pastenative event path preserved; package adds collaboration proof rows only
select-all/Deletenative action with model convergence proof
IME/compositionnative urgent path; no deferred cursor/update bridge in composition
mobile touch selectionno claim until mobile/browser row exists
undo/historyY.UndoManager plus Slate history skip proof; public undo/redo gated
collaboration/follow-up typingunit plus Playwright row required before issue closure

Simplicity

Simplicity verdict: the plan was mostly right, but the public API was too eager.

Cuts made in this pass:

  • snapshot encode/apply helpers moved out of the first public API
  • origin helpers moved out of the first public API
  • example helpers explicitly subordinate to showing the actual API call site

Rejected complexity:

  • provider component as readiness requirement
  • compatibility aliases for old wrapper names
  • generic network simulator abstraction in package source
  • broad public binding/controller object before tests prove lifecycle needs

Remaining complexity is earned: conversion, relative positions, undo selection, awareness, and browser tests are not optional in a collaboration package.

Applicable Implementation-Skill Review Matrix

LensApplicabilityCurrent-state findingPlan delta
Vercel React best practicesappliedawareness/cursor state must use narrow external-store subscriptions; React cannot be the collaboration engine@slate/yjs/react hooks use useSyncExternalStore / selector API
performance-oracleappliedconversion, replay, canonical replace, bookmarks, connect/disconnect, awareness, and selectionchange are hot pathsrequire current collab benchmark calibration, package stress rows, memory/DOM tags, and browser interaction proof
performanceappliedrepeated editor peers and large docs need cohort budgetspass 6 adds cohort, repeated-unit, trace/RUM, native behavior, and degradation gates
tddappliedbehavior surfaces are testable; package work should start with red unit/browser contractspass 6 defines vertical red rows before package source
shadcn/component compositionapplied to example onlyexample is a tool surface with controls; keep UI composable and denseuse controls/log panels; no marketing page
react-useeffectapplied to React bindingsprovider/awareness subscriptions are external-system effects; avoid reset-on-prop or derived-state effectsuse external store and cleanup via extension lifecycle
regression-lock-passapplied#5771 and prior Enter/select-all bugs are browser/runtime regressions, not serializer-only bugspackage rows must reuse existing collab unit contracts and add Playwright proof
code-simplicity-reviewerappliedfirst API exposed helpers before behavior rows earned themsnapshot and origin helpers are internal/release-gated first
steelman-passappliedpublic package/API decisions need maintainer-grade objections before closurepass 7 expands the objection ledger; pass 9 revises package identity to a publish-access gate
high-risk-deliberate-passappliedcollaboration package work can corrupt documents, selections, undo, awareness, browser behavior, issue claims, and release packagingpass 8 expands the pre-mortem, proof plan, rollback/remediation, and no-claim gates

High-Risk Deliberate Mode

Pass date: 2026-05-18 Status: complete Skill: .agents/skills/high-risk-deliberate-pass/SKILL.md

Trigger: this plan changes a public package boundary, collaboration behavior, selection/undo semantics, browser proof, extension substrate usage, migration path, issue claims, and release gates.

Source evidence:

  • docs/slate-issues/package-impact-matrix.md: runtime boundary owns the majority of issue pressure; selection/focus/DOM bridge and React runtime are cross-package risk, not a raw-core-only concern.
  • docs/slate-issues/test-candidate-map/5912-5771.md: #5771 needs high-QPS remote insert versus local selection proof.
  • docs/slate-v2/ledgers/issue-coverage-matrix.md:212: #5771 remains Improves, not Fixes, until real adapter/browser proof exists.
  • docs/slate-issues/benchmark-candidate-map.md: historical performance proof needs specific workload lanes, not vague "large doc" timing.
  • ../slate-v2/playwright/integration/examples/dom-coverage-boundaries.test.ts:181-263, :300-348: existing browser proof already checks pageerror, native drag selection, IME, and mobile boundary behavior.
  • ../slate-v2/playwright/stress/generated-editing.test.ts:914-1038, :1128-1148: stress harness already records native selection text, model selection, render budgets, backward selection, IME, undo, and focus proof.

Blast radius:

AreaAffected surfaceRisk
package sourcepackages/slate-yjsnew public package, exports, peer deps, build, docs
raw corepackages/slateoperations, transactions, bookmarks, commit metadata, full-document replace
React runtimepackages/slate-reactexternal-store cursor hooks, selection side effects, focus/scroll suppression
historypackages/slate-historyremote history skip, Y.UndoManager coordination, selection restore
browser examplessite/examples, example route registrysimulation controls become the browser proof surface
Playwrightplaywright/integration/examples, stress helpersselection bugs only close with browser proof
docs/PR/ledgerswalkthroughs, package docs, issue coverage, PR referenceissue overclaim risk

Pre-mortem and required mitigation:

FailureLikely causeUser-visible damageRequired mitigationRelease posture
CRDT divergence after nested move/split/mergeincremental Y event conversion misses a Slate structural edgepeers display different documentscanonical reconcile fallback, external fixture matrix, batch/separate convergence testsblock release
Remote import exports back to Yjsorigin/metadata filtering incompleteinfinite echo or duplicate contentonCommit loop suppression tests and event-log assertionsblock release
Remote import pollutes local undoremote metadata/history skip incompleteundo removes remote changes or corrupts stackSlate history skip tests plus Y.UndoManager testsblock release
Undo restores stale Slate pathsselection metadata stores paths instead of relative rangescaret jumps or throws after remote editsY relative-range metadata, bookmark tests, Playwright undo rowgate public undo/redo
Awareness update creates document commitcursor state stored through Slate value/committyping lag, dirty history, false exportsexternal-store cursor state and commit-count assertionblock React API release
Provider lifecycle leaks listenersconnect/disconnect cleanup incompleteduplicate cursor/events after reconnectconnect/disconnect heap/listener benchmark and cleanup testsblock release
Browser selection proof misses real bugtests only assert model text#5771-class bugs survivePlaywright rows assert model selection, DOM text, pageerror, and follow-up typingno Fixes claim
Benchmark passes but real interaction lagsbenchmark is core-only calibrationslow browser selection/typing in examplesinteraction rows with trace, memory/DOM tags, and native behavior proofno perf claim
Package identity unresolvedfinal public package name cannot publish or conflicts with the community packagedocs/import path churn after implementationpublish-access proof before docs/examples finalizerevise import path before publish
Public API too broadbinding/snapshot/origin helpers exposed too earlybreaking changes or long-term support burdeninternal-first helpers and type tests for only approved public APIblock docs until surface matches plan
Issue claim overreachescore substrate proof mistaken for package/browser proofmaintainer trust losscoverage/PR sync after implementation only; #5771 stays Improves until Playwright passesblock claim upgrade

Expanded proof plan:

Proof classRequired rows
Unit/packagemount extension without editor mutation; local export suppression; remote import metadata; conversion fixtures; relative point/range; undo selection metadata; connect/disconnect cleanup
Integrationpackage with slate, slate-history, and slate-react; remote history skip; canonical reconcile; awareness-only update without document commit
Browsertwo-peer convergence; selected-node removal plus follow-up typing; same-offset contention; select-all/Delete; left-editor type then right-editor Enter; remote cursor awareness; undo/redo after remote insert; no pageerror
Performancerepaired collab readiness benchmark; five-iteration calibration; interaction trace; memory/DOM tags; cursor count; batch/separate convergence red flags
Migration/adoptiondocs map external withYjs family to extension/state/tx calls; no compatibility aliases unless test-backed; Plate/provider boundary documented
Release/packageregistry availability for final package name; peer dependency check; no yjs dependency leak into raw slate; subpath typecheck/build
Issue/referencecoverage matrix and PR reference only upgrade issue claims after package + Playwright proof

Rollback and remediation:

  • safe to delete/recreate ../slate-v2/packages/slate-yjs source during implementation because current folder is residue, not source.
  • if conversion diverges, keep package internal and fall back to canonical reconcile while narrowing public conversion helpers.
  • if undo selection restoration fails, ship without public tx.yjs.undo() / tx.yjs.redo() commands.
  • if awareness causes document commits, split React cursor hooks from core package release until external-store proof passes.
  • if package identity fails, revise import path before docs and examples ship.
  • if Playwright cannot prove #5771-class behavior, keep issue language at Improves and do not close the issue.

Verdict: keep the plan. The risk is high, but the plan has the right hard cuts: no wrapper API, no provider policy, no broad public binding, no issue closure without browser proof, and no performance claim from a core benchmark alone.

Slate Maintainer Objection Ledger

Pass date: 2026-05-18 Status: complete Skill: .agents/skills/steelman-pass/SKILL.md

Architecture overview: raw Slate stays unopinionated and owns the transaction/commit/bookmark substrate. slate-yjs owns Yjs-specific binding state, relative-position mapping, undo-manager integration, and awareness. React owns rendering and external-store subscription. Plate owns app/provider policy.

DecisionStrongest fair objectionSteelman antithesis and tradeoffViable alternativesWhy chosen winsAdoption/docs/proofVerdict
Add first-party slate-yjs package"This makes Slate look officially Yjs-only."Not shipping it keeps raw Slate smaller and avoids maintaining CRDT semantics. The cost is that every app re-learns the same adapter bugs outside the core test system.leave third-party only; publish docs-only adapter recipe; put Yjs in raw slate corepackage boundary keeps raw core clean while giving the ecosystem one tested bindingdocs must state Yjs is optional; package tests must prove no yjs peer leaks into slate / slate-reactkeep
Public package name"Using a scoped name breaks the unscoped Slate package-family feel."Unscoped would be nicer if Slate owned it, but npm reality wins. A scoped name avoids confusing users and mirrors @lexical/yjs / @y/prosemirror.@slate/yjs; @slate-yjs/v2; keep only internal package; secure ownership transfer for slate-yjspass 9 proved slate-yjs is already published by BitPhinix/slate-yjs, so unscoped cannot be the default publish targetimplementation must use the final import path only after ownership/scope access is resolvedrevise: prefer @slate/yjs unless slate-yjs ownership is secured
Replace withYjs wrapper API with createYjsExtension(...)"Existing adapter users already know withYjs; changing names increases migration pain."Keeping wrapper names lowers apparent migration cost but preserves the wrong mental model: editor mutation and lifecycle monkey-patching.compatibility aliases; wrapper that internally installs extension; dual APIcurrent Slate v2 explicitly rejects wrapper-era extension slots; one public shape avoids two lifecyclesmigration docs map old names to new calls; no compatibility alias until tests prove a real adoption needkeep
Expose state.yjs / tx.yjs namespaces"Simple editor methods would be easier to discover."Flat methods are easy at first and messy later; they collide with plugins and hide read/write boundaries.editor.yjs.connect(); public binding object; hooks-only APIstate/tx namespaces match live extension contracts and keep read/write legality explicitAPI docs must show one-page lifecycle recipe; type tests must prove namespace augmentationkeep
Keep binding/controller internal first"Advanced users will need direct binding access for providers, debugging, and instrumentation."Public binding access helps power users but freezes internals before lifecycle tests settle.public YjsBinding; public debug controller; event emitterinternal-first keeps the first API small; state/tx and hooks cover normal usageexpose debug snapshots through state.yjs only when tests prove stable fieldskeep
Keep provider policy out"A collaboration package without provider UI feels incomplete."Provider components speed demos but drag auth, room naming, persistence, reconnection, and product policy into raw Slate.YjsProvider component; room/provider adapter interface; provider examples onlypackage should accept Y doc/root/awareness and leave hosting to apps/Plateexample wires an in-memory/local provider inline; docs show provider boundary, not a product room abstractionkeep
Gate public undo/redo commands"Users expect undo/redo in any collaboration package."Shipping commands early is tempting, but wrong selection restoration corrupts trust faster than missing commands.expose immediately; only expose Y.UndoManager; leave undo to appspublic commands ship only after relative-selection restoration passes unit and Playwright proofdocs can mention internal integration and say command exposure is gated by proofkeep
Require Playwright selection proof before issue closure"This slows package delivery; Node tests cover conversion."Skipping browser proof gets a faster package and a fake #5771 story. The real failures were browser selection/runtime failures.Node-only release; manual QA; issue marked improves onlyPlaywright is the only honest closure gate for selection bugs and Enter/select-all regressionsissue claims stay Improves until package/example Playwright proof passeskeep
Canonical reconcile fallback"Full replace can be expensive and may hide bad incremental conversion."Removing fallback forces all edge cases through incremental conversion but risks unrecoverable divergence.incremental-only; always canonical; explicit user-triggered reconcile onlyfallback is required for reconnect and ambiguous events, but benchmark red flags must prevent overusebenchmark canonical separately; event log marks canonical path; no production perf claim from fallbackkeep
Trim snapshot/origin helpers from first public API"Power users may need snapshots and origin introspection immediately."Exposing them now makes implementation details contractual before behavior rows exist.publish helpers now; debug-only exports; internal only foreverrelease-gated helpers preserve API minimalism while leaving a path if tests prove needpublic docs omit them; internal tests can use ./internal; add later only with behavior proofkeep
Full simulation example with many controls"The example could become a mini app that distracts from the API."A small demo is easier to read but misses selection, lag, undo, and failure controls.minimal two-editor example; docs-only controls; separate test harnessthe example is also the browser proof surface, so controls are justified if the API call site remains visiblefirst viewport shows actual setup; controls are dense tool UI; Playwright uses the same controlskeep
Conservative issue language"If core selection stress passes, why not say Fixes #5771?"Aggressive issue claims make the PR look stronger, but they collapse core substrate proof into package/browser proof.claim fixes now; never mention issue; split issue claims into later PRconservative language protects maintainer trust and prevents accidental overclaimPR/reference sync can upgrade only after package plus Playwright proofkeep

Pass conclusion: the major architecture decisions survive steelman pressure, but pass 9 revised the package-name decision. The package folder can still be packages/slate-yjs; the public package name must not silently claim slate-yjs.

Ecosystem Maintainer Pass

Pass date: 2026-05-18 Status: complete Mode: ecosystem boundary review after live package/source checks.

Source evidence:

  • npm view slate-yjs name version description repository --json: slate-yjs exists as 3.2.0, "Yjs bindings for Slate.", repository BitPhinix/slate-yjs.
  • npm view @slate/yjs name version description repository --json: registry returned E404 for @slate/yjs.
  • ../slate-yjs/package.json: root package name is slate-yjs; workspace packages are @slate-yjs/core and @slate-yjs/react.
  • packages/yjs/package.json: Plate publishes @platejs/yjs and currently depends on @slate-yjs/core, y-protocols, and yjs.
  • packages/yjs/src/lib/BaseYjsPlugin.ts:1-170 and packages/yjs/src/lib/withPlateYjs.ts:1-47: Plate owns provider config, Hocuspocus/WebRTC integration, plugin APIs, and wrapper-era withTYjs / withTCursors / withTYHistory composition.
  • docs/editor-test-harvester/yjs-collaboration/report.md:12-45: external slate-yjs, y-prosemirror, Yjs, and Lexical evidence is fixture/mechanism input, not a drop-in API mandate.
  • docs/solutions/documentation-gaps/2026-04-09-slate-collaboration-docs-must-mark-the-external-adapter-boundary.md:19-41: collaboration docs must name the boundary between Slate substrate and adapter/provider ownership.
  • docs/solutions/performance-issues/2026-05-08-dom-selection-bridges-must-stay-cheap-on-selectionchange.md:31-64: collaboration/cursor overlays get model Range | null, not raw DOM range policy.

Maintainer-facing decisions:

SurfaceDecisionWhy
package identitycreate ../slate-v2/packages/slate-yjs; prefer public name @slate/yjs; treat slate-yjs as blocked unless ownership transfer/publish access is provennpm already has [email protected]; docs cannot claim an import path that may not publish
external slate-yjs relationshipuse as MIT mechanism/fixture evidence; do not present as a compatible fork or drop-in replacementits public packages are @slate-yjs/core / @slate-yjs/react and its wrapper model conflicts with Slate v2 extension/state/tx
Plate adoptionkeep @platejs/yjs as provider/product policy; let it migrate internals to the new package only after package tests passPlate currently owns provider config, room lifecycle, user callbacks, and product API; raw Slate should not inherit that policy
provider ecosystemno first-party room/auth/provider abstraction in this packageHocuspocus/WebRTC/local providers are app/Plate choices; package accepts Y.Doc, shared root, and awareness
docswrite latest-state docs with final import path only; no changelog/migration prose in reference pagespackage identity and adapter boundary are user-facing API truth
examplessimulation example may use an in-memory/local provider and explicit failure controls; it must show the real extension/state/tx call siteexample is proof surface and DX surface, not marketing
issue claimskeep #5771 at Improves until real package plus Playwright selection proof passespackage naming and architecture evidence do not close a browser selection bug

Migration contract:

  • external @slate-yjs/core users get a concept map, not compatibility aliases.
  • Plate gets a downstream adoption plan: keep @platejs/yjs public APIs, replace wrapper internals behind its plugin boundary later, and reuse its fixture pack as integration proof.
  • raw Slate users get a small package: extension factory, conversion helpers, relative-position helpers, state/tx namespaces, and React cursor hooks.
  • provider users bring their own provider; docs show where to connect it without making provider hosting a Slate API.

Release communication rules:

  • do not publish docs that import from slate-yjs unless the npm package is controlled by Slate.
  • do not imply the new package supersedes the community package without naming the API break plainly in migration docs.
  • do not copy external source wholesale; port portable fixtures/mechanics with MIT attribution where required.
  • do not upgrade issue claims in the PR reference until the final import path, package tests, and Playwright rows exist.

Pass conclusion: keep the architecture, revise the package identity. The ecosystem-safe path is packages/slate-yjs as the repo folder and @slate/yjs as the preferred public package name, pending scope/publish access.

Revision Pass

Pass date: 2026-05-18 Status: complete Scope: pass-9 contradiction cleanup across package identity, public API, driver commands, scorecard, pass ledger, and final gates.

Revision findings:

AreaProblem foundRevision
current verdictplan still implied an unscoped first-party slate-yjs packagekept repo folder packages/slate-yjs; made @slate/yjs the preferred public name and slate-yjs blocked unless ownership is secured
public API examplesimports could drift between folder name and publish nameexamples use @slate/yjs/core and subpath list uses @slate/yjs/*
package shapepackage.json row still treated unscoped name as the targetpackage row now separates folder name from publish name
maintainer objection ledgerold package-name row framed conflict as hypotheticalrow now states npm reality and the scoped-name decision directly
implementation gatespackage filters used a publish name that may changegates use --filter ./packages/slate-yjs so local verification survives final package naming
score/pass statescorecard and pass ledger still described passes 1-9scorecard now records passes 1-10 and leaves issue sync/final gates open
final gatespackage identity language was too softfinal gate requires resolved public identity before docs/examples claim an import

Revision verdict: the plan is internally coherent enough for issue/reference sync. No architecture decision changed in this pass; this pass only removed stale wording and made the package identity rule executable.

Hard Cuts And Rejected Alternatives

Hard cuts:

  • primary withYjs / withYHistory / withCursors wrapper API
  • direct editor.children assignment
  • editor.apply or editor.onChange override
  • extension register / commitListeners
  • storing Yjs objects in Slate document values
  • claiming #5771 fixed without package/browser proof
  • treating dist/ residue as source
  • publishing or documenting slate-yjs as the package name without verified ownership/publish access

Rejected:

  • current-version external slate-yjs compatibility aliases before publish
  • product comments/suggestions in slate-yjs
  • broad provider hosting abstraction
  • Yjs-free collaboration claim

Implementation Phases With Owners

  1. Package source scaffold: ../slate-v2/packages/slate-yjs/package.json, tsdown.config.mts, tsconfig.json, src/{index,core,react,internal}.
  2. Core binding: extension factory, runtime state, onCommit export, remote import, lifecycle, loop suppression.
  3. Conversion: Slate nodes/ops <-> Y.XmlText/Y delta, with canonical fallback.
  4. Positions: Slate point/range <-> Y relative position/range.
  5. Undo/history: Y.UndoManager integration and selection metadata.
  6. Awareness/react: cursor state store, selector hooks, decoration helpers.
  7. Example: full simulation controls and event log.
  8. Playwright: selection/browser regression suite.
  9. Docs: package README/library page and collaborative editing walkthrough.
  10. Ledgers/PR reference: issue claims and proof rows.

Fast Driver Gates

Planning-only gate:

  • cwd: /Users/felixfeng/Desktop/repos/plate-copy
  • bun run completion-check -- --id 019e3967-668f-7f20-89e9-c6d3be500b9a

Slate v2 implementation gates:

  • cwd: /Users/felixfeng/Desktop/repos/slate-v2
  • bun install
  • bun --filter ./packages/slate-yjs test
  • bun --filter ./packages/slate-yjs typecheck
  • bun --filter ./packages/slate-yjs build
  • bun test ./packages/slate/test/collab-adapter-extension-contract.ts ./packages/slate/test/collab-selection-stress-contract.ts ./packages/slate/test/collab-bookmark-position-contract.ts ./packages/slate/test/collab-canonical-reconcile-contract.ts ./packages/slate-react/test/selection-side-effect-policy-contract.ts
  • bun run bench:core:collab-readiness:local
  • SLATE_COLLAB_READINESS_ITERATIONS=5 bun run bench:core:collab-readiness:local after the calibration benchmark runs once cleanly
  • bun typecheck:site
  • playwright test playwright/integration/examples/yjs-collaboration.test.ts --project=chromium
  • playwright test playwright/integration/examples/yjs-collaboration.test.ts --project=chromium --trace=on for the first browser-selection proof artifact
  • bun check

Confidence Scorecard

DimensionScoreEvidence
React 19.2 runtime performance0.85React research page; cursor hooks target; pass 6 adds interaction, memory/DOM, trace/RUM gates; pass 8 adds awareness/selection failure gates; no implementation proof yet
Slate-close unopinionated DX0.90current state/tx extension contracts; raw Slate ownership boundary; first API trimmed; steelman pass accepts namespace/API shape; revision pass removes publish-name ambiguity from API examples
Plate and slate-yjs migration-backbone shape0.90external slate-yjs mapping; Plate owner boundary; package target; shim boundary explicit; ecosystem pass proves Plate provider policy stays downstream and package identity must be scoped/resolved
Regression-proof testing strategy0.87concrete unit/browser matrix; pass 6 adds TDD red rows and native behavior contract; pass 8 adds pre-mortem and rollback/remediation gates; Playwright not implemented yet
Research evidence completeness0.92live Slate source, external slate-yjs, Lexical-Yjs, y-prosemirror, harvest report, dedicated compiled source page, npm/package identity checks, synced issue/reference rows
shadcn-style composability and hook/component minimalism0.85example/control plan and React hook target; pass 6 keeps helpers subordinate to API visibility; steelman pass accepts full controls only as proof surface
total0.92passes 1-12 complete; planning lane is ready for user review and later execution

Why not higher:

  • benchmark is core-only calibration and not package/browser proof
  • no package source exists
  • Playwright proof is not implemented
  • public package identity is revised but not yet proven with publish access

Pass Schedule And Pass-State Ledger

PassStatusEvidence addedPlan deltaOpen issuesNext owner
1. Current-state read and initial scorecompletelive package residue, Slate API changes, focused tests pass, benchmark pass, reference source scanrecreated plan around package implementation and current APIspackage absent; example and Playwright proof absentralplan
2. Related issue discoverycompletelocal issue ledgers, coverage matrix, fork dossier, PR reference, test-candidate map, requirements/package-impact docsconfirmed #5771/#5533/#1770/#3741 posture; added #4178 sync gap plus adjacent #2288/#4477/#3715/#3482 decisions#4178 gap resolved in pass 3ClawSweeper/ralplan
3. Issue-ledger passcompletecoverage matrix, fork dossier, pass-2 discoveryadded explicit related/non-closure #4178 rows; finalized current issue wordinglater PR-reference sync after API/proof finalizationralplan
4. Intent/boundary and decision briefcompleteintent-boundary pass, decision brief pressure tablehardened provider scope, public binding exposure, undo/redo gate, source-metadata non-claim, migration boundarynone for planningralplan
5. Research/ecosystem/live-source refreshcompletenew compiled Yjs collaboration binding source page; live line refs for Slate v2, external slate-yjs, Lexical Yjs, y-prosemirrorclosed research compile gap and strengthened steal/reject/diverge synthesisnone for this passralplan
6. Performance/DX/migration/regression/simplicity pressurecompleteperformance rule files, current benchmark source, package patterns, Playwright examples/stress helpers, DOM selection performance solutionadded cohort/repeated-unit/trace/RUM/native gates; trimmed first public API; added TDD red rows and migration boundariespackage/browser performance proof still pendingralplan
7. Maintainer objection ledgercompletesteelman-pass rows for package scope, name, wrapper cut, state/tx namespaces, binding exposure, provider boundary, undo/redo, Playwright proof, canonical reconcile, helper trimming, example controls, issue claimsaccepted current decisions; later pass 9 revised package identitypackage implementation proof remainssteelman-pass
8. High-risk deliberate modecompletepackage-impact matrix, #5771 test candidate, benchmark candidate map, issue coverage row, browser/stress proof lanesexpanded pre-mortem, blast radius, proof plan, rollback/remediation, and no-claim gatesimplementation proof still pendinghigh-risk-deliberate-pass
9. Ecosystem maintainer passcompletenpm package identity checks, external slate-yjs package names, Plate @platejs/yjs package/source, harvester report, collaboration docs boundary, DOM-selection learningrevised public package identity; kept Plate/provider policy downstream; added release communication rulesfinal import path still needs publish-access proofralplan
10. Revision passcompletepackage identity/API scan, command filter scan, score/pass-state scan, final-gate scancleaned unscoped-name contradictions, package filter commands, scorecard, pass ledger, and final gatesissue/reference sync still pendingralplan
11. Issue sync accountingcompletecoverage matrix, v2 sync ledger, fork issue dossier, PR referencekept #5771 Improves, kept #5533/#1770/#3741/#4178 related/non-closure, synced package identity caveatnone for issue syncralplan/ClawSweeper
12. Closure score/final gatescompletepass ledger, issue/reference sync, scorecard, final completion gatesreclassified package identity, package source, and Playwright rows as execution gates, not planning blockers; benchmark drift is resolvedimplementation remains for later executionralplan

Ralplan status: done. The next autonomous owner is implementation execution, not another planning pass.

Plan status remains pending for user review or Ralph implementation. That is intentional: the planning pass loop is closed, while package implementation and browser proof are the next execution lane.

Plan Deltas From This Review

Added:

  • stale package-residue finding
  • stale extension API finding
  • benchmark API drift finding and resolution
  • package-first verdict
  • current public API target
  • public API simplification after pass 6 pressure
  • full example controls
  • browser selection proof matrix
  • performance cohort/interaction/memory/native behavior gates
  • maintainer-grade steelman ledger
  • package identity publish-access gate
  • npm conflict for slate-yjs; preferred public package identity is now @slate/yjs pending Slate scope/publish access
  • high-risk pre-mortem, proof plan, rollback/remediation gates
  • ecosystem maintainer release communication rules
  • issue/reference sync for #5771, #5533, #1770, #3741, #4178, and the PR reference

Dropped:

  • old "do not create package yet" verdict
  • old register / commitListeners adapter examples
  • implicit compatibility with external wrapper API
  • unscoped slate-yjs as the default public package name

Strengthened:

  • Playwright selection proof is mandatory
  • #5771 remains Improves until package/browser proof exists
  • package must hard-cut dist residue into real source
  • #4178 is related source-metadata pressure; do not overclaim it from adapter origin tags. The coverage-matrix and fork-dossier rows are explicit related non-closure entries.
  • first public API does not include snapshot or origin helpers until behavior rows prove them
  • package/provider/API naming objections are resolved for planning, not deferred to implementation
  • high-risk pass makes issue-claim, package-name, awareness, undo/redo, and canonical-reconcile failure handling explicit
  • package identity is no longer a soft release check: slate-yjs is occupied, so docs/import paths must use the resolved final name only
  • PR reference now records package readiness without changing fixed issue counts or overclaiming #5771 as fixed

Unchanged:

  • raw Slate stays unopinionated
  • Yjs and awareness stay package-owned
  • Plate product collaboration stays out of raw package

Open Questions And What Would Change The Decision

  • If live issue discovery finds a current maintainer comment that asks for a different issue claim, update the issue matrix.
  • If repeated benchmark calibration exposes a core performance cliff, package start should first fix the core/benchmark blocker.
  • If Yjs relative position conversion cannot preserve Slate bookmarks across nested move/split/merge, narrow the public cursor/undo API until it can.
  • If Playwright cannot prove browser selection stability, do not claim #5771 fixed.
  • If Slate cannot publish @slate/yjs, revise the public import path before package docs, examples, or PR references claim a final import.

Final User-Review Handoff Outline

Accepted decisions by surface:

  • package source shape: recreate ../slate-v2/packages/slate-yjs as source; keep repo folder name, but use final public package identity only after publish access is resolved.
  • public API: prefer @slate/yjs with ./core, ./react, and ./internal; expose extension factory, conversion helpers, relative-position helpers, state.yjs, tx.yjs, and React cursor hooks.
  • internal binding/runtime: extension-owned binding state, setup, onCommit, remote import through editor.update, no editor monkey-patches.
  • conversion and canonical reconcile: incremental conversion first, canonical tx.value.replace(...) fallback for ambiguous/reconnect states.
  • undo/history: Y.UndoManager integration with relative-selection metadata; public undo/redo commands ship only after selection restoration proof.
  • awareness/react hooks: awareness stays outside document commits; React hooks use narrow external-store subscriptions.
  • example controls: full simulation tool with peer lifecycle, network failure controls, event log, selection JSON, cursor controls, undo/redo, and reconciliation actions.
  • Playwright regression rows: selection bugs require browser proof, including #5771-class selected-node/follow-up typing, same-offset contention, select-all/Delete, Enter crash, cursor awareness, and undo rows.
  • issue claims: #5771 remains Improves; #5533, #1770, #3741, and #4178 stay related/non-closure until implementation proof exists.
  • hard cuts: no wrapper API, no direct editor.children, no apply / onChange overrides, no provider hosting policy, no Yjs-free collaboration claim, no unowned slate-yjs public package claim.
  • verification gates: package test/typecheck/build, focused core collab tests, current collab benchmark, site typecheck, Playwright yjs-collaboration proof with trace, then bun check.

Closure Score/Final Gates Pass

Pass date: 2026-05-18 Status: complete

Closure verdict: done for Slate Ralplan planning. Not done for implementation.

Final-gate accounting:

GateStatusEvidence
all pass-state rows completecompletepasses 1-12 complete in this plan and completion state
related issue discovery and issue-ledger synccompleterelated issue pass, issue-ledger pass, and issue-sync accounting pass
PR reference and coverage matrix synccompletePR reference, coverage matrix, v2 sync ledger, and fork dossier updated in pass 11
collab benchmarkcomplete core calibrationbun run bench:core:collab-readiness:local passes in ../slate-v2; package/browser performance proof remains separate
public package identityexecution gateplan records slate-yjs occupied and prefers @slate/yjs pending publish access
package sourceexecution gateplan records package source absent and gives package source scaffold phase
Playwright proofexecution gateplan records required browser proof rows and no issue closure until they pass
score thresholdcompletetotal 0.92, no dimension below 0.85
final handoffcompleteaccepted decisions listed above

Reasoning: Slate Ralplan is a planning/review gate. The remaining unmet facts are exactly the build work this plan hands to a later execution owner. Keeping the hook pending would be fake rigor; it would only ask the next pass to repeat the same conclusion.

Final Completion Gates

Closed for planning:

  • every pass-state row is complete with evidence
  • related issue discovery and issue-ledger sync are complete
  • PR reference and coverage matrix are updated
  • goal-continuation audit revalidated the current Slate API, package residue, registry identity, local reference packages, focused collab tests, and current collab benchmark
  • collab benchmark stale API blocker is resolved; package/browser performance proof remains an execution gate
  • public package identity is recorded as an execution gate
  • final score is 0.92 with no dimension below 0.85
  • final handoff is emitted in this document