docs/plans/2026-04-18-001-refactor-slate-v2-parity-first-migration-plan.md
Rewrite the remaining slate-v2 migration plan around the real goal:
preserve the broadest honest contract surface backed by tests and proof, even
when that requires rewriting internals. Legacy behavior remains the default
truth. Draft contract coverage is a second required source of truth. Current
implementation shape is evidence, not the goal.
This plan supersedes 2026-04-18-slate-v2-lossless-remaining-work-replan.md as the active remaining-work doctrine.
The existing live plan corrected one failure and created another.
It correctly rejected broad rewrite-first package churn. But it overcorrected into a source-shape-preservation bias that is too timid for the actual goal.
The actual goal is not:
slate-v2 implementation shapeThe actual goal is:
The docs/slate-v2-draft/** corpus shows the strongest consistent lesson:
proof-owner coverage matters more than implementation nostalgia, but the draft
stack also drifted too far toward “justify the architecture we like.” This
plan fixes both mistakes by making the merged contract corpus the primary
driver.
packages/slatepackages/slate-historypackages/slate-hyperscriptpackages/slate-dompackages/slate-reactslate-v2 internals for their own sake.../slate,
.tmp/slate-v2, and .tmp/slate-v2-draft repos.Decision: Build one merged contract corpus per package. Legacy exact tests/docs and draft contract tests/docs both feed the kept surface decision. This replaces the weaker “legacy first, draft maybe later” hand-wave.
Decision: Let tests and proof drive the engine, not source nostalgia.
For packages/slate/src/**, implementation shape is subordinate to the kept
contract rows. Same-path/source-close pressure remains strongest for docs,
examples, and user-facing package surfaces.
Decision: Rewrite when needed, explicitly. The plan no longer treats rewrite as a smell. Rewrite is mandatory whenever current code cannot satisfy a kept legacy or draft contract row honestly.
Decision: Separate behavior rows from harness rows. Harness, manifest, and dead matrix files can be cut. Behavior-bearing tests, example rows, and package proof cannot be cut without explicit claim narrowing.
Decision: Use package order, but not package dogma.
Execution still runs in dependency order:
slate -> slate-history -> slate-hyperscript -> slate-dom ->
slate-react, but each package is driven by row classification, not by
broad family vibes.
Decision: Current live docs must be rewritten to match this doctrine before more code churn. If the control stack still says “classification before rewrite” without also saying “rewrite when the corpus demands it,” implementers will keep under-shooting the goal.
Keep the current lossless replan and just harden a few sentences Rejected because the underlying doctrine was still wrong. The problem is not wording polish. The problem is that the active plan still treated rewrite avoidance as a value.
Drive the migration from legacy only Rejected because it would silently throw away draft contract coverage that now represents intended v2 behavior and useful proof.
Drive the migration from draft implementation shape Rejected because the draft contains intentional breaking changes and architecture bias that are not automatically part of the kept public claim.
**Should implementation-source parity be the main driver for packages/slate/src/**?**
No. Test-backed contract parity is the driver.
Should draft implementation shape be imported wholesale? No. Draft contracts matter; draft internals are only a means.
Can rewrite be the right answer? Yes. If it is required to satisfy kept legacy and draft rows, rewrite is not only allowed, it is expected.
Should legacy and draft contract coverage both be preserved? Yes. The migration target is the broadest honest combined corpus, not just legacy-only or draft-only purity.
This illustrates the intended approach and is directional guidance for review, not implementation specification. The implementing agent should treat it as context, not code to reproduce.
Inputs per package:
legacy exact tests + legacy docs
+ draft contract tests + draft docs
+ current source + current browser/example proof
For each row in the merged corpus:
classify -> both | legacy-only | draft-only | dead
decide -> keep-now | keep-later | explicit-cut | post-RC
If keep-now:
import/preserve the row as RED or characterization coverage first
rewrite current implementation until GREEN
sync docs/ledgers/verdict in the same turn
If explicit-cut or post-RC:
record the reason in:
ledgers
roadmap
readiness
maintainer drift register
Rule:
user-facing source shape matters most for docs/examples/package surfaces
test-backed behavior matters most for core engine files
Goal: Replace the current anti-rewrite bias in the live roadmap/verdict/docs with a parity-first doctrine that explicitly allows rewrites when required by kept contract rows.
Requirements: R1, R3, R4, R6, R7
Dependencies: None
Files:
docs/slate-v2/master-roadmap.mddocs/slate-v2/release-readiness-decision.mddocs/slate-v2/true-slate-rc-proof-ledger.mddocs/slate-v2/release-file-review-ledger.mddocs/slate-v2/fresh-branch-migration-plan.mddocs/slate-v2/references/pr-description.mddocs/slate-v2/references/live-shape-register.mdApproach:
Patterns to follow:
Verification:
Live docs all describe the same decision order and no longer imply rewrite avoidance is a virtue by itself.
Unit 2: Create a Merged Contract Corpus for Every Package
Goal: Build one row-classified contract corpus per package that merges legacy exact rows, draft contract rows, and current proof owners.
Requirements: R1, R2, R4, R6, R7
Dependencies: Unit 1
Files:
docs/slate-v2/ledgers/slate-legacy-draft-contract-corpus.mddocs/slate-v2/ledgers/slate-history-legacy-draft-contract-corpus.mddocs/slate-v2/ledgers/slate-hyperscript-legacy-draft-contract-corpus.mddocs/slate-v2/ledgers/slate-dom-legacy-draft-contract-corpus.mddocs/slate-v2/ledgers/slate-react-legacy-draft-contract-corpus.mddocs/slate-v2/ledgers/README.mddocs/slate-v2/ledgers/example-parity-matrix.mdApproach:
bothlegacy-onlydraft-onlydeadkeep-nowkeep-laterexplicit-cutpost-RCPatterns to follow:
docs/slate-v2-draft/ledgers/legacy-slate-test-files.mddocs/slate-v2-draft/ledgers/example-parity-matrix.mdVerification:
Every package has one merged corpus doc, and no future package work can claim parity without referencing its corpus row statuses.
Unit 3: Recover slate Query, Interface, and Ref Surfaces from the Corpus
Goal: Drive the remaining slate query/interface/ref work from the merged corpus instead of source-diff vibes.
Requirements: R1, R2, R3, R4
Dependencies: Unit 2
Files:
.tmp/slate-v2/packages/slate/src/interfaces/editor.ts.tmp/slate-v2/packages/slate/src/interfaces/node.ts.tmp/slate-v2/packages/slate/src/interfaces/path.ts.tmp/slate-v2/packages/slate/src/interfaces/path-ref.ts.tmp/slate-v2/packages/slate/src/interfaces/point.ts.tmp/slate-v2/packages/slate/src/interfaces/point-ref.ts.tmp/slate-v2/packages/slate/src/interfaces/range.ts.tmp/slate-v2/packages/slate/src/interfaces/range-ref.ts.tmp/slate-v2/packages/slate/test/query-contract.ts.tmp/slate-v2/packages/slate/test/legacy-editor-nodes-fixtures.ts.tmp/slate-v2/packages/slate/test/legacy-interfaces-fixtures.tsdocs/slate-v2/ledgers/slate-editor-api.mddocs/slate-v2/ledgers/slate-interfaces-api.md.tmp/slate-v2/docs/api/nodes/editor.mdApproach:
both and legacy-only keep-now rows in the merged corpus.Execution note: Characterization-first. Preserve or import failing contract rows before changing implementation.
Patterns to follow:
.tmp/slate-v2/packages/slate/test/query-contract.ts.tmp/slate-v2/packages/slate/test/legacy-editor-nodes-fixtures.ts.tmp/slate-v2/packages/slate/test/legacy-interfaces-fixtures.tsTest scenarios:
Editor.before, Editor.after, Editor.next,
Editor.previous, Editor.nodes, Editor.levels, and positions rows
still pass with the current public API names.voids, and
nonSelectable rows behave like the kept corpus says, not like today’s
accidental narrowing.explicit-cut stay explicitly documented and do not
silently pass through fallback behavior.Verification:
slate query/interface/ref rows marked keep-now are green and synced in
code, tests, docs, and ledgers.Current progress:
restored and green:
.tmp/slate-v2/packages/slate/test/query-contract.ts.tmp/slate-v2/packages/slate/test/legacy-editor-nodes-fixtures.ts.tmp/slate-v2/packages/slate/test/legacy-interfaces-fixtures.ts Unit 4: Recover slate Transform, Operation, Snapshot, and Transaction Surfaces from the Corpus
Goal: Close the remaining slate transform and operation-width debt using the merged corpus, even where that requires deeper engine rewrites.
Requirements: R1, R2, R3, R4
Dependencies: Unit 2
Files:
.tmp/slate-v2/packages/slate/src/interfaces/transforms/general.ts.tmp/slate-v2/packages/slate/src/interfaces/transforms/node.ts.tmp/slate-v2/packages/slate/src/interfaces/transforms/selection.ts.tmp/slate-v2/packages/slate/src/interfaces/transforms/text.ts.tmp/slate-v2/packages/slate/src/transforms-node/insert-nodes.ts.tmp/slate-v2/packages/slate/src/transforms-node/set-nodes.ts.tmp/slate-v2/packages/slate/src/transforms-node/split-nodes.ts.tmp/slate-v2/packages/slate/src/transforms-text/delete-text.ts.tmp/slate-v2/packages/slate/src/transforms-text/insert-fragment.ts.tmp/slate-v2/packages/slate/src/core/apply.ts.tmp/slate-v2/packages/slate/src/create-editor.ts.tmp/slate-v2/packages/slate/test/operations-contract.ts.tmp/slate-v2/packages/slate/test/snapshot-contract.ts.tmp/slate-v2/packages/slate/test/accessor-transaction.test.ts.tmp/slate-v2/packages/slate/test/legacy-transforms-fixtures.tsdocs/slate-v2/ledgers/slate-transforms-api.md.tmp/slate-v2/docs/api/transforms.mdApproach:
Execution note: Test-first for feature-bearing rows; characterization-first for legacy transforms already represented in same-path fixtures.
Patterns to follow:
.tmp/slate-v2/packages/slate/test/legacy-transforms-fixtures.ts.tmp/slate-v2/packages/slate/test/operations-contract.ts.tmp/slate-v2/packages/slate/test/snapshot-contract.ts.tmp/slate-v2/packages/slate/test/accessor-transaction.test.tsTest scenarios:
move, delete, select,
setPoint, setSelection, insertNodes, setNodes, splitNodes, and
insertFragment stay green.withTransaction,
applyBatch, snapshot publication, and replacement visibility stay green.Verification:
slate transform/operation/snapshot rows marked keep-now are green, and
the implementation no longer depends on current underpowered helpers.
Unit 5: Recover slate-history and slate-hyperscript Against the Same Combined Standard
Goal: Apply the same merged-corpus doctrine to support packages instead of treating them as “close enough” because their current suites are smaller.
Requirements: R1, R2, R3, R4, R6
Dependencies: Units 2, 3, 4
Files:
.tmp/slate-v2/packages/slate-history/src/index.ts.tmp/slate-v2/packages/slate-history/src/history.ts.tmp/slate-v2/packages/slate-history/src/history-editor.ts.tmp/slate-v2/packages/slate-history/src/with-history.ts.tmp/slate-v2/packages/slate-history/test/index.spec.ts.tmp/slate-v2/packages/slate-history/test/history-contract.ts.tmp/slate-v2/packages/slate-hyperscript/src/index.ts.tmp/slate-v2/packages/slate-hyperscript/src/hyperscript.ts.tmp/slate-v2/packages/slate-hyperscript/src/creators.ts.tmp/slate-v2/packages/slate-hyperscript/test/index.spec.tsdocs/slate-v2/ledgers/slate-history-api.mdApproach:
both, legacy-only keep-now, and
draft-only keep-now rows in their corpora.slate-history, keep legacy undo/redo semantics where they still belong,
but also preserve draft history/transaction contract coverage where it
expresses intended v2 batching behavior.slate-hyperscript, keep fixture and runtime parity as contract, not as
incidental Bun-loader luck.Execution note: Import characterization coverage before changing support-package source; do not patch support packages around unresolved slate drift.
Patterns to follow:
.tmp/slate-v2/packages/slate-history/test/index.spec.ts.tmp/slate-v2/packages/slate-hyperscript/test/index.spec.tsTest scenarios:
History.isHistory, and kept
cursor/selection restore behavior pass.keep-now.Verification:
Support packages no longer rely on stale assumptions about slate internals,
and their kept rows are green under the merged corpus.
Unit 6: Recover slate-dom, slate-react, and Example/Browser Parity from the Same Corpus
Goal: Finish the runtime and user-facing surfaces using the merged corpus so example and browser proof close real product parity instead of dressing up partial core recovery.
Requirements: R1, R2, R3, R5, R6, R7
Dependencies: Units 2, 3, 4, 5
Files:
.tmp/slate-v2/packages/slate-dom/src/index.ts.tmp/slate-v2/packages/slate-dom/src/plugin/dom-editor.ts.tmp/slate-v2/packages/slate-dom/src/plugin/with-dom.ts.tmp/slate-v2/packages/slate-dom/test/bridge.ts.tmp/slate-v2/packages/slate-dom/test/clipboard-boundary.ts.tmp/slate-v2/packages/slate-react/src/index.ts.tmp/slate-v2/packages/slate-react/src/plugin/react-editor.ts.tmp/slate-v2/packages/slate-react/src/plugin/with-react.ts.tmp/slate-v2/packages/slate-react/src/components/editable.tsx.tmp/slate-v2/packages/slate-react/test/bun/editable.spec.tsx.tmp/slate-v2/packages/slate-react/test/react-editor.test.tsx.tmp/slate-v2/packages/slate-react/test/use-selected.test.tsx.tmp/slate-v2/site/examples/ts/check-lists.tsx.tmp/slate-v2/site/examples/ts/code-highlighting.tsx.tmp/slate-v2/site/examples/ts/custom-placeholder.tsx.tmp/slate-v2/site/examples/ts/editable-voids.tsx.tmp/slate-v2/site/examples/ts/images.tsx.tmp/slate-v2/site/examples/ts/markdown-preview.tsx.tmp/slate-v2/site/examples/ts/markdown-shortcuts.tsx.tmp/slate-v2/site/examples/ts/paste-html.tsx.tmp/slate-v2/site/examples/ts/plaintext.tsx.tmp/slate-v2/site/examples/ts/read-only.tsx.tmp/slate-v2/site/examples/ts/richtext.tsx.tmp/slate-v2/site/examples/ts/scroll-into-view.tsx.tmp/slate-v2/site/examples/ts/shadow-dom.tsx.tmp/slate-v2/site/examples/ts/styling.tsx.tmp/slate-v2/site/examples/ts/tables.tsx.tmp/slate-v2/playwright/integration/examples/check-lists.test.ts.tmp/slate-v2/playwright/integration/examples/code-highlighting.test.ts.tmp/slate-v2/playwright/integration/examples/custom-placeholder.test.ts.tmp/slate-v2/playwright/integration/examples/editable-voids.test.ts.tmp/slate-v2/playwright/integration/examples/images.test.ts.tmp/slate-v2/playwright/integration/examples/markdown-preview.test.ts.tmp/slate-v2/playwright/integration/examples/markdown-shortcuts.test.ts.tmp/slate-v2/playwright/integration/examples/paste-html.test.ts.tmp/slate-v2/playwright/integration/examples/plaintext.test.ts.tmp/slate-v2/playwright/integration/examples/read-only.test.ts.tmp/slate-v2/playwright/integration/examples/richtext.test.ts.tmp/slate-v2/playwright/integration/examples/select.test.ts.tmp/slate-v2/playwright/integration/examples/shadow-dom.test.ts.tmp/slate-v2/playwright/integration/examples/styling.test.ts.tmp/slate-v2/playwright/integration/examples/tables.test.tsdocs/slate-v2/ledgers/example-parity-matrix.mddocs/slate-v2/ledgers/slate-react-api.mdApproach:
recovered, mixed, extended, explicit-cut, or open.Execution note: Characterization-first for examples and browser rows; same-path source recovery first, then browser proof, then docs/ledger sync.
Patterns to follow:
docs/slate-v2-draft/ledgers/example-parity-matrix.mdTest scenarios:
keep-now behave like legacy on the recovered current runtime.extended and still have explicit proof for the new behavior.open, explicit-cut,
or post-RC instead of being silently softened.Verification:
The example/browser lane becomes a real parity lane instead of a mixture of current-only green tests and vague source drift.
Unit 7: Final RC Reconciliation from the Merged Corpus
Goal: Reconcile readiness, roadmaps, proof ledgers, and maintainer context only after the merged corpus has driven package and example recovery.
Requirements: R6, R7
Dependencies: Units 1 through 6
Files:
docs/slate-v2/master-roadmap.mddocs/slate-v2/release-readiness-decision.mddocs/slate-v2/true-slate-rc-proof-ledger.mddocs/slate-v2/release-file-review-ledger.mddocs/slate-v2/references/pr-description.mddocs/slate-v2/overview.mdApproach:
post-RC rows named and bounded instead of hiding them under “future
improvements.”Patterns to follow:
Verification:
slate
first, then support/runtime packages, then examples/browser proof.slate ->
slate-history -> slate-hyperscript -> slate-dom -> slate-react.
Legacy remains the default truth. Draft remains a value bank, not the default
shipped shape.| Risk | Mitigation |
|---|---|
| The merged corpus becomes a dumping ground and stalls execution | Keep one corpus file per package, row statuses tight, and package order strict |
| Draft-only rows silently widen the public claim | Require explicit draft-only keep-now classification before any code import |
| Rewrite aversion leaves broken current internals in place | Make rewrites the default for keep-now rows that current code cannot satisfy |
| Browser/example green rows are used to fake core closure again | Keep core contract tests and browser/example proof as separate required lanes |
| Harness churn is mistaken for behavior parity | Separate harness-only rows from behavior rows in the corpus and ledgers |