docs/plans/2026-04-22-slate-v2-authoritative-command-kernel-architecture-plan.md
Unify the core command/transaction work and the browser editing kernel work before more implementation.
Do not build a browser-only authoritative kernel and then redesign core extension later. That creates rewrite debt because the browser kernel depends on the command, transaction, history, selection, and repair contracts.
The active plan is two batches:
Batch 1 must be small enough to finish, but deep enough that Batch 2 does not rewrite the spine.
The old architecture made extension easy by letting anything patch anything.
That power is real, but the mechanism is wrong.
The new system must preserve extension power through typed registries and command middleware, not mutable method monkeypatching.
The best shape is:
Editor
-> ExtensionRegistry
-> CommandRegistry
-> TransactionEngine
-> OperationLog
-> EditorCommit
-> HistoryCommitConsumer
-> EditableEditingKernel
-> Selection/Repair contract
-> Browser proof gauntlets
This is not greenfield.
Existing .tmp/slate-v2 already has important pieces that must be audited and
completed, not duplicated:
packages/slate/src/core/public-state.ts has transaction and commit
machinery.packages/slate/src/interfaces/editor.ts exposes EditorCommit,
Editor.withTransaction, and Editor.getLastCommit.withTransaction(...).slate-history already groups committed operations, but still patches
e.apply and exposes undo/redo as instance methods.slate-react already has EditableCommand, transition traces,
EditableInputRule, onKeyCommand, and partial kernel decision prep.Batch 1 must consolidate these pieces into one spine.
It must not create a second transaction engine, second commit type, or second history model.
Path, Point, Range.Operation.Transforms, but routed through transactions.Editable.editor.apply override stacks as extension API.editor.children, editor.selection, editor.marks, editor.operations
as primary public write APIs.onChange as the primary notification contract.Editor.getSnapshot() as urgent render/read path.Important:
Do not remove these yet:
Transforms ergonomic helpers.isInline, isVoid,
markableVoid.Build the command/transaction/kernel spine:
ExtensionRegistry skeleton
-> CommandRegistry
-> TransactionEngine
-> EditorCommit
-> HistoryCommitConsumer
-> EditableEditingKernel
-> SelectionSource/Repair contract
-> Generated browser gauntlet base
Batch 1 should make later work extend the spine, not rewrite it.
Before adding new code, audit and freeze current behavior:
EditorCommit fieldswithTransaction nesting behavioronChange and commit subscriber orderExit:
Build only the minimum registry needed by the spine:
Do not build full plugin dependency/conflict ergonomics yet.
Required shape:
type ExtensionRegistry = {
commands: CommandRegistry;
normalizers: NormalizerRegistry;
capabilities: CapabilityRegistry;
commitListeners: CommitListenerRegistry;
};
Exit:
slate-react can register command handlers without replacing editor
methods.Commands replace method monkeypatching.
Required command families:
Middleware must support:
next(ctx)Required shape:
type CommandHandler<TCommand> = (
ctx: CommandContext<TCommand>,
next: () => CommandResult,
) => CommandResult;
Hard rule:
Every command runs inside a transaction.
Required:
EditorCommit.Required shape:
Editor.withTransaction(editor, meta, () => {
// commands/transforms/normalization
});
EditorCommit is the durable observation record.
Required fields:
History, React, browser kernel, and tests consume this.
Existing EditorCommit fields should be extended only when required by a
failing contract. Do not replace the type wholesale unless the audit proves the
existing shape cannot support command/history/kernel consumers.
Every operation is classified:
Do not make consumers rediscover intent from raw operation arrays.
Keep history rewrite in Batch 1.
History must become a commit/operation consumer.
Cut:
Replace with:
This cannot wait. If history stays override-based, the command/transaction semantics will be distorted around it.
Every command/event has one selection truth source:
Rules:
Repair becomes command/kernel metadata, not delayed guesswork.
Required repair kinds:
Required result:
type RepairPolicy = {
kind: ...
focus?: boolean
forceRender?: boolean
selectionSource: SelectionSource
}
Batch 1 kernel covers:
Other event families are reserved but not fully rewritten yet.
Kernel result:
type EditableKernelResult = {
handled: boolean;
nativeAllowed: boolean;
ownership: EditableOwnership;
targetOwner: EditableEventTargetOwner;
intent: InputIntent | null;
command: EditableCommand | null;
selectionSource: SelectionSource;
repair: RepairPolicy | null;
transition: EditableKernelTransition;
trace: EditableKernelTraceEntry;
};
Hard rule:
EditableDOMRoot attaches listeners and renders.EditableEditingKernel decides editing policy.Batch 1 must add generator infrastructure, not just hand examples.
Initial generated families:
Each step records:
withTransaction and EditorCommit unless a focused
contract proves they cannot support the command spine.Stop and replan the current tracer if any of these happen:
withTransaction cannot support command metadata
without changing transaction semantics.EditorCommit cannot be extended without breaking snapshot/history/react
consumers.Rollback rule:
Confidence rule:
No horizontal rewrite.
Use tracer bullets:
insertText extension pattern.KernelResult.Each tracer must:
Core:
bun test ./packages/slate/test/transaction-contract.ts --bail 1
bun test ./packages/slate/test/snapshot-contract.ts --bail 1
bun test ./packages/slate/test/surface-contract.ts --bail 1
History:
bun test ./packages/slate-history --bail 1
React/browser:
bun test ./packages/slate-react/test/dom-text-sync-contract.ts --bail 1
bun test ./packages/slate-react/test/large-doc-and-scroll.tsx --bail 1
bun test ./packages/slate-react/test/projections-and-selection-contract.tsx --bail 1
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --project=firefox --project=webkit --project=mobile --grep "navigation|Backspace|Delete|kernel|transition"
bunx playwright test ./playwright/integration/examples/markdown-shortcuts.test.ts --project=chromium --project=firefox --project=webkit --project=mobile
bunx playwright test ./playwright/integration/examples/inlines.test.ts --project=chromium --project=firefox --project=webkit --project=mobile
Build/type:
bunx turbo build --filter=./packages/slate --filter=./packages/slate-history --filter=./packages/slate-react --force
bunx turbo typecheck --filter=./packages/slate --filter=./packages/slate-history --filter=./packages/slate-react --force
bun run lint
Perf guardrails when text/selection hot paths change:
bun run bench:react:rerender-breadth:local
REACT_HUGE_COMPARE_BLOCKS=5000 REACT_HUGE_COMPARE_ITERATIONS=5 REACT_HUGE_COMPARE_TYPE_OPS=10 bun run bench:react:huge-document:legacy-compare:local
Batch 1 is complete only when:
EditorCommit exists and is consumed by history and React/runtime lanes.These are non-blockers for Batch 1 and should be replanned from Batch 1 evidence.
Defer:
Batch 1 only needs the registry skeleton.
Defer advanced schema.
Batch 1 needs only capabilities:
Defer:
Batch 1 must not block future perf, but correctness architecture wins first.
Defer full clipboard rewrite unless Batch 1 breaks it.
Batch 1 reserves command slots and preserves existing behavior.
Defer full IME architecture.
Batch 1 reserves composition mode and preserves existing IME proofs.
Defer deep shell polish.
Batch 1 keeps shell activation/selection contracts and perf gates green.
Defer.
Projection direction is accepted, but it does not block command/kernel spine.
Defer.
Batch 1 classifies mobile transport gaps and keeps semantic fallback honest.
Defer production/runtime throwing.
Batch 1 should fail generated tests on illegal transitions, but production stays non-throwing.
Defer until Batch 1 stabilizes.
Start Batch 2 only after Batch 1 exit criteria are met.
Batch 2 must be replanned from:
Do not prebuild Batch 2 APIs in Batch 1 just in case.
Batch 1, Tracer 0:
packages/slate and
packages/slate-historyDo not touch React kernel again until this audit contract is green.
Status: complete.
Actions:
packages/slate/src/core/public-state.ts owns transaction state.packages/slate/src/interfaces/editor.ts already exposes EditorCommit,
Editor.withTransaction, and Editor.getLastCommit.slate-history already observes commit operations through
Editor.subscribe(...), but still patches e.apply.EditorCommit type instead of creating a second commit
model.Changed files:
.tmp/slate-v2/packages/slate/src/interfaces/editor.ts.tmp/slate-v2/packages/slate/src/core/public-state.ts.tmp/slate-v2/packages/slate-history/test/integrity-contract.tsEvidence:
bun test ./packages/slate-history/test/integrity-contract.ts --bail 1
bun test ./packages/slate/test/transaction-contract.ts --bail 1
bun test ./packages/slate-history/test/history-contract.ts --bail 1
bun test ./packages/slate/test/surface-contract.ts --bail 1
bun test ./packages/slate/test/snapshot-contract.ts --bail 1
bun test ./packages/slate-history --bail 1
bun run build
bun run typecheck
cd ../slate-history && bun run build && bun run typecheck
bun run lint:fix
bun run lint
bun run bench:core:observation:compare:local
bun run bench:core:huge-document:compare:local
Results:
7 passed13 passed9 passed10 passed190 passed14 passed, 1 skippedTypecheck caveat:
bunx turbo typecheck --filter=./packages/slate --filter=./packages/slate-history --force
currently rebuilds packages/slate in a way that leaves packages/slate/dist
empty before slate-history typecheck, so slate-history cannot resolve
slate.packages/slate build followed by package-local
packages/slate-history typecheck passes.Decision:
EditorCommit.e.apply patch and make undo/redo command-backed.Rejected tactics:
Next owner:
CommandRegistry skeleton in packages/slateinsertText commandEditor.insertText through transaction-backed command executionStatus: complete.
Actions:
CommandRegistry skeleton in packages/slate.Editor.registerCommand(...).Editor.insertText(...) through command middleware.EditorCommit; no second commit type was created.Editor.insertText(...) default execution to run inside
withTransaction(...).classes: ['text'].slate-history to consume commit-local operations directly instead
of slicing by previous operation count.Changed files:
.tmp/slate-v2/packages/slate/src/core/command-registry.ts.tmp/slate-v2/packages/slate/src/core/index.ts.tmp/slate-v2/packages/slate/src/editor/insert-text.ts.tmp/slate-v2/packages/slate/src/interfaces/editor.ts.tmp/slate-v2/packages/slate/src/core/public-state.ts.tmp/slate-v2/packages/slate/test/transaction-contract.ts.tmp/slate-v2/packages/slate-history/src/with-history.ts.tmp/slate-v2/packages/slate-history/test/integrity-contract.tsEvidence:
bun test ./packages/slate/test/transaction-contract.ts --bail 1
bun test ./packages/slate-history/test/integrity-contract.ts --bail 1
bun test ./packages/slate-history/test/history-contract.ts --bail 1
bun test ./packages/slate/test/surface-contract.ts --bail 1
bun test ./packages/slate/test/snapshot-contract.ts --bail 1
bun test ./packages/slate-history --bail 1
bun run build
bun run typecheck
cd ../slate-history && bun run build && bun run typecheck
bun run lint:fix
bun run lint
bun run bench:core:observation:compare:local
bun run bench:core:huge-document:compare:local
Results:
14 passed7 passed9 passed10 passed190 passed14 passed, 1 skippedstartBlockTypeMs and selectAllMs; confirmation rerun had current green
or equal on all reported mean lanesFailed probes recorded:
Editor.registerCommand contract initially failed because no command
registry existed.insertText through a transaction, text commands were
misclassified as structural because the transaction also contained
set_selection. Operation classification now treats text+selection
transactions as text.slate-history previous operation-count slicing broke once commits became
transaction-local. History now consumes commit operations directly.Typecheck caveat:
bunx turbo typecheck --filter=./packages/slate --filter=./packages/slate-history --force
still rebuilds packages/slate in a way that can leave packages/slate/dist
empty before slate-history typecheck.slate, followed by package-local
build/typecheck for slate-history, passes.Decision:
Editor.insertText(...) now proves the intended pattern:
command middleware -> existing transaction engine -> existing EditorCommit
-> history commit consumer.Rejected tactics:
Next owner:
e.apply patch from withHistoryeditor.undo() / editor.redo() as compatibility methods that
forward to history commandsStatus: complete.
Actions:
editor.undo() and
editor.redo() go through command middleware.editor.undo() and editor.redo() execute
history_undo and history_redo commands.e.apply patch from withHistory.Editor.subscribe(...).Changed files:
.tmp/slate-v2/packages/slate-history/src/with-history.ts.tmp/slate-v2/packages/slate-history/test/history-contract.tsEvidence:
bun test ./packages/slate-history/test/history-contract.ts --bail 1
bun test ./packages/slate-history/test/integrity-contract.ts --bail 1
bun test ./packages/slate-history --bail 1
bun test ./packages/slate/test/transaction-contract.ts --bail 1
bun test ./packages/slate/test/surface-contract.ts --bail 1
bun test ./packages/slate/test/snapshot-contract.ts --bail 1
bun run build
bun run typecheck
cd ../slate-history && bun run build && bun run typecheck
bun run lint:fix
bun run lint
bun run bench:core:observation:compare:local
bun run bench:core:huge-document:compare:local
Results:
10 passed7 passed14 passed, 1 skipped14 passed10 passed190 passedFailed probes recorded:
Decision:
Editor.subscribe(...).Rejected tactics:
e.apply patching in withHistory.Next owner:
insertBreak commandEditor.insertBreak through transaction-backed command executionStatus: complete.
Actions:
insertBreak.Editor.insertBreak(...) through insert_break command execution.withTransaction(...) implementation for the default
insert-break behavior.Changed files:
.tmp/slate-v2/packages/slate/src/editor/insert-break.ts.tmp/slate-v2/packages/slate/test/transaction-contract.tsEvidence:
bun test ./packages/slate/test/transaction-contract.ts --bail 1
bun test ./packages/slate-history/test/history-contract.ts --bail 1
bun test ./packages/slate-history/test/integrity-contract.ts --bail 1
bun test ./packages/slate/test/surface-contract.ts --bail 1
bun test ./packages/slate/test/snapshot-contract.ts --bail 1
bun test ./packages/slate-history --bail 1
bun run build
bun run typecheck
cd ../slate-history && bun run build && bun run typecheck
bun run lint:fix
bun run lint
bun run bench:core:observation:compare:local
bun run bench:core:huge-document:compare:local
Results:
15 passed10 passed7 passed10 passed190 passed14 passed, 1 skippedFailed probes recorded:
Editor.insertBreak
bypassed command middleware.Decision:
Rejected tactics:
Next owner:
Editor.deleteBackward, Editor.deleteForward, and
Editor.deleteFragment through transaction-backed command executionStatus: complete.
Actions:
Editor.deleteBackward(...) and Editor.deleteForward(...) through
delete command execution.Editor.deleteFragment(...) through delete_fragment command
execution.Changed files:
.tmp/slate-v2/packages/slate/src/editor/delete-backward.ts.tmp/slate-v2/packages/slate/src/editor/delete-forward.ts.tmp/slate-v2/packages/slate/src/editor/delete-fragment.ts.tmp/slate-v2/packages/slate/test/transaction-contract.tsEvidence:
bun test ./packages/slate/test/transaction-contract.ts --bail 1
bun test ./packages/slate-history/test/history-contract.ts --bail 1
bun test ./packages/slate-history/test/integrity-contract.ts --bail 1
bun test ./packages/slate/test/surface-contract.ts --bail 1
bun test ./packages/slate/test/snapshot-contract.ts --bail 1
bun test ./packages/slate-history --bail 1
bun run build
bun run typecheck
cd ../slate-history && bun run build && bun run typecheck
bun run lint:fix
bun run lint
bun run bench:core:observation:compare:local
bun run bench:core:huge-document:compare:local
Results:
16 passed10 passed7 passed10 passed190 passed14 passed, 1 skippedFailed probes recorded:
set_selection.properties assertion was too brittle; the contract now
asserts command observation, delete operation payload, operation classes, and
operation type sequence.Decision:
Rejected tactics:
set_selection.properties in command-spine
tests.Next owner:
Transforms.select / Editor.select through transaction-backed
command execution where appropriateStatus: complete.
Actions:
Transforms.select(...), Transforms.setSelection(...), and
Transforms.deselect(...) through set_selection command execution.Changed files:
.tmp/slate-v2/packages/slate/src/transforms-selection/select.ts.tmp/slate-v2/packages/slate/src/transforms-selection/set-selection.ts.tmp/slate-v2/packages/slate/src/transforms-selection/deselect.ts.tmp/slate-v2/packages/slate/test/transaction-contract.ts.tmp/slate-v2/packages/slate-history/test/integrity-contract.tsEvidence:
bun test ./packages/slate/test/transaction-contract.ts --bail 1
bun test ./packages/slate-history/test/integrity-contract.ts --bail 1
bun test ./packages/slate-history/test/history-contract.ts --bail 1
bun test ./packages/slate/test/surface-contract.ts --bail 1
bun test ./packages/slate/test/snapshot-contract.ts --bail 1
bun test ./packages/slate-history --bail 1
bun run build
bun run typecheck
cd ../slate-history && bun run build && bun run typecheck
bun run lint:fix
bun run lint
bun run bench:core:observation:compare:local
bun run bench:core:huge-document:compare:local
Results:
17 passed8 passed10 passed10 passed190 passed14 passed, 1 skippedFailed probes recorded:
Editor.select does not exist as a static API; the real public selection
seam is Transforms.select(...).set_selection command type was wider than the real operation union;
it now uses the actual set_selection operation type.Decision:
CaretEngine kernel work.Rejected tactics:
Editor.select API just for this tracer.Next owner:
Transforms.move(...) through transaction-backed command executionStatus: complete.
Actions:
Transforms.move(...) through move_selection command execution.Changed files:
.tmp/slate-v2/packages/slate/src/transforms-selection/move.ts.tmp/slate-v2/packages/slate/test/transaction-contract.ts.tmp/slate-v2/packages/slate-history/test/integrity-contract.tsEvidence:
bun test ./packages/slate/test/transaction-contract.ts --bail 1
bun test ./packages/slate-history/test/integrity-contract.ts --bail 1
bun test ./packages/slate-history/test/history-contract.ts --bail 1
bun test ./packages/slate/test/surface-contract.ts --bail 1
bun test ./packages/slate/test/snapshot-contract.ts --bail 1
bun test ./packages/slate-history --bail 1
bun run build
bun run typecheck
cd ../slate-history && bun run build && bun run typecheck
bun run lint:fix
bun run lint
bun run bench:core:observation:compare:local
bun run bench:core:huge-document:compare:local
Results:
18 passed9 passed10 passed10 passed190 passed14 passed, 1 skippedFailed probes recorded:
Transforms.move(...)
bypassed command middleware.Decision:
Rejected tactics:
Next owner:
Editor.addMark and Editor.removeMark through command executionStatus: complete.
Actions:
Editor.addMark(...) through add_mark command execution.Editor.removeMark(...) through remove_mark command execution.Changed files:
.tmp/slate-v2/packages/slate/src/editor/add-mark.ts.tmp/slate-v2/packages/slate/src/editor/remove-mark.ts.tmp/slate-v2/packages/slate/test/transaction-contract.ts.tmp/slate-v2/packages/slate-history/test/integrity-contract.tsEvidence:
bun test ./packages/slate/test/transaction-contract.ts --bail 1
bun test ./packages/slate-history/test/integrity-contract.ts --bail 1
bun test ./packages/slate-history/test/history-contract.ts --bail 1
bun test ./packages/slate/test/surface-contract.ts --bail 1
bun test ./packages/slate/test/snapshot-contract.ts --bail 1
bun test ./packages/slate-history --bail 1
bun run build
bun run typecheck
cd ../slate-history && bun run build && bun run typecheck
bun run lint:fix
bun run lint
bun run bench:core:observation:compare:local
bun run bench:core:huge-document:compare:local
Results:
19 passed10 passed10 passed10 passed190 passed14 passed, 1 skippedstartBlockTypeMs at +0.01ms, classified as noise-levelFailed probes recorded:
Decision:
Rejected tactics:
Next owner:
ExtensionRegistry skeleton that owns the command
registry slotEditor.registerCommand(...) as compatibility forwarding into
the registryStatus: complete.
Actions:
ExtensionRegistry skeleton in packages/slate.ExtensionRegistry.commands.Editor.getExtensionRegistry(...).Editor.registerCommand(...) as compatibility forwarding into
the command slot.Changed files:
.tmp/slate-v2/packages/slate/src/core/extension-registry.ts.tmp/slate-v2/packages/slate/src/core/command-registry.ts.tmp/slate-v2/packages/slate/src/core/index.ts.tmp/slate-v2/packages/slate/src/interfaces/editor.ts.tmp/slate-v2/packages/slate/test/transaction-contract.tsEvidence:
bun test ./packages/slate/test/transaction-contract.ts --bail 1
bun test ./packages/slate-history/test/integrity-contract.ts --bail 1
bun test ./packages/slate-history/test/history-contract.ts --bail 1
bun test ./packages/slate/test/surface-contract.ts --bail 1
bun test ./packages/slate/test/snapshot-contract.ts --bail 1
bun test ./packages/slate-history --bail 1
bun run build
bun run typecheck
cd ../slate-history && bun run build && bun run typecheck
bun run lint:fix
bun run lint
bun run bench:core:observation:compare:local
bun run bench:core:huge-document:compare:local
Results:
20 passed10 passed10 passed10 passed190 passed14 passed, 1 skippedselectAllMs at +0.01ms, classified as noise-levelFailed probes recorded:
Editor.getExtensionRegistry(...) did not exist.Decision:
ExtensionRegistry is now the owner of the command registry slot.Rejected tactics:
Next owner:
ExtensionRegistryStatus: complete.
Actions:
Editor.registerCapability(...)Editor.registerNormalizer(...)Editor.registerCommitListener(...)Changed files:
.tmp/slate-v2/packages/slate/src/core/extension-registry.ts.tmp/slate-v2/packages/slate/src/interfaces/editor.ts.tmp/slate-v2/packages/slate/test/transaction-contract.tsEvidence:
bun test ./packages/slate/test/transaction-contract.ts --bail 1
bun test ./packages/slate-history/test/integrity-contract.ts --bail 1
bun test ./packages/slate-history/test/history-contract.ts --bail 1
bun test ./packages/slate/test/surface-contract.ts --bail 1
bun test ./packages/slate/test/snapshot-contract.ts --bail 1
bun test ./packages/slate-history --bail 1
bun run build
bun run typecheck
cd ../slate-history && bun run build && bun run typecheck
bun run lint:fix
bun run lint
bun run bench:core:observation:compare:local
bun run bench:core:huge-document:compare:local
Results:
21 passed10 passed10 passed10 passed190 passed14 passed, 1 skippedstartBlockTypeMs
mean shows +0.11ms on confirmation due first-sample warmup skew while median
is green, and selectAllMs is +0.03ms noise-levelFailed probes recorded:
Decision:
ExtensionRegistry now owns command, capability, normalizer, and
commit-listener slots.Rejected tactics:
Next owner:
Transforms.insertFragment(...) / Editor.insertFragment(...)
through transaction-backed command executionStatus: complete.
Actions:
Transforms.insertFragment(...) / Editor.insertFragment(...)
through insert_fragment command execution.Changed files:
.tmp/slate-v2/packages/slate/src/transforms-text/insert-fragment.ts.tmp/slate-v2/packages/slate/test/transaction-contract.tsEvidence:
bun test ./packages/slate/test/transaction-contract.ts --bail 1
bun test ./packages/slate-history/test/integrity-contract.ts --bail 1
bun test ./packages/slate-history/test/history-contract.ts --bail 1
bun test ./packages/slate/test/surface-contract.ts --bail 1
bun test ./packages/slate/test/snapshot-contract.ts --bail 1
bun test ./packages/slate-history --bail 1
bun run build
bun run typecheck
cd ../slate-history && bun run build && bun run typecheck
bun run lint:fix
bun run lint
bun run bench:core:observation:compare:local
bun run bench:core:huge-document:compare:local
Results:
22 passed10 passed10 passed10 passed190 passed14 passed, 1 skippedFailed probes recorded:
Transforms.insertFragment(...) bypassed command middleware.structural.Decision:
Rejected tactics:
insert_node.Next owner:
EditorCommitStatus: complete.
Actions:
EditorCommitCommand metadata to the existing EditorCommit type.executeCommand(...).insertText and insertBreak commits carry command metadata.Changed files:
.tmp/slate-v2/packages/slate/src/interfaces/editor.ts.tmp/slate-v2/packages/slate/src/core/public-state.ts.tmp/slate-v2/packages/slate/src/core/apply.ts.tmp/slate-v2/packages/slate/src/core/command-registry.ts.tmp/slate-v2/packages/slate/test/transaction-contract.tsEvidence:
bun test ./packages/slate/test/transaction-contract.ts --bail 1
bun test ./packages/slate-history/test/integrity-contract.ts --bail 1
bun test ./packages/slate-history/test/history-contract.ts --bail 1
bun test ./packages/slate/test/surface-contract.ts --bail 1
bun test ./packages/slate/test/snapshot-contract.ts --bail 1
bun test ./packages/slate-history --bail 1
bun run build
bun run typecheck
cd ../slate-history && bun run build && bun run typecheck
bun run lint:fix
bun run lint
bun run bench:core:observation:compare:local
bun run bench:core:huge-document:compare:local
Results:
22 passed10 passed10 passed10 passed190 passed14 passed, 1 skippedFailed probes recorded:
Decision:
EditorCommit.Rejected tactics:
Next owner:
ExtensionRegistry.commitListeners into existing commit notificationEditor.subscribe(...) behaviorStatus: complete.
Actions:
ExtensionRegistry.commitListeners into the existing
notifyListeners(...) commit path.Editor.subscribe(...) behavior.Changed files:
.tmp/slate-v2/packages/slate/src/core/extension-registry.ts.tmp/slate-v2/packages/slate/src/core/public-state.ts.tmp/slate-v2/packages/slate/src/interfaces/editor.ts.tmp/slate-v2/packages/slate/test/transaction-contract.tsEvidence:
bun test ./packages/slate/test/transaction-contract.ts --bail 1
bun test ./packages/slate-history/test/integrity-contract.ts --bail 1
bun test ./packages/slate-history/test/history-contract.ts --bail 1
bun test ./packages/slate/test/surface-contract.ts --bail 1
bun test ./packages/slate/test/snapshot-contract.ts --bail 1
bun test ./packages/slate-history --bail 1
bun run build
bun run typecheck
cd ../slate-history && bun run build && bun run typecheck
bun run lint:fix
bun run lint
bun run bench:core:observation:compare:local
bun run bench:core:huge-document:compare:local
Results:
23 passed10 passed10 passed10 passed190 passed14 passed, 1 skippedselectAllMs at +0.01ms, classified as noise-levelFailed probes recorded:
EditorCommitListener export; fixed by
using the interface type from interfaces/editor.Decision:
Rejected tactics:
notifyListeners(...).Editor.subscribe(...) ordering.Next owner:
Status: complete.
Actions:
Changed files:
.tmp/slate-v2/packages/slate-browser/src/playwright/index.ts.tmp/slate-v2/playwright/integration/examples/richtext.test.ts.tmp/slate-v2/packages/slate-browser/dist/** from package buildEvidence:
bun run build
bun run typecheck
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "generated navigation" --workers=1 --retries=0
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --project=firefox --project=webkit --project=mobile --grep "generated navigation|Arrow|word movement|line extension|navigation and mutation" --workers=4 --retries=0
bunx playwright test ./playwright/integration/examples/markdown-shortcuts.test.ts ./playwright/integration/examples/inlines.test.ts --project=chromium --project=firefox --project=webkit --project=mobile --workers=4 --retries=0
bun run lint:fix
bun run lint
Results:
1 passed24 passed32 passedFailed probes recorded:
slate-browser/playwright from built dist.
Building packages/slate-browser fixed the import.Decision:
Rejected tactics:
Next owner:
EditableEditingKernel result/transition traces authoritativeStatus: complete.
Actions:
Editor.getLastCommit(editor) through the slate-react browser
handle.get.lastCommit() through the slate-browser Playwright harness.EditableEditingKernel transition traces as the browser-row authority
and kept the generated gauntlet illegal-transition scan green.Changed files:
.tmp/slate-v2/packages/slate-react/src/editable/browser-handle.ts.tmp/slate-v2/packages/slate-browser/src/playwright/index.ts.tmp/slate-v2/playwright/integration/examples/richtext.test.ts.tmp/slate-v2/packages/slate-browser/dist/** from package build.tmp/slate-v2/packages/slate-react/dist/** from package buildEvidence:
bun run build
bun run typecheck
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "core command metadata for keydown movement" --workers=1 --retries=0
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --project=firefox --project=webkit --project=mobile --grep "generated navigation|Arrow|word movement|line extension|navigation and mutation|core command metadata" --workers=4 --retries=0
bunx playwright test ./playwright/integration/examples/markdown-shortcuts.test.ts ./playwright/integration/examples/inlines.test.ts --project=chromium --project=firefox --project=webkit --project=mobile --workers=4 --retries=0
bun test ./packages/slate-react/test/dom-text-sync-contract.ts --bail 1
bun test ./packages/slate-react/test/large-doc-and-scroll.tsx --bail 1
bun test ./packages/slate-react/test/projections-and-selection-contract.tsx --bail 1
bunx turbo build --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx turbo typecheck --filter=./packages/slate-dom --filter=./packages/slate-react --force
bun run lint:fix
bun run lint
Results:
1 passed24 passed32 passed1 passed15 passed6 passedFailed probes recorded:
undefined for
get.lastCommit() because slate-react dist was stale. Rebuilding
packages/slate, packages/slate-dom, packages/slate-react, and
packages/slate-browser fixed the proof path.Decision:
Continue checkpoint:
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "core command metadata"bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --project=firefox --project=webkit --project=mobile --grep "generated navigation|Arrow|word movement|line extension|navigation and mutation|core command metadata" --workers=4 --retries=0Next owner:
Status: complete.
Actions:
Changed files:
.tmp/slate-v2/playwright/integration/examples/richtext.test.tsEvidence:
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "native text input and delete" --workers=1 --retries=0
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --project=firefox --project=webkit --project=mobile --grep "text input and delete" --workers=4 --retries=0
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --project=firefox --project=webkit --project=mobile --grep "generated navigation|Arrow|word movement|line extension|navigation and mutation|core command metadata" --workers=4 --retries=0
bunx playwright test ./playwright/integration/examples/markdown-shortcuts.test.ts ./playwright/integration/examples/inlines.test.ts --project=chromium --project=firefox --project=webkit --project=mobile --workers=4 --retries=0
bun run lint:fix
bun run lint
Results:
1 passed4 passed32 passed32 passedFailed probes recorded:
page.keyboard.type(...)
mutated visible DOM at an unexpected offset and did not advance Slate model
selection. This matches existing mobile native transport debt, so the row now
uses semantic-handle transport on mobile while preserving command/model/DOM
text proof.if (!mobile) guard was accidentally applied
inside the Chromium-only navigation gauntlet where mobile is not scoped.
Removed that test-owned mistake.Decision:
Continue checkpoint:
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --project=firefox --project=webkit --project=mobile --grep "selectionchange|repair|core command metadata|generated navigation" --workers=4 --retries=0bun run lintNext owner:
Status: complete.
Actions:
Selection.setBaseAndExtent(...) so
generated DOM selection setup preserves anchor/focus direction and text-node
intent.Changed files:
.tmp/slate-v2/playwright/integration/examples/richtext.test.ts.tmp/slate-v2/packages/slate-react/dist/** after reverting the
unproven runtime experimentEvidence:
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --grep "selectionchange import and repair" --workers=1 --retries=0
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --project=firefox --project=webkit --project=mobile --grep "selectionchange and repair" --workers=4 --retries=0
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --project=firefox --project=webkit --project=mobile --grep "generated navigation|Arrow|word movement|line extension|navigation and mutation|core command metadata|selectionchange and repair" --workers=4 --retries=0
bun test ./packages/slate-react/test/dom-text-sync-contract.ts --bail 1
bun test ./packages/slate-react/test/large-doc-and-scroll.tsx --bail 1
bun test ./packages/slate-react/test/projections-and-selection-contract.tsx --bail 1
bunx turbo build --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx turbo typecheck --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx playwright test ./playwright/integration/examples/markdown-shortcuts.test.ts ./playwright/integration/examples/inlines.test.ts --project=chromium --project=firefox --project=webkit --project=mobile --workers=4 --retries=0
bun run lint:fix
bun run lint
Results:
1 passed4 passed36 passed1 passed15 passed6 passed32 passedFailed probes recorded:
selection-controller did
not close that red row and was reverted. Keeping it without a valid proof
would be patch debt.Decision:
Continue checkpoint:
bun test ./packages/slate/test/transaction-contract.ts --bail 1bun test ./packages/slate-history/test/history-contract.ts --bail 1bun test ./packages/slate-history/test/integrity-contract.ts --bail 1REACT_HUGE_COMPARE_BLOCKS=5000 REACT_HUGE_COMPARE_ITERATIONS=5 REACT_HUGE_COMPARE_TYPE_OPS=10 bun run bench:react:huge-document:legacy-compare:localdone only if every Batch 1 target is actually satisfied.Next owner:
active goal state to status: done
with Batch 1 completion rationaleStatus: complete.
Actions:
Changed files:
docs/plans/2026-04-22-slate-v2-authoritative-command-kernel-architecture-plan.mdactive goal stateEvidence:
bun test ./packages/slate/test/transaction-contract.ts --bail 1
bun test ./packages/slate-history/test/history-contract.ts --bail 1
bun test ./packages/slate-history/test/integrity-contract.ts --bail 1
bun test ./packages/slate/test/surface-contract.ts --bail 1
bun test ./packages/slate/test/snapshot-contract.ts --bail 1
bun test ./packages/slate-history --bail 1
cd packages/slate && bun run build
cd packages/slate && bun run typecheck
cd packages/slate-history && bun run build
cd packages/slate-history && bun run typecheck
bun run bench:core:observation:compare:local
bun run bench:core:huge-document:compare:local
bun test ./packages/slate-react/test/dom-text-sync-contract.ts --bail 1
bun test ./packages/slate-react/test/large-doc-and-scroll.tsx --bail 1
bun test ./packages/slate-react/test/projections-and-selection-contract.tsx --bail 1
bunx turbo build --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx turbo typecheck --filter=./packages/slate-dom --filter=./packages/slate-react --force
bunx playwright test ./playwright/integration/examples/richtext.test.ts --project=chromium --project=firefox --project=webkit --project=mobile --grep "generated navigation|Arrow|word movement|line extension|navigation and mutation|core command metadata|selectionchange and repair" --workers=4 --retries=0
bunx playwright test ./playwright/integration/examples/markdown-shortcuts.test.ts ./playwright/integration/examples/inlines.test.ts --project=chromium --project=firefox --project=webkit --project=mobile --workers=4 --retries=0
bun run bench:react:rerender-breadth:local
REACT_HUGE_COMPARE_BLOCKS=5000 REACT_HUGE_COMPARE_ITERATIONS=5 REACT_HUGE_COMPARE_TYPE_OPS=10 bun run bench:react:huge-document:legacy-compare:local
bun run lint:fix
bun run lint
Results:
23 passed10 passed10 passed10 passed190 passed14 passed, 1 skipped1 passed15 passed6 passed36 passed32 passed5000-block huge-doc means:
12.58ms vs legacy chunk-off 259.39ms and chunk-on 288.77ms0.14ms vs 17.73ms and 0.79ms8.43ms vs 168.38ms and 37.51ms12.18ms vs 179.18ms and 54.49ms0.65ms vs 165.74ms and 42.20ms0.54ms vs 178.57ms and 50.69ms13.66ms vs 175.30ms and 34.60ms23.83ms vs 105.21ms and
109.51ms23.27ms vs 117.01ms and 108.99msFailed probes recorded:
Decision:
EditorCommit,
history as a commit/operation consumer, React/browser command metadata proof,
selectionchange/repair kernel trace proof, generated slate-browser gauntlet
base, and green huge-doc perf guardrails.Final checkpoint:
bun completion-check