docs/plans/2026-05-12-slate-v2-type-inference-ralplan.md
Original broad inference execution completed on 2026-05-12.
Reopened on 2026-05-14 for the useEditorSelector<boolean, CustomEditor> DX
question. The selector generic inference subplan is ready for Ralph execution.
This is a planning pass only. No .tmp/slate-v2 implementation files were
edited.
Yes, push hard on inference. But do it precisely.
The best architecture is not "delete every type annotation." That would be sloppy and would weaken public package contracts. The right cut is:
satisfies for object literals that need structural checking without
widening the variable.The biggest code smell is create-editor.ts: it has about 88 local as any
hits and many (...args: any[]) wrappers even though the repo already has
mapped runtime method types. That is not a "manual type annotation" problem; it
is a missing typed binder/factory problem.
Intent:
satisfies.In scope:
/Users/zbeyens/git/slate-v2/packages/**/src/**/*.{ts,tsx}/Users/zbeyens/git/slate-v2/packages/**/test/**/*.{ts,tsx}/Users/zbeyens/git/slate-v2/site/examples/ts/**/*.{ts,tsx}/Users/zbeyens/git/slate-v2/playwright/**/*.{ts,tsx}/Users/zbeyens/git/slate-v2/scripts/benchmarks/**/*.{ts,tsx}Non-goals:
noExplicitAny gate until the intentional any buckets have a
written allowlist.Decision boundaries:
Live source:
/Users/zbeyens/git/slate-v2/package.json/Users/zbeyens/git/slate-v2/config/typescript/tsconfig.json/Users/zbeyens/git/slate-v2/biome.jsonc/Users/zbeyens/git/slate-v2/eslint.config.mjs/Users/zbeyens/git/slate-v2/packages/slate/src/create-editor.ts/Users/zbeyens/git/slate-v2/packages/slate/src/core/editor-runtime.ts/Users/zbeyens/git/slate-v2/packages/slate/src/core/public-state.ts/Users/zbeyens/git/slate-v2/packages/slate-react/src/components/editable-text-blocks.tsx/Users/zbeyens/git/slate-v2/packages/slate-react/src/hooks/use-editor-selector.tsx/Users/zbeyens/git/slate-v2/packages/slate-react/src/editable/runtime-root-engine.ts/Users/zbeyens/git/slate-v2/packages/slate-react/src/editable/runtime-event-engine.ts/Users/zbeyens/git/slate-v2/site/examples/ts/rendering-strategy-runtime.tsxPrior plans and learnings:
docs/plans/2026-04-26-slate-v2-plate-generics-type-system-plan.mddocs/plans/2026-04-25-slate-v2-source-first-typecheck-plan.mddocs/plans/2026-05-04-slate-v2-legacy-example-dx-ralplan.mddocs/plans/2026-04-13-any-cleanup.mddocs/solutions/developer-experience/2026-04-26-slate-v2-value-generics-should-be-public-boundary-not-runtime-variance.mddocs/solutions/developer-experience/2026-04-07-slate-v2-react-19-2-cleanup-should-remove-forwardref-not-selection-layout-effects.mddocs/solutions/developer-experience/2026-05-11-slate-v2-react-hooks-refs-lint-needs-real-render-fixes.mddocs/solutions/developer-experience/2026-05-04-package-typecheck-must-run-public-type-contracts.mddocs/solutions/workflow-issues/2026-03-26-published-package-type-regressions-may-be-release-artifacts-not-source.mdResearch and issue ledgers:
docs/research/README.mddocs/research/index.mddocs/research/log.mddocs/slate-issues/gitcrawl-live-open-ledger.mddocs/slate-issues/gitcrawl-v2-sync-ledger.mddocs/slate-v2/ledgers/issue-coverage-matrix.mddocs/slate-v2/ledgers/fork-issue-dossier.mdRead-only scanner over packages, site, scripts, and playwright:
files scanned: 1497
as any: 145
as unknown as: 83
React hook explicit generic calls: 48
explicit arrow return annotations: 619
function declaration return annotations: 7
const/let type annotations: 675
satisfies: 37
as const: 157
Top hotspots:
| File | Signal | Current shape |
|---|---|---|
/Users/zbeyens/git/slate-v2/packages/slate/src/create-editor.ts:323 | 88 as any hits | transform/query/runtime wrappers manually erase args before satisfying typed runtime records |
/Users/zbeyens/git/slate-v2/packages/slate/src/core/public-state.ts:1046 | 62 explicit arrow returns, 5 unknown as | good satisfies core views plus extension group record casts |
/Users/zbeyens/git/slate-v2/packages/slate-react/src/components/editable-text-blocks.tsx:1376 | 9 hook generics | some null state/ref generics are needed; some useMemo<T> returns can infer |
/Users/zbeyens/git/slate-v2/site/examples/ts/rendering-strategy-runtime.tsx:33 | many CSSProperties const annotations | good target for satisfies CSSProperties |
/Users/zbeyens/git/slate-v2/packages/slate-react/src/hooks/use-editor-selector.tsx:45 | createContext<...>({} as any) | avoidable context assertion; use nullable context and narrow |
/Users/zbeyens/git/slate-v2/packages/slate-react/src/editable/*.ts | DOM event as any | create typed beforeinput/dataTransfer helpers |
Important config facts:
/Users/zbeyens/git/slate-v2/config/typescript/tsconfig.json.any:
/Users/zbeyens/git/slate-v2/biome.jsonc.slate-react and site, with
only immutability disabled:
/Users/zbeyens/git/slate-v2/eslint.config.mjs.slate-react typecheck already includes the generic public contract:
/Users/zbeyens/git/slate-v2/packages/slate-react/package.json.Use advanced types where they remove local spelling. Do not use them to show off.
Allowed:
Parameters<T> and ReturnType<T> when deriving helper signatures from the
actual API owner.infer for binder helpers and value extraction.satisfies for object literals, configs, examples, scenario rows, and test
fixtures.Avoid:
unknown as T as the default answer. It is only acceptable at an explicit
runtime/generic bridge.useState<T | null>(null) and useRef<T | null>(null) where the initializer
is only null.const items: T[] = [] and let current: T | null = null where the initial
value cannot infer the element/member type.ReturnType<typeof createEditor> in tests where the helper has no exported
public type.as const for literal tuple/string preservation.useMemo<T>(...) when the callback return expression already infers T.const x: CSSProperties = { ... } in examples; use
const x = { ... } satisfies CSSProperties.as any for object literals that can use satisfies.(...args: any[]) => (fn as any)(editor, ...args) wrappers.createContext<T>({} as any) with createContext<T | null>(null) plus a
narrow hook or local context check.InputEvent casts with typed helpers:
getInputEventData, getInputEventDataTransfer, getBeforeInputTargetRanges.globalThis as any profiler access with a declared local global shape.bindEditorMethod /
bindRuntimeMethod helper.any only after a dedicated type-contract proves unknown or an
exact generic value type is better for consumers.Principles:
Drivers:
Chosen option:
satisfies, and public type
contracts.Rejected alternatives:
noExplicitAny: too early. It would force churn before intentional
any buckets are classified.any to unknown: mostly fake safety unless the consumer contracts
prove better narrowing.Owner: .tmp/slate-v2/packages/slate.
Target:
/Users/zbeyens/git/slate-v2/packages/slate/src/create-editor.ts:323 with
typed binders derived from EditorStaticApi, EditorTransformRegistry<V>,
and the existing InternalEditor*Runtime types.Candidate shape:
type BoundEditorMethod<T> = T extends (
editor: Editor,
...args: infer Args
) => infer Result
? (...args: Args) => Result
: never;
const bindEditorMethod = <T extends (editor: Editor, ...args: any[]) => any>(
getEditor: () => Editor,
method: T,
): BoundEditorMethod<T> =>
((...args) => method(getEditor(), ...args)) as BoundEditorMethod<T>;
The exact helper should avoid leaking any at call sites. If TypeScript needs a
single helper-level any, quarantine it there.
Proof:
bun --filter slate typecheckbunx tsc --project packages/slate/test/tsconfig.generic-types.json --noEmit --pretty falsebun test ./packages/slate/test/generic-editor-api-contract.ts ./packages/slate/test/state-tx-public-api-contract.ts --bail 1Owner: .tmp/slate-v2/packages/slate-react.
Target:
/Users/zbeyens/git/slate-v2/packages/slate-react/src/hooks/use-editor-selector.tsx:45
createContext<...>({} as any) with a nullable context./Users/zbeyens/git/slate-v2/packages/slate-react/src/editable/editing-kernel.ts:906/Users/zbeyens/git/slate-v2/packages/slate-react/src/editable/editing-kernel.ts:922/Users/zbeyens/git/slate-v2/packages/slate-react/src/editable/native-input-strategy.ts:107/Users/zbeyens/git/slate-v2/packages/slate-react/src/hooks/android-input-manager/android-input-manager.ts:435/Users/zbeyens/git/slate-v2/packages/slate-react/src/editable/selection-reconciler.ts:609as any in
/Users/zbeyens/git/slate-v2/packages/slate-react/src/editable/clipboard-input-strategy.ts:523
with satisfies or a typed snapshot helper.Proof:
bun --filter slate-react typecheckbunx tsc --project packages/slate-react/test/tsconfig.generic-types.json --noEmit --pretty falseOwner: .tmp/slate-v2/packages/slate-react.
Target:
useMemo<T> generics where callback return expressions infer the
shape.useState<T | null>(null) and useRef<T | null>(null).First files:
/Users/zbeyens/git/slate-v2/packages/slate-react/src/components/editable-text-blocks.tsx:1395/Users/zbeyens/git/slate-v2/packages/slate-react/src/components/editable-text-blocks.tsx:1420/Users/zbeyens/git/slate-v2/packages/slate-react/src/components/editable-text-blocks.tsx:1681/Users/zbeyens/git/slate-v2/packages/slate-react/src/editable/runtime-event-engine.ts:162/Users/zbeyens/git/slate-v2/packages/slate-react/src/editable/runtime-root-engine.ts:135/Users/zbeyens/git/slate-v2/packages/slate-react/src/editable/runtime-root-engine.ts:209Proof:
bun --filter slate-react typecheckbun lintOwner: .tmp/slate-v2/site/examples.
Target:
CSSProperties variable annotations to satisfies CSSProperties.as Value, as any, and custom editor casts with satisfies,
helper factories, or fixed upstream generic surfaces.ComponentProps wrappers where there
is no inference owner.First files:
/Users/zbeyens/git/slate-v2/site/examples/ts/rendering-strategy-runtime.tsx:33/Users/zbeyens/git/slate-v2/site/examples/ts/huge-document.tsx/Users/zbeyens/git/slate-v2/site/examples/ts/paste-html.tsx:211/Users/zbeyens/git/slate-v2/site/examples/ts/components/index.tsxProof:
bun typecheck:siteOwner: .tmp/slate-v2/packages/**/test, .tmp/slate-v2/playwright,
.tmp/slate-v2/scripts/benchmarks.
Target:
ReturnType / Parameters when deriving from helper owners.satisfies.Proof:
bun test ./packages/slate --bail 1bun --cwd packages/slate-react test -- --bail 1any ClassificationOwner: public type-contract lane.
Target:
any into:
/Users/zbeyens/git/slate-v2/packages/slate/src/interfaces/editor.ts:613
Editor<V extends Value = any>/Users/zbeyens/git/slate-v2/packages/slate/src/interfaces/editor.ts:1133
addMark valueReactEditor<any> defaults in public hooksProof:
.d.ts shape matters:
bunx turbo build --filter=./packages/slate --filter=./packages/slate-react --forceany collapseOwner: tooling.
Target:
as anyas unknown asuseMemo<T>CSSProperties variable annotationsanyRejected as first move:
noExplicitAny globally. It would make noise before the real
boundary decisions are encoded.ClawSweeper/live GitHub discovery: skipped for this pass.
Reason: cache-first ledgers already cover the TypeScript/API issue rows, and this plan makes no fix or close claim. No broad GitHub refresh is justified.
Related but not claimed:
| Issue | Existing ledger status | This plan |
|---|---|---|
#5612 examples not 100% type safe | not-claimed in docs/slate-issues/gitcrawl-v2-sync-ledger.md:125 | Phase 4 may improve examples, but no claim until current repro and site proof |
#4290 TypeScript definition from example | issue-reviewed in docs/slate-issues/gitcrawl-v2-sync-ledger.md:450 | Phase 4 related only |
#4095 example n: Node type incorrect | issue-reviewed in docs/slate-issues/gitcrawl-v2-sync-ledger.md:498 | Phase 4 related only |
#5075 formatting type indexing | not-claimed in docs/slate-issues/gitcrawl-v2-sync-ledger.md:281 | Public API type lane may revisit, not claimed |
#5508 CustomEditor overriding prop breaks typing | cluster-synced in docs/slate-issues/gitcrawl-v2-sync-ledger.md:191 | Phase 6 related only |
#5487 createEditor return typing | cluster-synced in docs/slate-issues/gitcrawl-v2-sync-ledger.md:197 | Phase 6 related only |
#5404 hook return type | cluster-synced in docs/slate-issues/gitcrawl-v2-sync-ledger.md:224 | Phase 2 and Phase 6 related only |
#4366 generalized Slate React types | cluster-synced in docs/slate-issues/gitcrawl-v2-sync-ledger.md:439 | Phase 2 and Phase 6 related only |
No Fixes #... rows should be added until implementation proves a current
issue-shaped contract.
| Risk | Required proof |
|---|---|
| Public generic precision regresses | package root-import generic type contracts |
Declaration output collapses to any | package build plus generated declaration inspection when public exports change |
| Runtime wrappers lose a method or wrong args | slate generic/static API contracts and core tests |
| React selector context changes outside provider behavior | slate-react provider/selector tests |
| DOM input helper changes event behavior | focused beforeinput/clipboard/selection tests |
| Example type cleanup changes visible behavior | bun typecheck:site; Playwright only for runtime-output changes |
| Lint cleanup fights React Hooks rules | bun lint plus React Hooks recommended preset still active |
| Dimension | Score | Evidence |
|---|---|---|
| React 19.2 runtime performance | 0.84 | React hook cleanup keeps lazy state and memo walls from existing React Hooks learnings; no runtime behavior changes accepted without slate-react tests |
| Slate-close unopinionated DX | 0.88 | examples inference target from 2026-05-04-slate-v2-legacy-example-dx-ralplan.md; public type contracts remain root-import based |
| Plate and slate-yjs migration backbone | 0.82 | keeps public/static generic boundary from 2026-04-26-slate-v2-plate-generics-type-system-plan.md; does not weaken broad internal runtime variance |
| Regression-proof testing | 0.80 | proof matrix names package, type-contract, and declaration gates; implementation still pending |
| Research evidence completeness | 0.84 | live source scan plus prior TS/generic learnings; no external ecosystem refresh needed for this local type cleanup |
| shadcn-style composability and hook/component minimalism | 0.86 | nullable context cleanup and example prop inference reduce weird surface; public wrappers kept where useful |
Weighted total: 0.84.
Status stays pending because this is the first activation of a new plan, and
the implementation trials/type-contract bake-off have not run.
| Pass | Status | Evidence added | Plan delta | Open issues | Next owner |
|---|---|---|---|---|---|
| Skill reload and prior context | complete | loaded slate-ralplan, typescript-advanced-types, goal workflow, learnings-researcher; memory and docs/solutions checked | planning-only boundary confirmed | none | current pass |
| Current-state read and initial score | complete | live scanner counts, config reads, hotspot line refs | created this plan | scanner regex is approximate, so ralph should trust compiler over counts | ralph |
| Related issue cache pass | complete | ledger search for type/API issue rows | no fix claims | no live GH refresh needed | ralph if implementation claims issues |
| Ralph activation | complete | active goal state, session completion reset to this plan | execution handoff written | none | ralph |
| TypeScript binder design pass | complete | bun --filter slate typecheck; core runtime now has one local binder boundary instead of per-method as any wrappers | implemented in .tmp/slate-v2/packages/slate/src/create-editor.ts; extension group registration no longer casts factories through any | one intentional helper-level any[] remains to bind arbitrary static editor methods | closed |
| React context and DOM event pass | complete | bun --filter slate-react typecheck; selector/input/clipboard/rendering tests passed | nullable selector context, typed DOM input helpers, context-first useElementSelected path lookup | native onBeforeInput fallback still needs unknown as React.FormEvent because React synthetic and DOM native events do not overlap | closed |
| Hook and example inference pass | complete | bun typecheck:site; useMemo<...> and useCallback<...> audit count is 0 | examples use satisfies for style/rule objects; render-prop path usage moved to hooks | remaining CSSProperties annotations are prop/return contracts, not redundant literals | closed |
| Public API any classification | complete | bunx tsc --project packages/slate/test/tsconfig.generic-types.json --noEmit --pretty false; surface contract passed | ElementOf/TextOf/entry helpers now derive through Editor<V>; stale primitive instance-method contract rewritten to transaction API | repo still has intentional test/mock escape hatches outside package source | closed |
| Closure score | complete | all focused gates below passed after bun lint:fix | implementation accepted | no blocker | closed |
Completed on 2026-05-12.
Primary outcomes:
.tmp/slate-v2/packages/slate/src and .tmp/slate-v2/packages/slate-react/src
now have no repeated implementation as any wrappers. The remaining package
source any is the single editor binder rest tuple in create-editor.ts.as any = 36, as unknown as = 89,
useMemo< = 0, useCallback< = 0, : CSSProperties = 10.ElementOf<typeof editor>, TextOf<typeof editor>, and entry helpers retain
Editor<V> value information.useElementPath/useElementSelected instead.Fresh verification:
bun lint:fix
bun --filter slate typecheck
bun --filter slate-react typecheck
bun typecheck:site
bun typecheck:root
bunx tsc --project packages/slate/test/tsconfig.generic-types.json --noEmit --pretty false
bun test ./packages/slate/test/generic-editor-api-contract.ts ./packages/slate/test/state-tx-public-api-contract.ts ./packages/slate/test/transaction-target-runtime-contract.ts --bail=1
bun test ./packages/slate-react/test/provider-hooks-contract.tsx --bail=1
bun test ./packages/slate-react/test/rendering-strategy-and-scroll.tsx --bail=1
bun test ./packages/slate-react/test/surface-contract.tsx --bail=1
bun --filter slate-react test:vitest test/model-input-strategy-contract.test.ts test/dom-coverage-native-bridge-contract.test.ts
Run from /Users/zbeyens/git/slate-v2 unless noted.
Core:
bun --filter slate typecheck
bunx tsc --project packages/slate/test/tsconfig.generic-types.json --noEmit --pretty false
bun test ./packages/slate/test/generic-editor-api-contract.ts ./packages/slate/test/state-tx-public-api-contract.ts --bail 1
React:
bun --filter slate-react typecheck
bunx tsc --project packages/slate-react/test/tsconfig.generic-types.json --noEmit --pretty false
bun --cwd packages/slate-react test -- --bail 1
Examples:
bun typecheck:site
Whole repo closeout:
bun lint
bun run lint:fix
bun check
Declaration proof when public exports change:
bunx turbo build --filter=./packages/slate --filter=./packages/slate-react --force
Planning repo state:
bun run completion-check
any change without a package root-import type contract.as any reduction replaced by behavior drift..tmp/slate-v2 package gates pass for touched packages.bun check passes before claiming closure.Execute this plan in order. Start with Phase 1 and Phase 2 because they remove
the worst assertion debt and improve real safety. Keep Phase 6 separate from
local cleanup; public any changes need type-contract proof and declaration
inspection.
Do not run a cosmetic sweep that removes useful annotations. The target is fewer local lies, not fewer type characters at any cost.
learnings-researcher skill references
docs/solutions/patterns/critical-patterns.md, but that file does not exist
in this repo. This was already noted in docs/plans/2026-04-13-any-cleanup.md.find -exec substituted
the package path into a JavaScript expression. The replacement Node scan
succeeded and showed current package typecheck scripts.No, useEditorSelector<boolean, CustomEditor>(...) is not the absolute best DX.
The return type should be inferred from the selector result.
The target is not a new hook and not a generic-order break. The implemented
target keeps useEditorSelector, annotates the editor parameter only where the
custom editor type is needed, and lets the return type infer:
const active = useEditorSelector((editor: CustomEditor) =>
isMarkActive(editor, format),
);
useEditorState(..., { deps }) remains the preferred normal state-read shape
when the custom value type can be inferred. Ralph tested it here and rejected it
for these examples because the current public generic order forces explicit
return generics or a wrapper to keep CustomValue; that would reintroduce the
DX problem this pass is removing.
Explicit return generics stay only in type-contract code when the test is proving the generic itself.
Intent:
In scope:
.tmp/slate-v2/site/examples/ts/{richtext,hovering-toolbar,inlines,iframe}.tsx
call sites that currently use useEditorSelector<boolean, CustomEditor>..tmp/slate-v2/packages/slate-react/test/generic-react-editor-contract.tsx
type-contract coverage for inferred selector return values.useEditorState
for state reads, useEditorSelector only for editor/operation reads.Non-goals:
useTypedEditorSelector, useCustomEditorSelector, or curried selector
factory.Decision boundary:
useEditorState.CustomEditor only for
low-level editor-object selectors that cannot be moved to useEditorState.| Surface | Current owner | Current shape | Decision |
|---|---|---|---|
| Slate selector API | .tmp/slate-v2/packages/slate-react/src/hooks/use-editor-selector.tsx:82 | useEditorSelector<T, TEditor>(selector, equalityFn?, options?) returns T. | Keep runtime API; infer T at call sites. |
| Slate state selector API | .tmp/slate-v2/packages/slate-react/src/hooks/use-editor-selector.tsx:161 | useEditorState<T, TEditor>(selector, options?) wraps editor.read and has deps. | Prefer for toolbar/shell state reads. |
| Slate hook docs | .tmp/slate-v2/docs/libraries/slate-react/hooks.md:25 and :55 | Docs already say normal app reads use useEditorState; useEditorSelector is low-level. | Align examples with docs. |
| Slate richtext example | .tmp/slate-v2/site/examples/ts/richtext.tsx:500 and :540 | useEditorSelector<boolean, CustomEditor>(...). | Replace with inferred return, preferably via useEditorState. |
| Slate hovering toolbar example | .tmp/slate-v2/site/examples/ts/hovering-toolbar.tsx:147 | useEditorSelector<boolean, CustomEditor>(...). | Replace with inferred return, preferably via useEditorState. |
| Slate inlines example | .tmp/slate-v2/site/examples/ts/inlines.tsx:414 and :436 | useEditorSelector<boolean, CustomEditor>(...). | Replace with inferred return, preferably via useEditorState. |
| Slate iframe example | .tmp/slate-v2/site/examples/ts/iframe.tsx:97 | useEditorSelector<boolean, CustomEditor>(...). | Replace with inferred return, preferably via useEditorState. |
| Slate generic type contract | .tmp/slate-v2/packages/slate-react/test/generic-react-editor-contract.tsx:87 | useEditorSelector<number, typeof reactEditor>(...). | Add/replace a contract that proves return inference without spelling number. |
| Plate selector API | packages/core/src/react/stores/plate/useEditorSelector.ts:15 | useEditorSelector<T, E>(selector, deps, options?). | Plate has similar generic ordering, but most call sites infer T; copy that usage lesson, not Plate's deps signature wholesale. |
Principles:
Viable options:
| Option | Pros | Cons | Verdict |
|---|---|---|---|
Keep useEditorSelector<boolean, CustomEditor> in examples | No code movement. | Teaches redundant return generics and makes examples look harder than Plate. | Reject. |
Reorder useEditorSelector generics to editor-first | Lets callers imagine useEditorSelector<CustomEditor>. | Breaks useEditorSelector<T> style and TypeScript still lacks true partial type-arg inference for every desired form. | Reject. |
Add useTypedEditorSelector or a curried factory | Can hide editor parameter annotations. | Extra public API for a small TypeScript ergonomics issue; not Slate-close. | Reject. |
Use useEditorState for state reads and infer useEditorSelector returns for low-level reads | Matches current docs and would give explicit closure deps. | Current useEditorState<T, TEditor> cannot keep custom value typing here without spelling the return generic or adding a wrapper. | Tried during Ralph, rejected for this slice. |
Keep useEditorSelector but remove explicit return generics | Smallest code change, preserves runtime behavior, proves return inference, and avoids wrapper/public API churn. | Still uses a low-level selector for toolbar active state. | Final implementation. |
| System | Source | Mechanism | Avoids | Steal | Reject | Slate target | Verdict |
|---|---|---|---|---|---|---|---|
| Plate | packages/core/src/react/stores/plate/useEditorSelector.ts:15, packages/core/src/react/stores/plate/useEditorSelector.spec.tsx:24 | Selector return type is inferred at normal call sites; dependencies are explicit. | Repeated <boolean, Editor> spelling in app code. | Teach inferred selector returns and explicit closure deps where Slate has that option. | Do not copy Plate's product-layer selector shape into raw Slate. | Inferred useEditorSelector returns now; revisit state-hook inference only with an API pass, not local wrappers. | partial |
| Slate v2 docs | .tmp/slate-v2/docs/libraries/slate-react/hooks.md:25-68 | Separate useEditorState from low-level useEditorSelector. | App code opening editor.read inside a generic selector. | Align examples with the documented hook split. | Do not document raw selectors as the common toolbar path. | Examples stop spelling return generics and prefer state selectors. | agree |
| React 19.2 external-store model | .tmp/slate-v2/packages/slate-react/src/hooks/use-generic-selector.tsx:48-136 | Selector result equality controls rerender; selector identity changes recompute during render. | Broad rerenders from unrelated commits. | Keep equality/invalidation runtime unchanged. | Do not make type cleanup alter subscriptions. | Type-only/example cleanup with existing selector runtime proof. | agree |
Keep:
useEditorState(selector, options?)
useEditorSelector(selector, equalityFn?, options?)
Do not add:
useTypedEditorSelector();
useCustomEditorSelector();
useEditorSelector.withEditor();
Do not teach:
useEditorSelector<boolean, CustomEditor>(...)
.tmp/slate-v2/site/examples/ts/richtext.tsx:500.tmp/slate-v2/site/examples/ts/richtext.tsx:540.tmp/slate-v2/site/examples/ts/hovering-toolbar.tsx:147.tmp/slate-v2/site/examples/ts/inlines.tsx:414.tmp/slate-v2/site/examples/ts/inlines.tsx:436.tmp/slate-v2/site/examples/ts/iframe.tsx:97useEditorState(..., { deps: [...] }) only when the custom value type
stays inferred without explicit return generics or a wrapper.useEditorSelector((editor: CustomEditor) => ...) and let the boolean return
infer.generic-react-editor-contract.tsx so one contract proves:
typeof reactEditor;Operation<CustomValue>[];number without
useEditorSelector<number, typeof reactEditor>.useEditorSelector<boolean, remains in .tmp/slate-v2.ClawSweeper/live GitHub discovery: skipped.
Reason: this is a type/example DX cleanup under an already-classified hook
typing cluster. It makes no Fixes #... claim and does not change runtime
behavior.
Related but not claimed:
| Issue | Ledger status | This pass |
|---|---|---|
#5404 hook return type | related in docs/slate-v2/ledgers/fork-issue-dossier.md:4375 | Improves the same hook-typing pressure but no legacy useSlateStatic closure. |
#4366 generalized Slate React types | related in docs/slate-v2/ledgers/fork-issue-dossier.md:4491 | Keeps generic React editor contracts; no exact legacy component typing closure. |
#5612 examples type safety | not-claimed in docs/slate-issues/gitcrawl-v2-sync-ledger.md:125 | Example type cleanup may improve it; no fix claim until exact current repro is proven. |
docs/slate-v2/references/pr-description.md unchanged: no fixed issue claims,
accepted public API shape, release gate, or maintainer-facing claim changes.
| Lens | Applicability | Finding | Plan delta |
|---|---|---|---|
vercel-react-best-practices | applied | Type cleanup must not widen subscriptions. Ralph kept the existing selector runtime instead of changing subscriptions. | Remove explicit return generics only. |
performance-oracle | applied | The state-hook tactic would improve closure stability but currently needs explicit generics/wrappers for custom values. | Keep runtime unchanged; defer state-hook inference to an API pass if needed. |
tdd | applied | Public hook typing needs compiler-backed proof. | Update generic React editor contract before/with examples. |
build-web-apps:shadcn | skipped | No UI chrome shape change. | none |
react-useeffect | skipped | No effect or external synchronization change. | none |
Risk is low because no runtime API or subscription engine changes are accepted.
Failure scenarios:
Proof plan:
cd /Users/zbeyens/git/slate-v2
bun --filter slate-react typecheck
bun typecheck:site
rg -n "useEditorSelector<boolean," packages site
bunx tsc --project packages/slate-react/test/tsconfig.generic-types.json --noEmit --pretty false
| Dimension | Score | Evidence |
|---|---|---|
| React 19.2 runtime performance | 0.93 | Keeps useEditorSelector runtime unchanged; current source verified in use-editor-selector.tsx and use-generic-selector.tsx. |
| Slate-close unopinionated DX | 0.94 | Removes redundant return generics without adding a new public hook. |
| Plate and slate-yjs migration backbone | 0.88 | No collab/data-model surface; Plate comparison supports inferred selector usage while raw Slate keeps its smaller hook split. |
| Regression-proof testing | 0.92 | Type-contract, site typecheck, package typecheck, and grep gate are named. |
| Research evidence completeness | 0.90 | Live Slate v2 source/docs plus live Plate selector source were read; no algorithmic external editor research needed. |
| shadcn-style composability and hook/component minimalism | 0.93 | Cuts call-site type noise and rejects extra wrapper hooks. |
Weighted total: 0.92.
From /Users/zbeyens/git/slate-v2:
bun --filter slate-react typecheck
# passed
bun typecheck:site
# passed
These prove the current baseline only. Ralph still owns the post-change gates above.
| Pass | Status | Evidence added | Plan delta | Open issues | Next owner |
|---|---|---|---|---|---|
| Selector generic inference reopen | complete | live Slate selector API, Slate docs, six example call sites, generic contract, Plate selector source, current slate-react and site typechecks | accepted inferred return target; rejected generic reorder and new wrapper hooks | implementation applied by Ralph | verification sweep |
useEditorState and useEditorSelector; no new selector
hook.useEditorSelector<boolean, CustomEditor> from examples.useEditorState(..., { deps }) only when custom value typing
does not force explicit return generics or a wrapper.slate-react typecheck, site typecheck, generic type-contract
tsc, and grep for useEditorSelector<boolean,.Completed on 2026-05-14.
Changed files in .tmp/slate-v2:
site/examples/ts/richtext.tsxsite/examples/ts/hovering-toolbar.tsxsite/examples/ts/inlines.tsxsite/examples/ts/iframe.tsxpackages/slate-react/test/generic-react-editor-contract.tsxImplementation:
useEditorSelector<boolean, CustomEditor> calls with
useEditorSelector((editor: CustomEditor) => ...), so the selector result
infers as boolean.useEditorSelector<number, typeof reactEditor> call with an inferred return
and const inferredSelected: number = selected.useEditorState route and rejected it for this slice:
current useEditorState<T, TEditor> defaults to Value and cannot keep the
examples' CustomValue without explicit return generics or a wrapper.Verification from .tmp/slate-v2:
bun --filter slate-react typecheck
bun typecheck:site
bunx tsc --project packages/slate-react/test/tsconfig.generic-types.json --noEmit --pretty false
rg -n "useEditorSelector<boolean,|useEditorSelector<number, typeof reactEditor>" packages site
bun check
Results:
slate-react typecheck passed.bun check passed: lint, all package/site/root typechecks, Bun tests, and
Slate React Vitest.