docs/plans/2026-05-23-slate-v2-staged-architecture-cleanup-ralplan.md
Status: done
Runtime id: 019e46be-4ec4-7d11-bc6e-9fcf033a8803
Scope: staged changes in .tmp/slate-v2
Skill: slate-ralplan
Review the staged Slate v2 rewrite slice for the cleanest long-term architecture, API, DX, and test shape before it is treated as shippable.
This pass does not edit Slate v2 implementation code. It records the cleanup lane Ralph should execute next.
git diff --cached --stat and git diff --cached --name-only in
.tmp/slate-v2: 164 staged files, 15,854 insertions, 1,715 deletions.packages/slate/src/internal/root-location.tspackages/slate/src/interfaces/path-ref.tspackages/slate/src/editor/path-ref.tspackages/slate/src/transforms-text/delete-text.tspackages/slate/src/transforms-text/insert-text.tspackages/slate-history/src/history-extension.tspackages/slate-layout/src/index.tspackages/slate-layout/src/react.tsxpackages/slate-layout-pretext/*packages/slate-react/src/components/editable-text-blocks.tsxpackages/slate-react/src/components/editable.tsxpackages/slate-react/src/dom-strategy/create-segment-plan.tssite/examples/ts/pagination.tsxsite/examples/ts/multi-root-document.tsxsite/examples/ts/document-state.tsxpackages/slate-react/test/surface-contract.tsxNot absolute best yet.
The macro architecture is correct:
slate-layout;The staged diff still has avoidable architecture dirt:
PathRef leaks root metadata publicly while PointRef and RangeRef hide it;slate-layout-pretext is a package-shaped alias with no ownership;page and settings for the same concept;shell vocabulary after the public API cut;previewChars, which belongs to staged
partial-DOM previews, not viewport virtualization.Score before cleanup: 8.1 / 10 staged. Score after cleanup: 9.3 / 10 staged.
Keep packages/slate/src/internal/root-location.ts as the single authority, but
finish the job.
Ralph should:
root?: string from public PathRef;slate-history from
slate/internal;MAIN_ROOT_KEY, getOperationRoot, and range-root clones duplicated
in slate-history.Public DX target:
const ref = Editor.pathRef(editor, [0])
ref.current
ref.unref()
No public ref.root. Root binding is runtime metadata.
Cut slate-layout-pretext.
Pretext is not an optional peer strategy here; it is the Slate layout engine. Keeping a wrapper package only adds install/docs/API surface without giving users a real choice.
Target:
import { useSlateLayout } from 'slate-layout/react'
No:
import { pretextPageLayoutEngine } from 'slate-layout-pretext'
Collapse the public layout API to one obvious path.
Preferred public call:
const layout = useSlateLayout(editor, {
page: pageSettings,
root: 'main',
typography,
})
Rules:
page is the page settings source, either a state field or a literal value;settings as an alias;useSlatePageLayout path as the default docs
story unless a real second engine exists.Keep the public boundary clear:
domStrategy="full": force full DOM;domStrategy="staged": progressive full-DOM materialization;domStrategy={{ type: 'virtualized' }}: explicit degraded viewport-only DOM.Cut shell as strategy vocabulary.
Ralph should rename internal strategy values/reasons:
type: 'shell' -> internal type: 'partial-dom' or type: 'staged-preview';shell-aggressive -> partial-dom-aggressive or staged-preview;shellCount -> partial-DOM coverage names.Do not rename SlateVoidShell; that is a void-element component concept, not
DOM strategy vocabulary.
Also remove previewChars from public { type: 'virtualized' } options unless
viewport virtualization actually consumes it.
Do one extraction only if it reduces real complexity:
const domPlan = useEditableDOMStrategyPlan(...)
It should own strategy normalization, partial-DOM/staged config, virtualizer config, runtime payload, metrics, and materialization callbacks.
Do not create tiny helper functions just to look tidy. Extract the planning
block because EditableTextBlocks currently mixes editor rendering with strategy
selection.
Ralph should add or tighten tests for:
at mutates that root only;slate-history.slate-layout-pretext workspace/package/API;slate-layout.shell;type: 'shell' and
shell-aggressive;Next owner: none
Expected implementation order:
slate-layout-pretext hard cut and package/test script cleanup.Lane is closed. The cleanup was executed in .tmp/slate-v2 and verified with:
bun test ./packages/slate/test/root-location-contract.ts ./packages/slate/test/rooted-operation-contract.ts ./packages/slate-history/test/history-contract.ts ./packages/slate-history/test/document-state-history-contract.ts ./packages/slate-layout/test/page-layout-contract.test.ts ./packages/slate-layout/test/pretext-page-layout-engine.test.ts ./packages/slate-react/test/surface-contract.tsx ./packages/slate-react/test/dom-strategy-and-scroll.test.tsxbun --filter ./packages/slate typecheckbun --filter ./packages/slate-history typecheckbun --filter ./packages/slate-dom typecheckbun --filter ./packages/slate-layout typecheckbun --filter ./packages/slate-react typecheckbun lint:fixbun --filter ./packages/slate-layout testbun --filter ./packages/slate-react test:vitestCurrent staged scope in .tmp/slate-v2: 184 files, 19,110 insertions, 2,117
deletions.
Verdict: no further architecture refactor is justified.
Additional staged surfaces reviewed after the cleanup lane closed:
EditorExtensionEditorGroups and editor? setup output were
cut; state, tx, operations.apply, clipboard.insertData, normalizers,
transforms, and onCommit are the right raw Slate extension slots;isolating, selectable, keyboardSelectable,
readOnly, atom, and void; the names are explicit and unopinionated;ScrubberApi; keep DebugValueScrubber and
setDebugValueScrubber as the narrower debugging surface;shell vocabulary is gone; remaining partial-DOM naming
is internal and accurate;useSlateLayout(editor, { page, root, typography }) as the
default path and useSlatePageLayout as the advanced engine-injection path.Non-blocking release-note nit: .changeset/shell-backed-partial-paste.md
still has a stale filename, but its content says partial-DOM-backed. Rename it
only as release hygiene; it is not an architecture or DX blocker.