Back to Plate

Slate v2 Architecture Deslop Review Ralplan

docs/plans/2026-05-07-slate-v2-architecture-deslop-review-ralplan.md

53.0.633.9 KB
Original Source

Slate v2 Architecture Deslop Review Ralplan

1. Current Verdict

Do not rewrite Slate v2 again.

The current architecture is the right backbone: Slate model/operations, a small public editor instance, editor.read / editor.update, typed state / tx extension namespaces, a runtime-owned React/input shell, deterministic commits, and conservative browser proof. A whole rewrite would mostly re-open decisions that the live source has already resolved.

The cleanest next move is bounded deslop, not architecture replacement:

  • keep the public core API small and type-led;
  • keep the internal static Editor helper table ugly and explicitly internal;
  • do not expose Editor.*, primitive writers, SlateSpacer, or command/chain sugar as normal public Slate API;
  • reduce test and doc reliance on slate/internal only where it hides public API expectations;
  • keep Mobile/IME and browser behavior under the shared runtime owner graph;
  • avoid broad runtime facade churn unless proof shows a real owner leak.

Final answer: the architecture is strong enough to keep. Refactor only the remaining legacy-teaching seams and internal-helper test debt. No rewrite.

2. Intent / Boundary Record

Intent:

  • answer whether the current Slate v2 architecture is the absolute best, cleanest, maintainable shape after the latest Mobile/IME and API hard cuts;
  • apply a deslop lens without using it as an excuse for a speculative rewrite;
  • decide whether the next owner should rewrite, refactor, cleanup, or stop.

Desired outcome:

  • a source-backed keep/rewrite/refactor verdict;
  • a focused cleanup list with behavior locks;
  • no implementation edits from this planning skill.

In scope:

  • public slate API surface;
  • internal slate/internal helper boundary;
  • slate-react render shell, selector hooks, runtime input ownership, and Mobile/IME owner graph;
  • Plate and slate-yjs migration backbone;
  • Lexical, ProseMirror, and Tiptap strategy comparison;
  • deslop smells in the current architecture and tests.

Non-goals:

  • no Slate v2 code patch in this Ralplan pass;
  • no current-version Plate or slate-yjs adapter work;
  • no new command/chain API for raw Slate;
  • no exact mobile/device issue claim changes;
  • no broad test rewrite for its own sake.

Decision boundaries:

  • breaking cleanup is allowed before publish if it removes a normal public footgun;
  • internal/test helpers may survive when they are fenced behind slate/internal;
  • runtime owners should be split only by proven ownership leakage, not file size discomfort;
  • issue claims remain unchanged because this review changes no user-facing behavior.

Unresolved user-decision points:

  • none.

3. Decision Brief

Principles:

  • one public lifecycle for coherent reads and writes;
  • raw Slate stays unopinionated and model-first;
  • browser input is runtime protocol, not React component state;
  • operations and commits are the collaboration/history truth;
  • cleanup must remove real ambiguity, not just rename working code.

Top drivers:

  • the live package already exports Editor as type-only from /Users/zbeyens/git/slate-v2/packages/slate/src/index.ts:6;
  • live BaseEditor exposes only read, subscribe, update, and extend at /Users/zbeyens/git/slate-v2/packages/slate/src/interfaces/editor.ts:480;
  • public-surface tests lock the editor instance method set and reject public static Editor value export at /Users/zbeyens/git/slate-v2/packages/slate/test/public-surface-contract.ts:320 and :337;
  • slate-react no longer exports SlateSpacer from the public package index, and the surface test guards that at /Users/zbeyens/git/slate-v2/packages/slate-react/test/surface-contract.tsx:204;
  • input ownership is centralized in useEditableRootRuntime and owner engines at /Users/zbeyens/git/slate-v2/packages/slate-react/src/editable/runtime-root-engine.ts:106, :199, :246, and :283.

Viable options:

