docs/solutions/developer-experience/2026-05-13-slate-v2-yjs-readiness-needs-core-contracts-before-package-work.md
Starting a first-party slate-yjs package before proving the raw Slate v2
substrate would invite dirty adapter hacks: monkey-patched editor methods,
direct document mutation, vague remote-selection behavior, and inflated issue
claims.
The better move is to prove the substrate first, then let the package own Yjs schema, providers, awareness, and cursor UI.
slate-yjs patterns depend on editor.apply, editor.onChange, and
direct editor.children replacement.slate-yjs first. That would make package ergonomics drive raw core
API before the failure modes were known.Run Yjs readiness as a core contract lane before package work.
The fake adapter contract should mount through public extension APIs and export from commit listeners:
const extension = defineEditorExtension({
name: "fake-collab-adapter",
register(context) {
const state = context.runtimeState({
connected: true,
exports: [],
paused: false,
});
return {
cleanup() {
state.set((current) => ({
...current,
connected: false,
paused: true,
}));
},
commitListeners: [
(commit) => {
if (
commit.tags.includes("skip-collab") ||
commit.tags.includes("collaboration") ||
commit.metadata.collab?.origin === "remote"
) {
return;
}
state.set((current) => ({
...current,
exports: [...current.exports, commit.operations],
}));
},
],
};
},
});
Remote imports should stay transaction-owned and metadata-rich:
editor.update(
(tx) => {
tx.operations.replay(remoteOperations);
},
{
metadata: {
collab: { origin: "remote", saveToHistory: false },
history: { mode: "skip" },
selection: { dom: "preserve", focus: false, scroll: false },
},
tag: ["collaboration", "remote-import"],
},
);
React selection side effects should read the last commit policy instead of guessing from call sites:
export const shouldSkipSelectionScroll = (editor: ReactEditor) => {
const commit = editor.read((state) => state.value.lastCommit());
return Boolean(
commit?.tags.includes("skip-scroll-into-view") ||
commit?.metadata.selection?.scroll === false,
);
};
The final lane added these proof owners:
.tmp/slate-v2/packages/slate/test/collab-adapter-extension-contract.ts.tmp/slate-v2/packages/slate/test/collab-selection-stress-contract.ts.tmp/slate-v2/packages/slate/test/collab-bookmark-position-contract.ts.tmp/slate-v2/packages/slate/test/collab-canonical-reconcile-contract.ts.tmp/slate-v2/packages/slate-react/test/selection-side-effect-policy-contract.ts.tmp/slate-v2/scripts/benchmarks/core/current/collab-readiness.mjsIssue accounting moved #5771 only to Improves, not Fixes.
The contracts force raw Slate to prove the behavior future collaboration packages need without absorbing collaboration product policy.
Core owns deterministic replay, commit metadata, history skipping, bookmark and
runtime-id stability, canonical reconcile, and side-effect policy signals. A
future slate-yjs package can then translate Y events into those primitives
without patching editor methods or leaking Yjs objects into document values.
The benchmark keeps the performance claim honest by measuring collaboration as a composed workload: local export, remote replay, bookmark rebase, canonical replace, history skip, and connect/disconnect cleanup.
Y.Doc, provider, awareness, and cursor API.collaboration, history skip, and selection
side-effect metadata.Improves claim for provider bugs until a real
adapter/browser reproduction passes.docs/plans/2026-05-13-slate-v2-yjs-core-readiness-ralplan.md