docs/plans/2026-04-26-slate-v2-plate-generics-type-system-plan.md
Done.
Status: done.
Actions:
generic-value-contract.ts, generic-editor-api-contract.ts,
generic-extension-contract.ts, and the React generic selector contract.ElementOrTextIn, DescendantIn, NodeIn, AncestorIn, ChildOf,
MarksOf, MarksIn, MarkKeysOf, NodeEntryIn, NodeEntryOf,
ElementEntryOf, and TextEntryOf.V extends Value through public static editor APIs for children,
operations, commits, snapshots, transactions, extension listeners, and
React selectors.Editor<V> unusable by existing internal
helpers. Public/static contracts remain the generic boundary.DescendantIn<V>[].Evidence:
bunx tsc --project packages/slate/test/tsconfig.generic-types.json --noEmit --pretty falsebunx tsc --project packages/slate-history/test/tsconfig.generic-types.json --noEmit --pretty falsebunx tsc --project packages/slate-react/test/tsconfig.generic-types.json --noEmit --pretty falsebun test ./packages/slate --bail 1bun test ./packages/slate-history --bail 1bun --cwd packages/slate-react test -- --bail 1bun test ./test/bridge.test.ts ./test/clipboard-boundary.test.ts --bail=1
from .tmp/slate-v2/packages/slate-dombun run lint:fixbunx turbo build --filter=./packages/slate --filter=./packages/slate-history --filter=./packages/slate-dom --filter=./packages/slate-react --forcebunx turbo typecheck --filter=./packages/slate --filter=./packages/slate-history --filter=./packages/slate-dom --filter=./packages/slate-react --forcebun run lintRejected tactic:
Replace Slate v2 declaration merging with a Plate-aligned generic type system, without guessing or inventing a parallel model.
The target is:
type Value = TElement[];
type Editor<V extends Value = Value> = EditorBase<V> & RuntimeEditor<V>;
Everything else derives from Value, TElement, TText, and Editor<V>.
Drop CustomTypes / ExtendedType as the primary type system.
Plate already solved the practical DX shape better than Slate legacy: document shape is carried by Value, editor APIs derive node/text/element types from that value, and plugin/editor layers add their own generics on top. Slate v2 should pull that model, not infer a new one.
The only allowed improvements over Plate are explicit:
EditorMarks = Record<string, any> with marks derived from the editor text type.Value, because Slate v2 treats operations as collaboration truth and commits as runtime truth.CustomTypes docs as the primary TypeScript story.| Plate source | Plate generic law | Slate v2 target | Action | Drift allowed |
|---|---|---|---|---|
../plate/packages/slate/src/interfaces/editor/editor-type.ts | Editor<V extends Value = Value>, EditorBase<V>, EditorMethods<V>, Value, ValueOf<E>, EditorSelection | .tmp/slate-v2/packages/slate/src/interfaces/editor.ts, .tmp/slate-v2/packages/slate/src/create-editor.ts, .tmp/slate-v2/packages/slate/src/core/public-state.ts | Port the Value-first editor model. Keep Slate v2 read/update/transaction fields in the runtime portion. | No, except marks improvement. |
../plate/packages/slate/src/interfaces/element.ts | TElement, Element = TElement, ElementIn<V>, ElementOf<N>, ElementOrTextOf<E> | .tmp/slate-v2/packages/slate/src/interfaces/element.ts, .tmp/slate-v2/packages/slate/src/interfaces/node.ts | Replace ExtendedType<'Element'> with direct generic helpers. | No. |
../plate/packages/slate/src/interfaces/text.ts | TText, Text = TText, TextIn<V>, TextOf<N> | .tmp/slate-v2/packages/slate/src/interfaces/text.ts, .tmp/slate-v2/packages/slate/src/interfaces/editor.ts | Replace ExtendedType<'Text'>; derive marks from TextOf<E>. | Yes: improve marks. |
../plate/packages/slate/src/interfaces/node.ts | TNode, Ancestor, Descendant, NodeOf<N>, AncestorOf<N>, DescendantOf<N>, NodeProps<N> | .tmp/slate-v2/packages/slate/src/interfaces/node.ts | Port helper structure directly. | No. |
../plate/packages/slate/src/interfaces/node-entry.ts | NodeEntryOf<E>, ElementEntryOf<E>, TextEntryOf<E>, AncestorEntryOf<E>, DescendantEntryOf<E> | .tmp/slate-v2/packages/slate/src/interfaces/node-entry.ts or .tmp/slate-v2/packages/slate/src/interfaces/node.ts | Add if missing; do not scatter entry helpers across editor APIs. | No. |
../plate/packages/slate/src/interfaces/editor/editor-api.ts | Every editor query/match option is generic on V extends Value where node shape matters. | .tmp/slate-v2/packages/slate/src/interfaces/editor.ts, .tmp/slate-v2/packages/slate/src/editor/*.ts | Thread V through editor options and return types. | No. |
../plate/packages/slate/src/interfaces/editor/editor-transforms.ts | EditorTransforms<V>, transform options generic by V, transform node types derived from ValueOf<E> | .tmp/slate-v2/packages/slate/src/interfaces/transforms/*.ts, .tmp/slate-v2/packages/slate/src/transforms-*/*.ts, .tmp/slate-v2/packages/slate/src/editor/*.ts | Preserve flexible primitives; do not invent semantic method bloat. | No. |
../plate/packages/slate/src/interfaces/editor/legacy-editor.ts | Compat bridge for legacy transform shape. | None as primary. Optional internal-only bridge if a migration tracer proves it is needed. | Do not port as public API. | Yes: hard cut public compat. |
../plate/packages/core/src/lib/editor/SlateEditor.ts | TSlateEditor<V, P> layers plugin config generics over Slate editor generics. | .tmp/slate-v2/packages/slate/src/core/editor-extension.ts, .tmp/slate-v2/packages/slate/src/core/extension-registry.ts | Use as the extension generic model, not the core node model. | No in spirit; names can fit Slate v2. |
../plate/packages/core/src/lib/editor/withSlate.ts | withSlate<V, P> preserves editor value and plugin generics. | .tmp/slate-v2/packages/slate/src/core/editor-extension.ts, package consumers | Keep extension runtime generic over E extends Editor<V>. | No. |
../plate/packages/core/src/react/editor/PlateEditor.ts | TPlateEditor<V, P> extends Slate editor with React/plugin runtime. | .tmp/slate-v2/packages/slate-react/src/plugin/react-editor.ts, .tmp/slate-v2/packages/slate-react/src/context.tsx | Use the layering idea, not Plate plugin runtime implementation. | No in architecture; implementation differs. |
Plate:
type EditorMarks = Record<string, any>;
Slate v2 target:
type MarksOfText<T extends TText> = Partial<Omit<T, "text">>;
type EditorMarksOf<E extends Editor> = MarksOfText<TextOf<E>>;
type EditorMarks<V extends Value = Value> = MarksOfText<TextIn<V>>;
Reason: mark keys should come from the editor's text union, not any. This keeps custom mark DX flexible without losing agent/type guidance.
Plate keeps operation payloads mostly at static Node / Operation shape. Slate v2 should generic-thread them:
type Operation<V extends Value = Value> = ...
type EditorTransaction<V extends Value = Value> = ...
type EditorSnapshot<V extends Value = Value> = ...
type EditorCommit<V extends Value = Value> = ...
Reason: operations are Slate v2 collaboration truth and commits are local runtime truth. If operation payloads collapse to untyped Node, the generic model leaks exactly where history, Yjs, React dirtiness, and tests need precision.
If this is too wide for the first implementation slice, phase it:
Do not skip it permanently.
Do not replace Value with a schema object. Add a helper only for authoring:
type EditorSchema<
TElementUnion extends TElement = TElement,
TTextUnion extends TText = TText,
> = {
Element: TElementUnion;
Text: TTextUnion;
};
type ValueOfSchema<S extends EditorSchema> = S["Element"][];
Reason: plugin authors often think in element/text unions; Plate-compatible core still thinks in Value.
| Type | Meaning | Owner file |
| ---------------------- | -------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------- | ----------------------------------------------------- |
| TText | Base text node with { text: string } plus custom properties. | .tmp/slate-v2/packages/slate/src/interfaces/text.ts |
| TElement | Base element node with children. | .tmp/slate-v2/packages/slate/src/interfaces/element.ts |
| Value | Top-level editor document: TElement[]. | .tmp/slate-v2/packages/slate/src/interfaces/editor.ts or .tmp/slate-v2/packages/slate/src/interfaces/node.ts |
| Text | Alias of TText, not declaration-merged app text. | .tmp/slate-v2/packages/slate/src/interfaces/text.ts |
| Element | Alias of TElement, not declaration-merged app element. | .tmp/slate-v2/packages/slate/src/interfaces/element.ts |
| Descendant | TElement | TText. | .tmp/slate-v2/packages/slate/src/interfaces/node.ts |
| Ancestor | Editor | TElement. | .tmp/slate-v2/packages/slate/src/interfaces/node.ts |
| Node | Editor | TElement | TText. | .tmp/slate-v2/packages/slate/src/interfaces/node.ts |
| TextIn<V> | Text union inside V. | .tmp/slate-v2/packages/slate/src/interfaces/text.ts |
| ElementIn<V> | Element union inside V. | .tmp/slate-v2/packages/slate/src/interfaces/element.ts |
| NodeIn<V> | Node union inside V. | .tmp/slate-v2/packages/slate/src/interfaces/node.ts |
| TextOf<E> | Text union inside editor/node E. | .tmp/slate-v2/packages/slate/src/interfaces/text.ts |
| ElementOf<E> | Element union inside editor/node E. | .tmp/slate-v2/packages/slate/src/interfaces/element.ts |
| NodeOf<E> | Node union inside editor/node E. | .tmp/slate-v2/packages/slate/src/interfaces/node.ts |
| ValueOf<E> | E['children']. | .tmp/slate-v2/packages/slate/src/interfaces/editor.ts |
| EditorMarksOf<E> | Mark object derived from TextOf<E>. | .tmp/slate-v2/packages/slate/src/interfaces/editor.ts |
| Operation<V> | Operation payloads typed from NodeIn<V> / Range. | .tmp/slate-v2/packages/slate/src/interfaces/operation.ts |
| Editor<V> | Public editor type. | .tmp/slate-v2/packages/slate/src/interfaces/editor.ts |
| EditorTransaction<V> | Write boundary runtime. | .tmp/slate-v2/packages/slate/src/interfaces/editor.ts, .tmp/slate-v2/packages/slate/src/core/public-state.ts |
| EditorCommit<V> | Local runtime observation payload. | .tmp/slate-v2/packages/slate/src/interfaces/editor.ts, .tmp/slate-v2/packages/slate/src/core/public-state.ts |
| HistoryEditor<V> | History-enhanced editor. | .tmp/slate-v2/packages/slate-history/src/history-editor.ts |
| DOMEditor<V> | DOM-enhanced editor. | .tmp/slate-v2/packages/slate-dom/src/plugin/dom-editor.ts |
| ReactEditor<V> | React-enhanced editor. | .tmp/slate-v2/packages/slate-react/src/plugin/react-editor.ts |
Legend:
G0: no generic change expected; verify only.G1: base model generic migration.G2: editor API / transform generic threading.G3: operation / transaction / commit generic threading.G4: package editor wrapper generic threading.G5: app, docs, test, and fixture migration away from declaration merging.packages/slate| File or file group | Class | Required work |
| -------------------------------------------------------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----- | ----- | -------------------------------- |
| .tmp/slate-v2/packages/slate/src/types/custom-types.ts | G1 | Delete or replace with a non-augmenting generic helper file. Remove CustomTypes and ExtendedType. |
| .tmp/slate-v2/packages/slate/src/types/index.ts | G1 | Stop exporting declaration-merging helpers. Export generic helpers if they live under types. |
| .tmp/slate-v2/packages/slate/src/types/types.ts | G1 | Audit for old aliases that assume global custom types. |
| .tmp/slate-v2/packages/slate/src/interfaces/text.ts | G1 | Replace ExtendedType<'Text'> with TText, Text, TextIn<V>, TextOf<N>. |
| .tmp/slate-v2/packages/slate/src/interfaces/element.ts | G1 | Replace ExtendedType<'Element'> with TElement, Element, ElementIn<V>, ElementOf<N>, ElementOrTextOf<E>. |
| .tmp/slate-v2/packages/slate/src/interfaces/node.ts | G1 | Add Plate-compatible TNode, NodeIn<V>, NodeOf<N>, AncestorOf<N>, DescendantOf<N>, NodeProps<N>. |
| .tmp/slate-v2/packages/slate/src/interfaces/editor.ts | G1/G2/G3 | Make Editor<V>, BaseEditor<V>, Value, ValueOf<E>, EditorMarks<V>, EditorMarksOf<E>, EditorSnapshot<V>, EditorTransaction<V>, EditorCommit<V>, extension and command types generic. |
| .tmp/slate-v2/packages/slate/src/interfaces/operation.ts | G3 | Remove ExtendedType from operation subtypes. Add Operation<V>, InsertNodeOperation<V>, RemoveNodeOperation<V>, SplitNodeOperation<V>, MergeNodeOperation<V>, SetNodeOperation<V> with typed node payloads. |
| .tmp/slate-v2/packages/slate/src/interfaces/range.ts | G1 | Replace ExtendedType<'Range'> with direct Range. Do not generic-thread unless range metadata becomes real. |
| .tmp/slate-v2/packages/slate/src/interfaces/point.ts | G1 | Replace ExtendedType<'Point'> with direct Point. |
| .tmp/slate-v2/packages/slate/src/interfaces/location.ts | G1 | Verify Location uses direct Path | Point | Range | Span and no global custom type. |
| .tmp/slate-v2/packages/slate/src/interfaces/bookmark.ts | G3 | Make bookmarks explicit about selection/node generics if bookmark stores node snapshots. |
| .tmp/slate-v2/packages/slate/src/interfaces/path.ts | G0 | Verify path stays structural, not generic. |
| .tmp/slate-v2/packages/slate/src/interfaces/path-ref.ts | G0 | Verify no global custom type. |
| .tmp/slate-v2/packages/slate/src/interfaces/point-ref.ts | G0 | Verify no global custom type. |
| .tmp/slate-v2/packages/slate/src/interfaces/range-ref.ts | G0 | Verify no global custom type. |
| .tmp/slate-v2/packages/slate/src/interfaces/scrubber.ts | G0 | Verify only accepts structural values. |
| .tmp/slate-v2/packages/slate/src/interfaces/index.ts | G1/G2/G3 | Re-export the generic vocabulary. Do not re-export CustomTypes / ExtendedType. |
| .tmp/slate-v2/packages/slate/src/interfaces/transforms/general.ts | G2 | Thread V through transform options that accept Node, Descendant, Element, Text, or match predicates. |
| .tmp/slate-v2/packages/slate/src/interfaces/transforms/node.ts | G2 | Thread V; nodes, match, at, inserted node payloads derive from Value. |
| .tmp/slate-v2/packages/slate/src/interfaces/transforms/selection.ts | G2 | Verify direct Range / Point; no declaration merging. |
| .tmp/slate-v2/packages/slate/src/interfaces/transforms/text.ts | G2 | Thread text type through marks and insertion options. |
| .tmp/slate-v2/packages/slate/src/create-editor.ts | G1/G3 | createEditor<V extends Value = Value>(): Editor<V>. Construct base editor internally; cast only at the boundary. |
| .tmp/slate-v2/packages/slate/src/index.ts and .tmp/slate-v2/packages/slate/index.ts | G1/G2/G3 | Public export audit; no CustomTypes primary export. |
| .tmp/slate-v2/packages/slate/src/core/apply.ts | G3 | applyOperation<V> and operation middleware use Operation<V>. |
| .tmp/slate-v2/packages/slate/src/core/public-state.ts | G1/G3 | Generic current value, selection, marks, operations, snapshots, transactions, and commits. |
| .tmp/slate-v2/packages/slate/src/core/command-registry.ts | G3 | Command context uses E extends Editor<V>. |
| .tmp/slate-v2/packages/slate/src/core/editor-extension.ts | G3 | Extension methods use E extends Editor<V>, not BaseEditor = Editor without value. |
| .tmp/slate-v2/packages/slate/src/core/extension-registry.ts | G3 | Registry stores typed methods/listeners without erasing V. |
| .tmp/slate-v2/packages/slate/src/core/batch-dirty-paths.ts | G3 | Dirty paths stay path-based; commit metadata generic only if node payload leaks in. |
| .tmp/slate-v2/packages/slate/src/core/get-dirty-paths.ts | G3 | Same as dirty paths. |
| .tmp/slate-v2/packages/slate/src/core/update-dirty-paths.ts | G3 | Operation generic input. |
| .tmp/slate-v2/packages/slate/src/core/get-fragment.ts | G2/G3 | Fragment return type derives from ValueOf<E>. |
| .tmp/slate-v2/packages/slate/src/core/leaf-lifecycle.ts | G1/G3 | Empty leaf normalization must use TextOf<E> / marks type, not untyped text. |
| .tmp/slate-v2/packages/slate/src/core/normalize-node.ts | G2 | Normalize entry and node types derive from E. |
| .tmp/slate-v2/packages/slate/src/core/should-normalize.ts | G2 | Options generic only if node entry payloads are exposed. |
| .tmp/slate-v2/packages/slate/src/core/index.ts | G1/G2/G3 | Re-export audit. |
| .tmp/slate-v2/packages/slate/src/editor/*.ts | G2 | Every editor query/mutation must accept E extends Editor and derive ValueOf<E>, NodeOf<E>, ElementOf<E>, TextOf<E>, EditorMarksOf<E>. |
| .tmp/slate-v2/packages/slate/src/transforms-node/*.ts | G2 | Preserve flexible primitives; replace static Node / Element / Descendant inputs with derived generic types. |
| .tmp/slate-v2/packages/slate/src/transforms-selection/*.ts | G2 | Verify selection transforms do not import global custom types; keep structural points/ranges. |
| .tmp/slate-v2/packages/slate/src/transforms-text/*.ts | G2 | insertText, deleteText, mark behavior derive marks from text type. |
| .tmp/slate-v2/packages/slate/src/utils/types.ts | G1/G2 | Central helper type cleanup; remove ExtendedType assumptions. |
| .tmp/slate-v2/packages/slate/src/utils/runtime-ids.ts | G0/G3 | Keep runtime IDs structural; only generic if public node payloads are exposed. |
| .tmp/slate-v2/packages/slate/src/utils/get-default-insert-location.ts | G2 | Insert location uses E extends Editor. |
| .tmp/slate-v2/packages/slate/src/utils/modify.ts | G2 | Verify point/range only. |
| .tmp/slate-v2/packages/slate/src/utils/*.ts | G0 | Verify no hidden CustomTypes import or global Element/Text assumption. |
| .tmp/slate-v2/packages/slate/src/range-projection.ts | G3 | Generic only if projection payloads carry nodes. |
| .tmp/slate-v2/packages/slate/src/selection-operation.ts | G3 | Selection op stays structural, but union must compose with Operation<V>. |
| .tmp/slate-v2/packages/slate/src/text-units.ts | G0 | Text unit logic stays structural. |
| .tmp/slate-v2/packages/slate/test/**/*.ts and .tmp/slate-v2/packages/slate/test/**/*.tsx | G5 | Replace custom-type fixture tests with explicit createEditor<ExampleValue>() compile/runtime contracts. Delete tsconfig.custom-types.json. |
packages/slate-dom| File or file group | Class | Required work |
|---|---|---|
.tmp/slate-v2/packages/slate-dom/src/custom-types.ts | G4/G5 | Delete module augmentation. Move DOM-only global declarations to a non-Slate globals.d.ts if needed. |
.tmp/slate-v2/packages/slate-dom/src/plugin/dom-editor.ts | G4 | DOMEditor<V extends Value = Value> and DOM helpers accept E extends Editor<V>. |
.tmp/slate-v2/packages/slate-dom/src/plugin/with-dom.ts | G4/G5 | Replace CustomTypes comments with generic editor examples. Preserve runtime behavior. |
.tmp/slate-v2/packages/slate-dom/src/index.ts and .tmp/slate-v2/packages/slate-dom/index.ts | G4 | Re-export generic DOM editor types. |
.tmp/slate-v2/packages/slate-dom/src/utils/types.ts | G4 | DOM data attributes and weak-map helpers typed against generic Slate nodes where needed. |
.tmp/slate-v2/packages/slate-dom/src/utils/range-list.ts | G0 | Verify structural ranges only. |
.tmp/slate-v2/packages/slate-dom/src/utils/*.ts | G0/G4 | Only generic-thread files that mention Slate nodes/editor. |
.tmp/slate-v2/packages/slate-dom/test/**/*.ts | G5 | Replace any module augmentation expectations with explicit generic examples. |
packages/slate-history| File or file group | Class | Required work |
|---|---|---|
.tmp/slate-v2/packages/slate-history/src/history.ts | G3/G4 | History<V> stores Operation<V>[] / EditorCommit<V> as appropriate. |
.tmp/slate-v2/packages/slate-history/src/history-editor.ts | G4 | HistoryEditor<V extends Value = Value>. |
.tmp/slate-v2/packages/slate-history/src/with-history.ts | G4/G5 | withHistory<V, E extends Editor<V>>(editor: E): E & HistoryEditor<V>. Remove CustomTypes comments. |
.tmp/slate-v2/packages/slate-history/src/index.ts and .tmp/slate-v2/packages/slate-history/index.ts | G4 | Export generic history types. |
.tmp/slate-v2/packages/slate-history/test/**/*.ts and .tmp/slate-v2/packages/slate-history/test/**/*.tsx | G5 | Add at least one custom Value history compile/runtime contract. |
packages/slate-hyperscript| File or file group | Class | Required work |
|---|---|---|
.tmp/slate-v2/packages/slate-hyperscript/src/creators.ts | G5 | Hyperscript output type parameterizes editor/value where callers specify it. |
.tmp/slate-v2/packages/slate-hyperscript/src/hyperscript.ts | G5 | Allow explicit Value for JSX fixture creators; do not rely on module augmentation. |
.tmp/slate-v2/packages/slate-hyperscript/src/tokens.ts | G0/G5 | Verify structural token types. |
.tmp/slate-v2/packages/slate-hyperscript/src/index.ts and .tmp/slate-v2/packages/slate-hyperscript/index.ts | G5 | Export generic creator signatures. |
.tmp/slate-v2/packages/slate-hyperscript/test/**/*.ts, .tmp/slate-v2/packages/slate-hyperscript/test/**/*.tsx, .tmp/slate-v2/packages/slate-hyperscript/test/fixtures/**/*.tsx | G5 | Replace any hidden global custom type dependency with explicit helper value types. |
packages/slate-react| File or file group | Class | Required work |
|---|---|---|
.tmp/slate-v2/packages/slate-react/src/custom-types.ts | G4/G5 | Delete module augmentation. Move React-only ambient declarations elsewhere if truly needed. |
.tmp/slate-v2/packages/slate-react/src/plugin/react-editor.ts | G4 | ReactEditor<V extends Value = Value> layered over DOMEditor<V> and Editor<V>. |
.tmp/slate-v2/packages/slate-react/src/plugin/with-react.ts | G4/G5 | Generic withReact; remove CustomTypes comments. |
.tmp/slate-v2/packages/slate-react/src/context.tsx | G4 | Context stores generic editor internally without leaking app-wide module augmentation. |
.tmp/slate-v2/packages/slate-react/src/components/slate.tsx | G4 | <Slate> props generic on V / E extends ReactEditor<V>. |
.tmp/slate-v2/packages/slate-react/src/components/editable.tsx | G4 | Editable props generic over E; render props derive ElementOf<E> / TextOf<E>. |
.tmp/slate-v2/packages/slate-react/src/components/editable-text-blocks.tsx | G4 | Replace local TElement extends SlateElementNode workaround with ElementOf<E>. |
.tmp/slate-v2/packages/slate-react/src/components/slate-element.tsx | G4 | Element prop generic derives from editor context. |
.tmp/slate-v2/packages/slate-react/src/components/editable-element.tsx | G4 | Same as element rendering. |
.tmp/slate-v2/packages/slate-react/src/components/slate-text.tsx | G4 | Text prop generic derives from TextOf<E>. |
.tmp/slate-v2/packages/slate-react/src/components/editable-text.tsx | G4 | Same as text rendering. |
.tmp/slate-v2/packages/slate-react/src/components/slate-leaf.tsx | G4 | Leaf and marks typed from TextOf<E>. |
.tmp/slate-v2/packages/slate-react/src/components/text-string.tsx | G0/G4 | Verify text only. |
.tmp/slate-v2/packages/slate-react/src/components/zero-width-string.tsx | G0 | No model generic. |
.tmp/slate-v2/packages/slate-react/src/components/slate-placeholder.tsx | G4 | Placeholder element typed from ElementOf<E> if exposed. |
.tmp/slate-v2/packages/slate-react/src/components/void-element.tsx | G4 | Void element typed from ElementOf<E>. |
.tmp/slate-v2/packages/slate-react/src/components/slate-spacer.tsx | G0/G4 | Verify node props only if exposed. |
.tmp/slate-v2/packages/slate-react/src/components/restore-dom/*.tsx and *.ts | G0/G4 | DOM repair remains structural; generic only if editor typed in public functions. |
.tmp/slate-v2/packages/slate-react/src/editable/*.ts | G4 | Editing kernel and controllers accept ReactEditor<V>; no stale custom type import. |
.tmp/slate-v2/packages/slate-react/src/dom-text-sync.ts | G4 | Generic editor where Slate nodes are accepted. |
.tmp/slate-v2/packages/slate-react/src/projection-store.ts | G4 | Projection payloads use Range, TextOf<E>, ElementOf<E> where relevant. |
.tmp/slate-v2/packages/slate-react/src/annotation-store.ts | G4 | Annotation payloads generic over editor value when Slate nodes are exposed. |
.tmp/slate-v2/packages/slate-react/src/widget-store.ts | G4 | Widget anchors generic only if node payloads leak. |
.tmp/slate-v2/packages/slate-react/src/projection-context.tsx | G4 | Context typed from generic projection store. |
.tmp/slate-v2/packages/slate-react/src/large-document/*.ts and *.tsx | G4 | Large-doc shell editor/value generics preserved. |
.tmp/slate-v2/packages/slate-react/src/hooks/**/*.ts and .tmp/slate-v2/packages/slate-react/src/hooks/**/*.tsx if present | G4 | Hook return types derive from generic context. |
.tmp/slate-v2/packages/slate-react/src/index.ts and .tmp/slate-v2/packages/slate-react/index.ts | G4 | Export generic React types. |
.tmp/slate-v2/packages/slate-react/test/**/*.ts and .tmp/slate-v2/packages/slate-react/test/**/*.tsx | G5 | Add generic custom value render contract; remove module augmentation use. |
packages/slate-browser| File or file group | Class | Required work |
|---|---|---|
.tmp/slate-v2/packages/slate-browser/src/browser/*.ts | G5 | Browser snapshots are test protocol types, not Slate model generics. Verify names do not conflict with EditorSnapshot<V>. |
.tmp/slate-v2/packages/slate-browser/src/playwright/*.ts | G5 | Test handles remain protocol-level. Add scenarios that prove custom value examples compile/run without CustomTypes. |
.tmp/slate-v2/packages/slate-browser/test/**/*.ts | G5 | Add generic app fixture where useful. |
| File or file group | Class | Required work |
|---|---|---|
.tmp/slate-v2/site/examples/ts/custom-types.d.ts | G5 | Delete module augmentation. Replace with exported ExampleText, ExampleElement, ExampleValue, ExampleEditor from a normal module. |
.tmp/slate-v2/site/examples/ts/code-highlighting.tsx | G5 | Remove local declare module 'slate'; use explicit value/editor types. |
.tmp/slate-v2/site/examples/ts/*.tsx | G5 | Every example imports or declares its own Value / editor type when needed. No app-wide custom type pollution. |
.tmp/slate-v2/site/components/*.tsx | G5 | Example loader must not depend on global custom Slate types. |
.tmp/slate-v2/site/pages/**/*.tsx and .tmp/slate-v2/site/pages/**/*.ts | G5 | Site pages compile against package source aliases after module augmentation is gone. |
.tmp/slate-v2/site/next-env.d.ts | G0/G5 | Verify no Slate declaration merging. |
.tmp/slate-v2/docs/concepts/12-typescript.md | G5 | Rewrite around Value, TElement, TText, Editor<Value>. |
.tmp/slate-v2/docs/walkthroughs/01-installing-slate.md | G5 | Replace declare module 'slate' with generic editor setup. |
.tmp/slate-v2/docs/walkthroughs/*.md | G5 | Remove CustomTypes mental model and Transforms.* if generics examples touch mutation. |
.tmp/slate-v2/docs/api/nodes/*.md | G5 | Document Value, TElement, TText, Editor<V>, derived helpers. |
.tmp/slate-v2/docs/api/operations/*.md | G5 | Document Operation<V> if operation generics land. |
.tmp/slate-v2/docs/api/transforms.md | G5 | Mark Transforms.* as non-primary if still exported; show editor primitives in editor.update. |
.tmp/slate-v2/docs/libraries/slate-dom/**/*.md | G5 | Replace CustomTypes comments with generic editor examples. |
.tmp/slate-v2/docs/libraries/slate-react/**/*.md | G5 | Replace CustomTypes comments with generic React editor examples. |
.tmp/slate-v2/docs/libraries/slate-history/**/*.md | G5 | Replace CustomTypes comments with generic history editor examples. |
.tmp/slate-v2/docs/general/changelog.md | G5 | Historical mentions can remain only if changelog is intentionally historical; do not use as current guidance. |
Before implementation, generate the exact file checklist from disk and paste the result into the execution ledger:
rg --files .tmp/slate-v2/packages/slate .tmp/slate-v2/packages/slate-dom .tmp/slate-v2/packages/slate-history .tmp/slate-v2/packages/slate-hyperscript .tmp/slate-v2/packages/slate-react .tmp/slate-v2/packages/slate-browser .tmp/slate-v2/site .tmp/slate-v2/docs \
| rg '(\\.ts$|\\.tsx$|\\.d\\.ts$|\\.md$|\\.mdx$)' \
> .tmp/slate-v2-generics-file-inventory.txt
Implementation is not complete until every file in that inventory is classified as G0 through G5 in the execution ledger.
Add compile-only contracts before cutting anything:
.tmp/slate-v2/packages/slate/test/generic-value-contract.ts.tmp/slate-v2/packages/slate/test/generic-editor-api-contract.ts.tmp/slate-v2/packages/slate/test/generic-operation-contract.ts.tmp/slate-v2/packages/slate-react/test/generic-react-editor-contract.tsx.tmp/slate-v2/packages/slate-history/test/generic-history-contract.tsRequired contracts:
type Paragraph = { type: "paragraph"; children: CustomText[] };
type Quote = { type: "quote"; children: CustomText[] };
type CustomText = { text: string; bold?: true; code?: true };
type CustomValue = (Paragraph | Quote)[];
const editor = createEditor<CustomValue>();
editor.update(() => {
editor.setNodes({ type: "quote" });
editor.addMark("bold", true);
});
Assertions:
ValueOf<typeof editor> is CustomValue.ElementOf<typeof editor> is Paragraph | Quote.TextOf<typeof editor> is CustomText.EditorMarksOf<typeof editor> accepts bold / code and rejects element fields.Operation<CustomValue> payloads do not collapse to untyped Node.withHistory(editor) preserves CustomValue.withReact(editor) preserves CustomValue.Files:
packages/slate/src/types/custom-types.tspackages/slate/src/types/index.tspackages/slate/src/types/types.tspackages/slate/src/interfaces/text.tspackages/slate/src/interfaces/element.tspackages/slate/src/interfaces/node.tspackages/slate/src/interfaces/editor.tspackages/slate/src/interfaces/range.tspackages/slate/src/interfaces/point.tspackages/slate/src/interfaces/operation.tspackages/slate/src/interfaces/index.tspackages/slate/src/create-editor.tspackages/slate/src/index.tspackages/slate/index.tsWork:
Value, TElement, TText, derived node helpers.ExtendedType from primary model types.Editor to Editor<V>.createEditor to createEditor<V>().Files:
packages/slate/src/interfaces/editor.tspackages/slate/src/interfaces/transforms/*.tspackages/slate/src/editor/*.tspackages/slate/src/transforms-node/*.tspackages/slate/src/transforms-selection/*.tspackages/slate/src/transforms-text/*.tspackages/slate/src/utils/get-default-insert-location.tspackages/slate/src/utils/types.tsWork:
E extends Editor.ValueOf<E>.setNodes, unwrapNodes, wrapNodes, insertNodes, delete, insertText, insertFragment.Files:
packages/slate/src/interfaces/operation.tspackages/slate/src/interfaces/editor.tspackages/slate/src/core/apply.tspackages/slate/src/core/public-state.tspackages/slate/src/core/command-registry.tspackages/slate/src/core/editor-extension.tspackages/slate/src/core/extension-registry.tspackages/slate/src/core/update-dirty-paths.tspackages/slate/src/core/get-dirty-paths.tspackages/slate/src/core/batch-dirty-paths.tspackages/slate-history/src/history.tspackages/slate-history/src/history-editor.tspackages/slate-history/src/with-history.tsWork:
Operation<V> becomes the operation truth.EditorTransaction<V> and EditorCommit<V> preserve value type.E extends Editor<V>.Files:
packages/slate-dom/src/custom-types.tspackages/slate-dom/src/plugin/dom-editor.tspackages/slate-dom/src/plugin/with-dom.tspackages/slate-dom/src/index.tspackages/slate-dom/index.tspackages/slate-react/src/custom-types.tspackages/slate-react/src/plugin/react-editor.tspackages/slate-react/src/plugin/with-react.tspackages/slate-react/src/context.tsxpackages/slate-react/src/components/*.tsxpackages/slate-react/src/components/restore-dom/*packages/slate-react/src/editable/*.tspackages/slate-react/src/large-document/*packages/slate-react/src/*.tspackages/slate-react/src/*.tsxpackages/slate-react/src/index.tspackages/slate-react/index.tsWork:
ReactEditor<V> / DOMEditor<V>.Files:
packages/slate-hyperscript/src/*.tspackages/slate-hyperscript/test/**/*.tspackages/slate-hyperscript/test/**/*.tsxpackages/slate/test/**/*.tspackages/slate/test/**/*.tsxpackages/slate-dom/test/**/*.tspackages/slate-history/test/**/*.tspackages/slate-history/test/**/*.tsxpackages/slate-react/test/**/*.tspackages/slate-react/test/**/*.tsxsite/examples/ts/custom-types.d.tssite/examples/ts/*.tsxsite/components/*.tsxsite/pages/**/*.tssite/pages/**/*.tsxdocs/**/*.mddocs/**/*.mdxWork:
declare module 'slate' with normal exported app types.ExampleValue / ExampleEditor types.Value generics.CustomTypes.Blocked today by app CustomTypes leaking into package internals. Reopen after Batch 5.
Work:
Run from .tmp/slate-v2.
rg -n "CustomTypes|ExtendedType|declare module ['\\\"]slate|declare module \\\"slate\\\"" packages site docs --glob '!**/dist/**' --glob '!**/node_modules/**' --glob '!site/out/**'
Expected:
packages/**, site/**, and current docs.rg -n "EditorMarks = Record<string, any>|Record<string, any>" packages/slate/src packages/slate-react/src packages/slate-dom/src packages/slate-history/src
Expected:
any.bunx tsc --project packages/slate/test/tsconfig.generic-types.json --noEmit --pretty false
bunx tsc --project packages/slate-history/test/tsconfig.generic-types.json --noEmit --pretty false
bunx tsc --project packages/slate-react/test/tsconfig.generic-types.json --noEmit --pretty false
bunx turbo typecheck --filter=./packages/slate --filter=./packages/slate-history --filter=./packages/slate-dom --filter=./packages/slate-react --filter=./packages/slate-hyperscript --force
bun test ./packages/slate --bail 1
bun test ./packages/slate-history --bail 1
bun test ./packages/slate-dom --bail 1
bun test ./packages/slate-react --bail 1
bun test ./packages/slate-hyperscript --bail 1
bun typecheck:site
bun typecheck
bun run lint:fix
bun run lint
Do not run bun test:integration-local for this type-system plan unless a React/browser runtime file change proves behavior changed.
CustomTypes as "temporary" after core generics land. That creates two truth sources.Editor<V> infer the site example value from ambient context. That recreates the current source-first typecheck bug.EditorMarks = Record<string, any> unchanged.as unknown as Editor.packages/slate helpers are stable.This plan is complete when:
CustomTypes / ExtendedType are gone from primary packages and current docs.createEditor<CustomValue>() is the canonical custom typing entrypoint.Editor<V>, ValueOf<E>, ElementOf<E>, TextOf<E>, NodeOf<E>, and EditorMarksOf<E> work across core, DOM, React, history, hyperscript, site examples, and tests.V.complete-plan, set active goal state to pending, generated active goal state.active goal state, updated active goal state.ExtendedType in interfaces/editor.ts, interfaces/text.ts, interfaces/element.ts, interfaces/range.ts, interfaces/point.ts, and interfaces/operation.ts; DOM/React still have custom-types.ts declaration augmentation files.active goal state, active goal state, docs/plans/2026-04-26-slate-v2-plate-generics-type-system-plan.md..tmp/slate-v2-generics-file-inventory.txt, add the first generic contract tests, run focused gates, and record expected failures.rg --files .tmp/slate-v2/packages/slate .tmp/slate-v2/packages/slate-dom .tmp/slate-v2/packages/slate-history .tmp/slate-v2/packages/slate-hyperscript .tmp/slate-v2/packages/slate-react .tmp/slate-v2/packages/slate-browser .tmp/slate-v2/site .tmp/slate-v2/docs | rg '(\\.ts$|\\.tsx$|\\.d\\.ts$|\\.md$|\\.mdx$)' > .tmp/slate-v2/tmp/slate-v2-generics-file-inventory.txtbunx tsc --project tsconfig.generic-types.json --noEmit --pretty falsebunx tsc --project packages/slate/test/tsconfig.generic-types.json --noEmit --pretty falsebunx tsc --project packages/slate-history/test/tsconfig.generic-types.json --noEmit --pretty falsebunx tsc --project packages/slate-react/test/tsconfig.generic-types.json --noEmit --pretty false.tmp/slate-v2/tmp/slate-v2-generics-file-inventory.txt with 1502 files..tmp/slate-v2/packages/slate/test/generic-value-contract.ts.tmp/slate-v2/packages/slate/test/generic-editor-api-contract.ts.tmp/slate-v2/packages/slate/test/generic-operation-contract.ts.tmp/slate-v2/packages/slate-history/test/generic-history-contract.ts.tmp/slate-v2/packages/slate-react/test/generic-react-editor-contract.tsx.tmp/slate-v2/packages/slate/test/tsconfig.generic-types.json.tmp/slate-v2/packages/slate-history/test/tsconfig.generic-types.json.tmp/slate-v2/packages/slate-react/test/tsconfig.generic-types.jsonValue, ValueOf, ElementOf, TextOf, EditorMarksOf; createEditor<CustomValue>() is rejected; Editor and Operation are not generic.bun test to tsc because Bun tests do not typecheck compile-only generic contracts.bun test for type-only proof; do not begin React generic threading before core exports exist.packages/slate/src/interfaces/{text,element,node,editor,operation}.ts, types, and create-editor.ts until the Slate core generic contract progresses.Value, TElement, TText, ElementOf, TextOf, NodeOf, NodeIn, ValueOf, and EditorMarksOf; made Editor<V> and core operation payloads generic; threaded generic editor preservation through withHistory, withDOM, and withReact; removed primary Slate core ExtendedType aliases and deleted React/DOM editor declaration merging.bunx tsc --project packages/slate/test/tsconfig.generic-types.json --noEmit --pretty falsebunx tsc --project packages/slate-history/test/tsconfig.generic-types.json --noEmit --pretty falsebunx tsc --project packages/slate-react/test/tsconfig.generic-types.json --noEmit --pretty falserg -n "CustomTypes|ExtendedType|declare module ['\\\"]slate|declare module \\\"slate\\\"" packages/slate/src packages/slate-dom/src packages/slate-react/src packages/slate-history/src --glob '!**/dist/**' --glob '!**/node_modules/**'rg -n "CustomTypes|ExtendedType|declare module ['\\\"]slate|declare module \\\"slate\\\"" packages site docs --glob '!**/dist/**' --glob '!**/node_modules/**' --glob '!site/out/**'packages/slate/test, packages/slate-history/test, and packages/slate-react/test.tmp/slate-v2/packages/slate-react/src/dom-globals.d.ts.tmp/slate-v2/packages/slate-dom/src/dom-globals.d.tspackages/slate/src, packages/slate-dom/src, packages/slate-react/src, and packages/slate-history/src.CustomTypes.Editor as a compatibility merge; it erases Editor<V> and recreates the source-first type pollution bug.CustomTypes declarations and remove the old packages/slate/test/tsconfig.custom-types.json fixture path or reclassify it as archived-only proof.createEditor<CustomValue>(), added source aliases for the site, and made React hooks generic over the full editor instance type so history/extension methods survive through React context.bunx tsc --project packages/slate/test/tsconfig.generic-types.json --noEmit --pretty falsebunx tsc --project packages/slate-history/test/tsconfig.generic-types.json --noEmit --pretty falsebunx tsc --project packages/slate-react/test/tsconfig.generic-types.json --noEmit --pretty falsebun typecheckbun test ./packages/slate --bail 1bun test ./packages/slate-history --bail 1bun test ./packages/slate-hyperscript --bail 1bun --cwd packages/slate-react test -- --bail 1bun test ./test/bridge.test.ts ./test/clipboard-boundary.test.ts --bail=1bun run lint:fixbun run lintrg -n "CustomTypes|ExtendedType|declare module ['\\\"]slate|declare module \\\"slate\\\"" packages/slate/src packages/slate-dom/src packages/slate-react/src packages/slate-history/src --glob '!**/dist/**' --glob '!**/node_modules/**'rg -n "CustomTypes|ExtendedType|declare module ['\\\"]slate|declare module \\\"slate\\\"" packages site docs --glob '!**/dist/**' --glob '!**/node_modules/**' --glob '!site/out/**'rg -n "EditorMarks = Record<string, any>|Record<string, any>" packages/slate/src packages/slate-react/src packages/slate-dom/src packages/slate-history/srctsconfig.json source aliases, generic React context hooks, source-first site example typing, removed current custom-types fixture gate, current TypeScript docs updated to createEditor<CustomValue>().CustomTypes, ExtendedType, or declare module "slate"; full repo search only shows historical changelog entries.BaseElement.type; the type bridge stays cast-only so fixture output remains unchanged.