OptionVerdictWhy
Whole rewrite againrejectIt would discard a now-good public lifecycle and re-open solved API/runtime decisions.
Keep architecture with no cleanuprejectInternal helper usage in tests and docs can still blur the public story.
Add raw Slate command/chain sugarrejectTiptap-style sugar belongs in product layers such as Plate, not raw Slate core.
Keep architecture and run bounded deslop sliceschooseIt preserves the proven substrate and targets only remaining ambiguity.

Chosen option:

  • keep the architecture;
  • run cleanup only against scoped public-surface, docs, and test-helper seams;
  • keep browser/runtime refactors proof-led.

Consequences:

  • future contributors get one public core story: createEditor, editor.read, editor.update, editor.extend, and defineEditorExtension;
  • tests may still use slate/internal, but only when explicitly proving internals;
  • docs/examples must stay stricter than tests;
  • issue closure remains conservative.

4. Confidence Scorecard

DimensionWeightScoreEvidence
React 19.2 runtime performance0.200.94React is kept as projection, while useEditableRootRuntime owns stable runtime refs and engines in /Users/zbeyens/git/slate-v2/packages/slate-react/src/editable/runtime-root-engine.ts:106, :188, :199, :246, and :283. Large-surface render budgets exist in /Users/zbeyens/git/slate-v2/playwright/stress/generated-editing.test.ts:221, :947, and :1020.
Slate-close unopinionated DX0.200.93The public editor instance is locked to extend, read, subscribe, and update in /Users/zbeyens/git/slate-v2/packages/slate/test/public-surface-contract.ts:132; root exports keep Editor type-only in /Users/zbeyens/git/slate-v2/packages/slate/src/index.ts:6.
Plate and slate-yjs migration-backbone shape0.150.94Extension namespaces are typed as state and tx groups in /Users/zbeyens/git/slate-v2/packages/slate/src/interfaces/editor.ts:433, :441, and :477; current Plate/Tiptap command sugar is deliberately rejected as raw Slate law.
Regression-proof testing strategy0.200.93Public-surface guards, render-void contracts, runtime owner audits, generated stress rows, and Mobile/IME proof rows now exist. Exact device claims remain non-claims without matching artifacts.
Research evidence completeness0.150.94Fresh local source checks covered Lexical dirty/composition/update logic, ProseMirror transaction/replace-range strategy, and Tiptap extension/command DX.
shadcn-style composability and hook/component minimalism0.100.93renderVoid receives content-only props and runtime owns hidden DOM at /Users/zbeyens/git/slate-v2/packages/slate-react/test/surface-contract.tsx:437 and :485; public hooks expose selector-style APIs from /Users/zbeyens/git/slate-v2/packages/slate-react/src/index.ts:77.

Weighted total: 0.94.

5. Source-Backed Architecture North Star

Keep:

  • model-first slate;
  • type-only public Editor;
  • editor.read((state) => ...);
  • editor.update((tx) => ...);
  • defineEditorExtension plus typed state / tx extension groups;
  • runtime-owned browser shell, selection import/export, IME, Android, repair, and kernel trace;
  • generated browser proof and conservative issue claims.

Cut from normal public API:

  • public static Editor value;
  • public primitive writer exports;
  • public SlateSpacer;
  • normal app docs that teach slate/internal;
  • command/chain sugar in raw Slate.

6. Ecosystem Strategy Synthesis

SystemSourceMechanismAvoidsStealRejectSlate targetVerdict
Lexical/Users/zbeyens/git/lexical/packages/lexical/src/LexicalUpdates.ts:243 and :965read/update discipline, dirty leaves/elements, composition key, transform loopwhole-tree work and composition corruptiondirty runtime buckets, update tags, composition-specific proofclass node model and $function public styleSlate runtime dirty ids plus read / update lifecycleagree
Lexical events/Users/zbeyens/git/lexical/packages/lexical/src/LexicalEvents.ts:161 and :179root event table plus beforeinput when availablescattered browser policycentralized input ownership and composition-aware event routingcopying Lexical command registry as raw public APISlate root runtime and event enginespartial
ProseMirror/Users/zbeyens/git/prosemirror/state/src/transaction.ts:26 and :67transaction carries doc changes, selection mapping, and metadatastale selection after editstx-local selection/read authority and metadatainteger position modelSlate path/range tx with commit metadataagree
ProseMirror replace/Users/zbeyens/git/prosemirror/transform/src/replace.ts:334fit slices by target depth and schema constraintsbroad post-hoc normalization after paste/replacebulk fragment fitting strategy for large paste/replacefull schema-first identitySlate fragment insertion fast paths with explicit proofpartial
Tiptap/Users/zbeyens/git/tiptap/packages/core/src/Extension.ts:23 and /Users/zbeyens/git/tiptap/packages/extension-code-block/src/code-block.ts:187extension configs expose command DX over ProseMirrorraw engine complexity in app codecomposable extension ergonomics for Plateraw Slate editor.commands / chain().focus().run()Plate owns product command sugar over Slate state/txdiverge

Strategy:

  • Slate should keep the hybrid already chosen: Lexical-style runtime locality, ProseMirror-style transaction/fitting discipline, and Tiptap-style extension ergonomics above the raw core.
  • The current source mostly matches this. The deslop target is remaining teaching/test ambiguity, not architecture replacement.

7. Public API Target

Accepted current shape:

  • createEditor, isEditor, defineEditorExtension, elementProperty;
  • type-only Editor;
  • pure data namespaces: Node, Path, Point, Range, Element, Text, Operation, Scrubber;
  • editor instance methods: read, update, subscribe, extend;
  • no root public static editor helper table.

Deslop target:

  • tests that are meant to prove public API should avoid slate/internal;
  • tests that prove internal transforms may keep explicit slate/internal imports;
  • docs/examples should stay stricter than tests.

8. Internal Runtime Target

Keep the runtime owner graph:

  • useEditableRootRuntime orchestrates root refs and engine wiring;
  • composition, Android, repair, selection import/export, beforeinput/input, clipboard, drag/drop, and trace stay in runtime modules;
  • React components attach stable handlers and render projection.

Do not split runtime files merely because they are large. Split only if a test or review proves an owner violation, duplicated policy body, or hot-path subscription leak.

9. Hook / Component / Render DX Target

Keep:

  • selector hooks: useEditorSelector, useEditorState, useNodeSelector, useTextSelector, useElementSelected;
  • content-only renderVoid;
  • runtime-owned hidden anchors/spacers;
  • SlateElement, SlateText, SlateLeaf, and placeholder primitives as normal public building blocks.

Reject:

  • eager selected / focused void props;
  • app-owned hidden void children;
  • public SlateSpacer.

10. Plate Migration-Backbone Target

Plate should migrate product APIs onto:

  • raw state / tx extension groups;
  • schema/spec policy;
  • selector hooks and content targets;
  • deterministic commits and local-only runtime ids.

Raw Slate should not freeze current Plate editor.api / editor.tf or Tiptap command/chain sugar as core law.

11. slate-yjs Migration-Backbone Target

slate-yjs should use:

  • deterministic operations;
  • commit metadata;
  • explicit remote replay;
  • local-only target/runtime ids;
  • schema/spec interpretation that is independent from DOM timing.

No current adapter support is required by this review.

12. Issue-Ledger Accounting

ClawSweeper related-issue pass: skipped for this pass.

Reason: this review changes no Slate v2 implementation, no issue claim, no PR fixed/improved count, and no public behavior surface. Existing issue-ledger owners remain:

  • whole-rewrite and public API hard-cut plans for API/runtime shape;
  • Mobile/IME Ralplan for input-runtime issue classifications;
  • issue coverage matrix for fixed/improved/related rows.

Issue matrix:

IssueClusterClaimWhyProof routeLive ledger syncPR line
nonearchitecture reviewNot claimedNo behavior changed in this pass.no-code reviewunchangedunchanged

Reference sync:

  • docs/slate-issues/gitcrawl-live-open-ledger.md: unchanged, no issue status change.
  • docs/slate-v2/ledgers/fork-issue-dossier.md: unchanged, no reviewed issue section added.
  • docs/slate-v2/ledgers/issue-coverage-matrix.md: unchanged, no fixed or related issue row added.
  • docs/slate-v2/references/pr-description.md: unchanged, no PR claim/API text change.

13. Legacy Regression Proof Matrix

SurfaceCurrent proofDeslop stance
Public core API/Users/zbeyens/git/slate-v2/packages/slate/test/public-surface-contract.ts:132 and :337Keep; add only if a new public surface appears.
State/tx writes/Users/zbeyens/git/slate-v2/packages/slate/src/interfaces/editor.ts:461Keep; do not add parallel public writers.
Void/render DX/Users/zbeyens/git/slate-v2/packages/slate-react/test/surface-contract.tsx:437 and :485Keep; no public spacer.
Input/IME runtime/Users/zbeyens/git/slate-v2/packages/slate-react/src/editable/runtime-root-engine.ts:199 and Mobile/IME browser rowsKeep; exact device claims need device proof.
Large rendering/Users/zbeyens/git/slate-v2/packages/slate-react/test/rendering-strategy-and-scroll.tsx:386 and generated stress budgetsKeep; budgets can be made more release-grade later.

14. Browser Stress / Parity Strategy

Keep the existing strategy:

  • unit contracts for public API and internal runtime boundaries;
  • React/JSDOM contracts for selector fanout and render shape;
  • Playwright integration rows for browser behavior;
  • generated stress rows for render budgets, shell behavior, IME, paste, selection, and replay.

Future deslop should not delete browser rows just because they look repetitive. They are behavior locks.

15. Applicable Implementation-Skill Review Matrix

LensApplicabilityFindingsPlan delta
Vercel React best practicesappliedReact remains projection; runtime subscriptions are selector/source oriented.No rewrite; split only proven owner leaks.
performance-oracleappliedDirty runtime ids, large-doc budgets, and staged/virtualized proof exist.Add release-grade p95/p99 budgets later, not a rewrite.
performanceappliedLarge repeated editor surfaces are in scope.Keep cohort metrics; no broad perf claim without budget proof.
tddappliedBehavior locks exist for public API, render, runtime, Mobile/IME.Deslop slices need focused regression proof before cleanup.
build-web-apps:shadcnskippedNo UI app surface changed in this review.No delta.
react-useeffectappliedRuntime effects synchronize DOM/browser systems.No effect rewrite without concrete leak.

16. High-Risk Deliberate-Mode Pre-Mortem

Triggered because the review covers public API, runtime, browser behavior, and migration backbone.

Failure scenarios:

  • cleanup removes an internal helper that tests need to prove runtime behavior;
  • a runtime split moves browser policy back into React components;
  • a public convenience API is added for friendliness and recreates old Slate API sprawl.

Proof plan:

  • public-surface contract after any API export change;
  • focused Slate React runtime test after any owner split;
  • browser proof after any IME, selection, paste, shell, or DOM coverage change;
  • docs/examples grep after any docs cleanup.

Rollback answer:

  • keep cleanup slices small and reversible;
  • do not combine runtime owner splits with behavioral fixes.

17. Hard Cuts And Rejected Alternatives

Hard cuts:

  • no whole rewrite;
  • no public Editor static helper value;
  • no public primitive writer exports;
  • no public SlateSpacer;
  • no raw Slate editor.commands / editor.chain;
  • no product Mobile/IME policy namespace in raw Slate;
  • no exact device issue claim from desktop/synthetic proof.

Rejected alternatives:

  • rewriting runtime facades because they are large;
  • moving Mobile/IME into core model code;
  • preserving Editor.* as friendly docs syntax;
  • importing Tiptap command DX into raw Slate;
  • treating current Plate/slate-yjs adapters as release requirements.

18. Slate Maintainer Objection Ledger

Change / stanceLikely objectionAnswerVerdict
Keep slate/internal static Editor table"This is legacy API hiding under a new path."It is explicitly internal and not exported from root; tests and internals still need a helper table. Public guards reject root export.keep
No whole rewrite"The runtime is still big."Size is not the failure criterion. Owner leakage, subscription fanout, or behavior regressions are. Existing runtime owner tests and browser rows are more valuable than churn.keep
No command/chain sugar"Tiptap DX is nicer."Product sugar belongs in Plate. Raw Slate needs primitive, unopinionated state/tx.keep
Bounded test deslop"Tests using internal helpers look messy."Internal tests may be messy when proving internals. Public API tests and docs should be stricter.keep

19. Pass Schedule And Pass-State Ledger

PassStatusEvidence addedPlan deltaOpen issuesNext owner
1. Current-state readcompletelive public API, render DX, runtime owner graph, prior closed planscreated this plannonepass 2
2. Ecosystem research refreshcompleteLexical, ProseMirror, Tiptap local source checksadded strategy tablenonepass 3
3. Deslop pressurecompletepublic/internal helper grep, docs/test surface grepchose bounded cleanup over rewriteno code edits in this skillpass 4
4. Risk and closure scorecompletescorecard, issue no-claim accounting, maintainer objectionsset score 0.94 and closed reviewnoneuser review / later Ralph if desired

20. Plan Deltas From Review

Added:

  • explicit no-rewrite verdict;
  • current live-source pointers for public API and runtime owner graph;
  • deslop scope and behavior locks;
  • ecosystem synthesis for Lexical, ProseMirror, and Tiptap;
  • issue-ledger no-claim accounting.

Dropped:

  • any suggestion to split runtime modules by size alone;
  • any suggestion to add command/chain sugar to raw Slate;
  • any suggestion to promote exact Mobile/IME issue claims.

Strengthened:

  • public docs/examples must be stricter than internal tests;
  • slate/internal helper use is acceptable only as internal proof support.

21. Open Questions

None for this review.

What would change the decision:

  • a public-surface test shows duplicate public write/read paths returned;
  • a runtime owner audit finds browser policy in React components;
  • performance proof shows broad subscription fanout;
  • a real browser/device row fails because the owner graph cannot express the fix without a deeper rewrite.

22. Implementation Phases With Owners

No implementation starts from this Ralplan. Later Ralph/deslop work, if desired:

  1. Public-doc and example grep cleanup.
  2. Test-helper audit: separate public API tests from internal proof tests.
  3. Internal static helper naming audit under slate/internal.
  4. Runtime owner audit only when a failing proof names an owner leak.
  5. Release-grade performance budgets for repeated editor surfaces.

23. Fast Driver Gates

For any later cleanup slice:

  • public API export change: bun --filter slate test:node -- public-surface-contract.ts;
  • Slate React surface change: bun --filter slate-react test:vitest -- surface-contract.tsx;
  • runtime owner change: bun --filter slate-react test:vitest -- kernel-authority-audit-contract.ts;
  • browser behavior change: matching Playwright row;
  • docs cleanup: public-surface grep test plus relevant docs/examples check.

24. Final User-Review Handoff Outline

Accepted decisions:

  • Public API: keep type-only Editor; normal editor instance stays read, update, subscribe, extend.
  • Core writes: keep writes in tx; no public primitive writer exports.
  • Internal helpers: keep slate/internal static table as internal/test owner.
  • React/runtime: keep shared root runtime; split only proven owner leaks.
  • Render DX: keep content-only renderVoid; runtime owns spacers and hidden anchors.
  • Hooks: keep selector hooks; no eager broad selection/focus props.
  • Plate: product command/UI sugar lives above raw Slate.
  • slate-yjs: rely on deterministic operations, commits, and remote replay.
  • Tests: keep browser/stress rows; deslop only where tests hide public API law.
  • Issues: no fixed/improved claim changes from this review.
  • Rewrite: rejected.

25. Final Completion Gates

GateResult
score at least 0.92pass: 0.94
no dimension below 0.85pass
live source cited for current shapepass
ecosystem synthesis completepass
issue ledger accounting explicitpass: no claims changed
high-risk deliberate mode completepass
deslop scope boundedpass
no implementation edits from Ralplanpass
final user-review handoff availablepass

Completion verdict: done.

26. Ralph Execution Grounding

Started: 2026-05-08T00:15:49+08:00.

Task statement:

  • execute the latest architecture/deslop plan after user approval via Ralph.

Desired outcome:

  • run bounded deslop slices from section 22 without rewriting Slate v2 or changing issue claims unless the cleanup actually changes a public or behavior surface.

Known facts / evidence:

  • review score is 0.94;
  • architecture verdict stays keep/no rewrite;
  • implementation phases are ordered in section 22;
  • phase 1 is public-doc and example grep cleanup.

Constraints:

  • public docs/examples must not teach slate/internal, public Editor.* helper values, public primitive writers, SlateSpacer, or command/chain sugar as normal raw Slate API;
  • internal package source and internal tests may still use slate/internal when proving internal behavior;
  • generated site/out and .next output are not source owners.

Unknowns / open questions:

  • whether phase 1 produces source edits or a recorded no-op after grep.

Likely touchpoints:

  • .tmp/slate-v2/docs;
  • .tmp/slate-v2/site/examples;
  • source-only grep results excluding generated site output.

Execution ledger:

TimePassOwnerEvidenceResultNext
2026-05-08T00:15:49+08:00deslop-passpublic docs/examplescompletion state reset to pending; active goal state refreshedstartedgrep source docs/examples and patch only real public-teaching drift
2026-05-08T00:21:19+08:00deslop-passpublic docs/examplesremoved slate/internal from .tmp/slate-v2/site/examples/ts/forced-layout.tsx; replaced stale Editor.children prose in .tmp/slate-v2/docs/libraries/slate-react/annotations.md; source grep clean; bun typecheck:site, public-surface contract, bun check, and focused forced-layout Playwright stress row passedphase 1 completephase 2 test-helper audit
2026-05-08T00:22:15+08:00deslop-passpackage testscompletion state moved to phase 2in progressgrep package tests for public-vs-internal helper ambiguity
2026-05-08T00:29:59+08:00deslop-passpackage testsmoved public read/update and generic API tests off slate/internal; focused public/helper tests passedphase 2 completephase 3 internal helper naming audit
2026-05-08T00:29:59+08:00deslop-passinternal helper namingverified source uses InternalEditor internally and root slate exports Editor type-only; slate/internal keeps the compatibility aliasphase 3 complete, no code editphase 4 runtime owner audit
2026-05-08T00:29:59+08:00deslop-passruntime owner auditno failing runtime proof named an owner leak; existing kernel authority inventory remains the owner lockphase 4 skipped by plan conditionphase 5 performance budget audit
2026-05-08T00:29:59+08:00deslop-passperformance budgetsrepeated-surface render budgets already exist in .tmp/slate-v2/playwright/stress/generated-editing.test.ts; no extra budget edit needed for this cleanup slicephase 5 complete, no code editrelease discipline and closeout
2026-05-08T00:29:59+08:00debug + verification-sweep-passcloseoutremoved stale editor.operations from .tmp/slate-v2/packages/slate-dom/test/bridge.ts; refreshed classified escape-hatch counts; bun test:release-discipline, bun lint:fix, bun check, and focused forced-layout Playwright row passed in .tmp/slate-v2execution donecompletion state done