Back to Plate

Slate v2 provider-owned page layout fragments

docs/plans/2026-05-26-slate-v2-provider-owned-page-layout-fragments.md

53.0.884.1 KB
Original Source

Slate v2 provider-owned page layout fragments

Objective: Close the Slate Plan for provider-owned paginated layout fragments: define the best long-term API, DX, runtime architecture, example target, and robust TDD plan for multi-page tables and media-like boxes without splitting the canonical Slate AST and without adding raw Slate product table packages.

Goal plan: docs/plans/2026-05-26-slate-v2-provider-owned-page-layout-fragments.md

Template: docs/plans/templates/slate-plan.md

Primary template: docs/plans/templates/slate-plan.md

Applied packs:

  • slate-plan

Completion threshold:

  • Slate Plan closure is legal only when score >= 0.92, no dimension is below 0.85, every pass row is complete or intentionally skipped with evidence, issue/reference sync rows are closed, final handoff is emitted, and node .agents/rules/autogoal/scripts/check-complete.mjs docs/plans/2026-05-26-slate-v2-provider-owned-page-layout-fragments.md passes.
  • Planning creates the accepted API/architecture/TDD/example plan only. .tmp/slate-v2 implementation starts after user review and a second explicit slate-plan execution invocation.

Verification surface:

  • Planning checks run in plate-2.
  • Slate v2 source/runtime/API claims cite and verify the live .tmp/slate-v2 workspace.
  • Current pass verification: cwd=.tmp/slate-v2, command bun --filter ./packages/slate-layout test, result: pass, 31 tests.
  • Execution verification target: focused slate-layout unit tests, slate-react layout/render tests, /examples/pagination Playwright rows, package typecheck, and the broadest feasible .tmp/slate-v2 gate for touched packages.

Constraints:

  • Planning mode may edit only docs/plans/**, docs/research/**, docs/slate-issues/**, docs/slate-v2/ledgers/**, and docs/slate-v2/references/**.
  • Keep raw Slate unopinionated. Plate/app packages own table commands, table maps, cell-selection UX, Markdown syntax, GFM, menus, and product behavior.
  • The canonical document AST stays semantic. Pagination layout fragments must not rewrite one table node into many table nodes.
  • Example code should show the public call-site shape first. Helpers are fine only when they explain the API, not when they hide it.

Boundaries:

  • In scope: slate-layout layout provider protocol, derived fragments, page projection, PagedEditable layout consumption, /examples/pagination multi-page table fixture, robust TDD plan, page-level virtualization interaction, and collaboration/export metadata shape.
  • Non-goals: shipping raw Slate table/Markdown product packages, table editing commands, cell selection UX, table-map algorithms, browser print/export parity, and current-version Plate/slate-yjs adapters.
  • Allowed current-state source reads: .tmp/slate-v2/packages/slate-layout/**, .tmp/slate-v2/packages/slate-react/**, .tmp/slate-v2/site/examples/ts/pagination.tsx, .tmp/slate-v2/playwright/integration/examples/pagination.test.ts, local ../tiptap-docs, and compiled research under docs/research/**.

Blocked condition:

  • Block only if the same missing external/user decision prevents progress for three consecutive goal turns and no source read, research pass, issue-ledger pass, objection pass, or plan-hardening move remains runnable.

Slate Plan lane state:

  • slate_plan_lane_status: complete
  • current_pass: closure-score-and-final-gates
  • current_pass_status: complete
  • next_pass: none
  • next_action: user review; execution starts only after explicit accepted-plan invocation
  • final_handoff_status: complete

Current verdict:

  • verdict: user-review-ready plan; execute only after explicit acceptance
  • confidence: 0.93 after closure score and final gates
  • keep / cut / revise call: keep slate-layout, useSlateLayout, PagedEditable, page/spread virtualization, and opt-in pageBreaks; revise shallow boxes into a provider-owned node layout and fragment protocol; cut raw Slate TableKit/product table packages.
  • reason: current source already has box kinds, split vocabulary, provider injection, page fragments, page mount plans, and tests, but actual pagination still consumes line counts and only honors block-level avoid splitting. Real table row pagination needs provider-owned measured units/fragments.

Completion rule:

  • Do not call update_goal(status: complete) while any required checklist item remains unchecked. If an item does not apply, check it and add N/A: <reason>.
  • Do not call update_goal(status: complete) until every Slate Plan completion gate below is satisfied and the check-complete command passes.
  • Do not create hook state for this goal. This file plus the active goal are the durable state.

Start Gates:

GateAppliesEvidence
Skill analysis before editscompleteslate-plan loaded from .agents/skills/slate-plan/SKILL.md
Active goal checked or createdcompleteget_goal returned null; create_goal created this lane
Source of truth read before editscompletelive .tmp/slate-v2 source and compiled research read
docs/solutions checked for non-trivial existing-code workcompletedocs/solutions/developer-experience/2026-05-22-raw-slate-should-not-own-markdown-table-product-packages.md read
Live .tmp/slate-v2 grounding needed for current-state claimscompletebun --filter ./packages/slate-layout test passed in .tmp/slate-v2

Work Checklist:

  • Objective includes lane outcome, full pass schedule, one-pass-per- activation policy, completion threshold, verification surface, constraints, boundaries, and blocked condition.
  • One-pass-per-activation policy respected for this activation.
  • Live source grounding recorded for current implementation claims.
  • Related issue discovery / ClawSweeper reuse recorded with concrete evidence.
  • Issue-ledger pass completed with current ledger/reference evidence.
  • Final issue sync accounting complete after later revisions, or skipped with concrete evidence.
  • Intent/boundary record and decision brief complete.
  • Research/ecosystem/live-source refresh complete for Pretext, Tiptap Pages, TanStack Virtual, and current .tmp/slate-v2 layout source.
  • Pressure passes complete for performance, DX, migration, regression, and simplicity.
  • Research and ecosystem synthesis complete for every external system used as evidence.
  • Scorecard recorded with evidence; total score >= 0.92 and no dimension below 0.85 before closure.
  • Applicable implementation-skill review matrix applied or skipped with concrete reason.
  • Slate maintainer objection ledger complete for every breaking/paradigm change, or marked N/A with reason.
  • Verification workspace gate recorded for current Slate v2 source claims.
  • TDD used for behavior/proof changes with a sane test surface, or marked N/A with reason.
  • Browser proof captured for browser-surface claims, or marked N/A with reason.

Completion Gates:

GateAppliesRequired actionEvidence
Named verification thresholdyesrun planning and Slate v2 gates named herecurrent .tmp/slate-v2 source gates rerun and passed; final planning check queued in this closure pass
Slate v2 source, runtime, browser, package, public API, or issue-fix claimyesrecord live .tmp/slate-v2 command/proofclosure rerun passed bun --filter ./packages/slate-layout test and focused slate-react virtualized/degraded DOM tests; browser-specific execution claims remain queued, not promoted
Issue ledger or PR reference changedyesrun related issue discovery and sync references if claim/API narrative changesissue matrix, manual sync ledger, and PR reference updated with 2026-05-26 no-claim accounting for nodeLayout, fragment access, no public boxes, no public render-prop path, no raw TableKit, no AST split, and degraded/native-incomplete virtualization
Autoreview for uncommitted implementation changesno for planning passexecution mode will load autoreview for non-trivial .tmp/slate-v2 editsN/A planning-only
Final user-review handoffyesemit final handoff only after closure passfinal handoff section is complete and names accepted API, hard cuts, non-claims, proof gates, and execution queue
Goal plan completeyesrun check-complete only after closure gatescheck-complete passed after closure gates

Phase / pass table:

PhaseStatusEvidenceNext
Current-state read and initial scorecompletelive source, research, solutions, and slate-layout tests readrelated issue discovery
Related issue discoverycompletereused 2026-05-25 Pretext Pagination / Page Virtualization Feedback sync; live rows read for #5944, #5924, #790; no broad live GitHub crawl neededissue-ledger pass
Issue-ledger passcompleteverified issue matrix, v2 sync ledger, fork dossier, and PR reference already cover provider/split protocols, no fixed/improved claims, and proof gatesintent/boundary pass
Intent/boundary and decision briefcompleteraw Slate owns derived layout contract, providers own schema measurement/split policy, nodeLayout wins, boxes is not public beta APIresearch refresh
Research, ecosystem strategy, live-source refreshcompletelocal Pretext source, local Tiptap Pages docs, TanStack Virtual research, live .tmp/slate-v2 source/tests refreshed; nodeLayout still winspressure passes
Performance/DX/migration/regression/simplicity pressure passescompletepressure pass added O(1) fragment lookup, page/spread budgets, pathless renderer hook, Plate/slate-yjs migration boundaries, vertical TDD queue, and simplicity cutsobjection ledger
Slate maintainer objection ledgercompletesteelmanned objections for nodeLayout, pathless fragment lookup, optional pageBreaks, page/spread virtualization, no TableKit, no AST split, Pretext drift, and provider cache purityhigh-risk pass
High-risk deliberate modecompleteduplicate editable DOM rejected, repeated headers excluded from first execution slice, virtualized DOM native-surface limits recorded, model-backed copy/materialize proof required, IME/selection retention and collab/export authority guards tightenedecosystem maintainer pass
Ecosystem maintainer passcompletePretext caveat kept precise, Tiptap TableKit framed as valid product-extension evidence but not a raw Slate model, TanStack kept internal, Premirror kept as derived-layout inspiration onlyrevision pass
Revision passcompletefinal API wording normalized, useSlateLayoutFragments() name accepted, repeated headers removed from first-slice example controls, Tiptap respectful-divergence wording reconciled, issue/reference sync queued and later closed by accounting passissue sync accounting
Issue sync accountingcompleteadded 2026-05-26 no-claim accounting to issue matrix and manual sync ledger; synced PR reference to the final public API targetclosure score and final gates
Closure score and final gatescompletefinal score is above threshold, browser proof is N/A for planning-only claims, user-review handoff is recorded, and final planning check is the closure proofnone

Scorecard:

DimensionWeightScoreEvidence
React 19.2 runtime performance0.200.93Closure keeps provider output cache-pure, pathless fragment lookup O(1), Pretext text measurement outside hot React render, TanStack internal only, page/spread mount planning already covered by current tests, and native/browser claims explicitly degraded until proof.
Slate-close unopinionated DX0.200.94Public target is one Slate-shaped entrypoint plus one provider callback and one pathless renderer hook; tools get explicit layout.getFragments(path). Public boxes, render-prop path, raw TableKit, duplicated example route, and AST splitting are cut.
Plate and slate-yjs migration backbone0.150.92Raw Slate owns derived layout fragments; Plate/app packages own table/media policy; slate-yjs syncs semantic AST only; optional pageBreaks carries accepted fragment/page metadata with profile/version for stricter collab/export.
Regression-proof testing strategy0.200.92Closure separates current proof from execution proof: current layout/React high-risk tests pass, and execution TDD rows cover no AST split, multi-page table rows, oversized media policy, duplicate editable DOM, IME/composition, copy/materialization, and virtualized native-surface metrics.
Research evidence completeness0.150.94Plan synthesizes current .tmp/slate-v2 source/tests, local Pretext canvas/profile caveats, local Tiptap Pages failure taxonomy and TableKit boundary, TanStack range-engine evidence, Premirror derived-page framing, and issue/reference accounting.
shadcn-style composability and minimalism0.100.90Example scope is restrained to existing /examples/pagination with rows, row height, media height/overflow, DOM strategy, and page view controls; no decorative route, table toolbar, or header-repeat control in the first execution slice.

Initial weighted score: 0.742 Current weighted score after research refresh: 0.751 Current weighted score after pressure pass: 0.855 Current weighted score after maintainer objections: 0.858 Current weighted score after high-risk deliberate mode: 0.873 Current weighted score after ecosystem maintainer pass: 0.884 Current weighted score after revision pass: 0.894 Current weighted score after issue sync accounting: 0.894 Final weighted score after closure gates: 0.927

Source-backed architecture north star:

  • target shape: slate-layout owns derived node layout plans and page fragments; app/Plate providers own measurement and split policy for tables, media, callouts, figures, and other structured boxes; slate-react consumes the fragment plan without rewriting the Slate AST.
  • source evidence: current box kinds and split vocabulary exist at .tmp/slate-v2/packages/slate-layout/src/index.ts:105-120; provider injection exists at :134-139; extraction stores boxes at :1371-1406; pager still line-paginates at :1970-2027.
  • rejected drift: do not copy Tiptap's CSS-float page gap strategy; do not ship a raw Slate TableKit; do not make user renderers split AST nodes.
  • migration posture: raw Slate provides generic layout substrate; Plate/app packages bind schema-specific table/media behavior.

Public API target:

SurfaceProposed shapeUser-facing DXCompatibility / migrationEvidenceVerdict
useSlateLayout optionsreplace public boxes with nodeLayout({ element, path, defaults, pageSettings, measurementProfile }) => SlateNodeLayoutPlanone function, Slate path/element terms, no product registry, no current-page cursoralpha hard cut before beta; an internal adapter is acceptable for local churn, not documented APIcurrent boxes at src/index.ts:355-374replace
Node layout plan{ kind: 'text' }, { kind: 'box', size, split }, { kind: 'units', units, split }providers describe measured units; core paginates unitsAST stays semantic; provider output is derivedcurrent SlatePageLayoutBox too shallow at src/index.ts:115-120add
Table providerapp/Plate provider returns row units with row heights, cell rects, header metadata, and overflow policyno raw TableKit; example can inline a minimal providerPlate can later replace the example provider with real table measurementsolution note and PR reference table boundaryadd
PagedEditable render accessexpose package-owned layout context/hook for the current element's fragments instead of reading data-slate-path manuallyrenderers call useSlateLayoutFragments(); tools can call layout.getFragments(path)regular Editable remains usable without pagination; RenderElementProps still does not expose pathcurrent example path map at pagination.tsx:434-456; render-prop path solution note; surface contractrevise
pageBreakskeep opt-in strict-fidelity snapshot with measurement profile and fragment identitycollab/export users can choose authority; default stays localno cross-client break promise by defaultsrc/index.ts:304-344, Pretext researchkeep

Internal runtime target:

LayerCurrent ownerTarget mechanismAvoidsEvidenceVerdict
MeasurementpretextPageLayoutEnginePretext for text; providers for structured unit measurementDOM reflow hot path and fake deterministic claimsresearch page and src/index.ts:1601-1933keep/revise
PaginationpaginateSlatePageLayoutBlockspaginate measured units/fragments, not only lineCount * lineHeightrow split hacks and AST mutationsrc/index.ts:1970-2027revise
ProjectiongetSlatePageLayoutProjectionproject fragments for top-level and child paths; expose fragment idsduplicate DOM ambiguity and example-owned mapssrc/index.ts:749-873, example maprevise
React mountingPagedEditablepage/spread mount plan remains the paged repeated unit; active/selected/composing pages retainedblock virtualization as wrong default in paged modesrc/react.tsx:211-252, tests :1309-1471keep
DOM renderingEditable render callbacksrenderers consume layout context; no renderer-owned splittingbrittle path parsing and product gluepagination.tsx:510-650revise

Hook / component / render DX target:

SurfaceCall-site shapeComposition rulePerformance ruleEvidenceVerdict
Example setupconst nodeLayout = useCallback(...); const layout = useSlateLayout(editor, { page, root, typography, nodeLayout })show state-dependent nodeLayout only where it matters; keep ordinary render callbacks boringprovider identity participates in layout refresh; no caller-side memo ceremony for normal rendererscurrent example uses useSlateLayout already; PR reference rejects useCallback cargo cult for beginner rendererskeep/revise
Table renderer<TableElement {...props} /> calls useSlateLayoutFragments()renderer positions rows from fragments; it does not mutate children and does not receive a public path propfragment lookup is map-backed by current element/path context and O(1)current renderer does manual elementBoxes lookup; RenderElementProps intentionally omits pathadd
Media rendererimage/callout provider returns avoid-split fixed boxrenderer uses one projected rectno ResizeObserver loop unless provider opts into DOM measurementprovider sizing test existskeep/revise
Debug controlspagination example adds table/media stress controls only if they prove APIcontrols: rows, row height, media height/overflow, DOM strategy, page viewno decorative UI, header-repeat control, or example-only architectureuser requested example supportadd in execution

Plate migration-backbone target:

PressureSlate substrate targetPlate adaptation routeNon-goalEvidenceVerdict
Rich table semanticsgeneric units/fragments with provider dataPlate table plugin supplies row heights, spans, header repeat, cell selection mappingraw Slate table commands/mapssolution note, PR referencekeep
Media/callout sizingavoid/page/custom unit policyPlate/media plugin supplies intrinsic sizes and resize commitsproduct resize UI in raw Slateprovider sizing testkeep
Export fidelitypageBreaks plus measurement profile and fragment idsPlate export pipeline can read authoritative fragmentsbyte-for-byte default guaranteePretext researchrevise

slate-yjs migration-backbone target:

PressureSlate substrate targetCollaboration routeNon-goalEvidenceVerdict
Shared document ASTlayout fragments are derived, not operationsYjs syncs one table node; page break snapshots optional shared statesyncing layout as document childrencurrent pageBreaks state field modelkeep
Cross-client driftmeasurement profile in page break snapshotone writer/server can publish accepted breaks; peers read or mark staledeterministic local Pretext across OS by defaultPretext drift researchkeep/revise
Fragment identitystable fragment ids include source path/unit key/version/profileremote cursors/export can refer to derived fragments without changing ASTcollaborative table layout conflict policy in raw Slateexecution proofadd

Intent / boundary record:

  • intent: make pagination a derived layout substrate that can handle structured nodes, not a line-count trick and not a product table package.
  • outcome:
    • one semantic table AST node can visually span pages through provider-owned row/unit fragments
    • media/callout/figure-like nodes can declare avoid/split/overflow policy without renderer-owned hacks
    • /examples/pagination proves a multi-page table using the same public call-site shape that package users would copy
    • tests prove no AST split, stable fragment identity, correct page projection, and bounded page/spread virtualization behavior
  • raw Slate owns:
    • the generic nodeLayout provider contract
    • derived layout plans, units, page fragments, and projection lookup
    • PagedEditable page/spread mount policy and layout render context
    • optional pageBreaks metadata for strict-fidelity consumers
  • app/Plate providers own:
    • schema-specific table/media measurement
    • row heights, spans, repeated-header policy, intrinsic media sizing, and overflow rules
    • table commands, table maps, cell selection UX, Markdown/GFM syntax, menus, and product editing behavior
  • in-scope:
    • public experimental nodeLayout API
    • SlateNodeLayoutPlan union for text, fixed boxes, and provider-owned units
    • stable unit keys and derived fragment ids
    • pathless fragment lookup for renderers, plus explicit path lookup for tools
    • page/spread virtualization interaction
    • multi-page table and media-like example fixtures
    • unit and browser TDD execution queue
  • non-goals:
    • raw Slate TableKit
    • AST page splitting
    • browser print/export parity
    • deterministic cross-client page breaks by default
    • current-version Plate or slate-yjs adapters
    • solving custom table selection, rowspan UX, or Web Component boundaries
  • decision boundaries:
    • choose the best raw Slate substrate now; later passes may stress-test the names, not reopen TableKit or AST splitting
    • nodeLayout is the public experimental API; public boxes should be cut before beta
    • providers describe layout; renderers consume fragments; neither mutates the Slate document to paginate
    • paged mode virtualizes pages/spreads by default, not arbitrary blocks
    • strict page-break authority is opt-in metadata, not a default promise
  • unresolved user-decision points: none for planning closure. Execution still requires explicit user acceptance of the final plan.

Decision brief:

  • principles:
    • keep the AST semantic and collaboration-friendly
    • make layout derived, cacheable, and disposable
    • keep raw Slate substrate generic; product semantics stay in app/Plate
    • make provider DX boring: element, path, stable unit keys, measured sizes
    • make render DX boring: ask for the current element's fragments through a hook; never parse DOM attrs or add moving paths to public render props
    • prove browser/perf/accessibility behavior before claiming issues
  • top drivers:
    • legacy Slate cannot own derived page layout for structured nodes
    • current boxes is useful evidence but cannot express rows/cells as first class repeated units
    • Tiptap-style CSS pagination is the wrong abstraction for BFC/table/media edge cases
    • Pretext is the right text engine target, but table/media semantics belong to providers
    • collab/export pressure requires stable fragment identity and optional page-break authority
  • viable options:
    • Option A: extend boxes only. Lowest diff, but too shallow; cannot express row/cell fragments, repeated headers, or projection ownership.
    • Option B: add provider-owned node layout plans and unit fragments. Best substrate; more API work; requires render/projection changes.
    • Option C: split AST tables at page breaks. Easy rendering; wrong document semantics; bad for undo/collab/export.
    • Option D: raw Slate TableKit. Tempting product DX; violates package ownership.
    • Option E: keep layout in example renderers. Fast demo, terrible package DX; it repeats data-attribute/path-map hacks and cannot become a reusable substrate.
  • chosen option: Option B.
  • rejected alternatives: A is underpowered, C corrupts document semantics, D bloats raw Slate and duplicates Plate, E keeps the mess at every app call site.
  • API decision:
    • public option name: nodeLayout, not boxes
    • public plan type: SlateNodeLayoutPlan
    • plan forms: text fallback, fixed box, provider-owned units
    • provider inputs: element, path, defaults, page settings, and measurement profile; no current page index/cursor, so output stays cacheable
    • unit identity: provider-owned stable unit.key, composed with source path, page index, and measurement profile into derived fragment ids
    • render access: package-owned fragment lookup, expected call shape useSlateLayoutFragments() under PagedEditable; optional low-level layout.getFragments(path) for non-renderer tooling
    • strict fidelity: optional pageBreaks stores accepted fragment/page results with measurement profile; local layout remains the default
  • consequences:
    • slate-layout grows a real provider/unit/fragment protocol
    • PagedEditable exposes fragment-aware render access
    • the pagination example becomes a real API specimen, not a clever demo
    • tests must cover table rows crossing pages, no AST mutation, oversized unit policy, page/spread virtualization, and fragment lookup
  • follow-ups:
    • research/ecosystem/live-source refresh checks the chosen names and constraints against Pretext, Tiptap, TanStack Virtual, and current source
    • pressure passes challenge React runtime cost, migration fit, regression proof, and simplicity before closure

Issue accounting:

Issue / clusterClaim categoryExact claimWhyProof routeV2 sync ledgerPR line
#5944 stable per-line paginationRelated, issue-reviewedno Fixes/Improves claimdirect pagination/layout pressure, but current plan has no browser proof for page-boundary flicker, caret mapping, or stable breaksmulti-page text/table Playwright proof before any claimexisting 2026-05-25 row coversexisting PR reference covers no-claim target
#790 dynamic rendering / virtualizationRelated, proof-route backlogno Fixes/Improves claimpage/spread virtualization contributes, but mounted-count/native-behavior proof is not donemount/edit/scroll benchmarks, mounted-count proof, DOM coverage, native behavior proofexisting 2026-05-25 row coversexisting PR reference covers no-claim target
#5924 structural DOM cursor exclusionNot claimeddo not add public ignore-cursor APIpage/table/debug DOM should be governed by DOM coverage, mount policy, and provider/split protocolsDOM coverage and selection proof only if execution touches structural DOMexisting 2026-05-25 row coversexisting PR reference covers no-claim target
#4141 nested rerender breadthExisting Improves unchangeddo not broaden claimlayout/page subscriptions must preserve the current nested rerender improvementrender-count proof if execution changes subscriptionsexisting guardrail row coversunchanged
#5131, #2051 subscription/perf guardrailsGuardrailno claimfragment layout must not regress selection/subscription performancefocused package tests plus browser perf proofexisting guardrails coverunchanged
#2793, #2572 accessibility guardsRelease guardno claimvirtualized/missing DOM and page fragments need screen-reader/native behavior proofaccessibility and native browser proof before promotionexisting guardrails coverunchanged
#3892 custom editor surfacesPolicy non-claimno claimraw Slate should provide substrate, not product-specific editor surfacesN/A for this planexisting non-claim row coversunchanged
#5945, #4056, #5992 existing related rowsExisting Improves unchangeddo not promotepagination plan does not change those classificationsunchangedexisting rows coverunchanged
#5551 Firefox rowspan table selectionNot claimed, table warningno claimrowspan selection belongs to custom table plugin/table selection model, not raw layout fragmentsbrowser proof only if an app table plugin claims itexisting matrix non-claim coversunchanged
#5550 Web Component selection boundaryNot claimed, DOM warningno claimstructural DOM rules must not imply Web Component selection supportN/A for this planexisting matrix non-claim coversunchanged
#6034 table last-node ArrowDownExisting Fixes unchangeddo not broadenexact table-edge regression floor, not pagination/table-fragment coverageunchangedexisting Fixes row coversunchanged

Issue-ledger sync status:

  • ClawSweeper related-issue pass: complete by reuse, not rerun. Existing 2026-05-25 Pretext Pagination / Page Virtualization Feedback rows already cover #5944, #790, #5924, #4141, #5131, #2051, #2793, #2572, #3892, #5945, #4056, and #5992.
  • generated live gitcrawl rows read: complete for #5944, #5924, #790; table/DOM warning rows checked for #5551, #5550, #6034.
  • manual v2 sync ledger update: unchanged for this pass; no new fixed, improved, reviewed, or excluded classification beyond existing rows.
  • fork issue dossier update: unchanged for this pass; existing 2026-05-25 section covers the same public API and browser-behavior surface.
  • issue coverage matrix update: unchanged for this pass; existing matrix rows already cover the exact provider/split protocol target.
  • PR description sync: unchanged for this pass; existing pagination target already says generic provider/split protocols and no fixed/improved claim.

Issue-ledger pass result:

  • Issue coverage matrix evidence: docs/slate-v2/ledgers/issue-coverage-matrix.md:302-324 already records generic provider/split protocols for table/media/BFC pagination, keeps #5944 issue-reviewed, keeps #790 as proof-route backlog, keeps #5924 not claimed, preserves #4141, #5131, #2051, #2793, #2572, #3892, #5945, #4056, and #5992.
  • DOM/table warning evidence: docs/slate-v2/ledgers/issue-coverage-matrix.md:434-437 keeps #5550 and #5551 not claimed, and keeps #5924 not claimed. Final issue sync accounting result:
  • Issue matrix evidence: docs/slate-v2/ledgers/issue-coverage-matrix.md:362-387 records a 2026-05-26 no-claim planning sync for nodeLayout, useSlateLayoutFragments(), layout.getFragments(path), no public boxes, no public RenderElementProps.path, no raw Slate TableKit, no AST split, Pretext measurementProfile, and degraded/native-incomplete page/spread virtualization.
  • Manual sync ledger evidence: docs/slate-issues/gitcrawl-v2-sync-ledger.md:335-359 records the same no-claim issue classifications and preserves #5944, #790, #5924, #4141, #5131, #2051, #2793, #2572, #3892, #5945, #4056, #5992, and #6034 statuses.
  • PR reference evidence: docs/slate-v2/references/pr-description.md:266-277 names the accepted public beta target and keeps the no fixed/improved issue claim boundary.
  • Fork dossier decision: no new issue dossier section is needed because this pass reviews no new issue thread and changes no fixed/improved classification. The existing dossier remains historical evidence for the earlier provider/split classification set.
  • Decision: issue/reference accounting is complete. The revised public API and ecosystem wording changed the reference narrative, not the issue claim set.

Ecosystem strategy synthesis:

SystemSourceMechanismAvoidsStealRejectSlate targetVerdict
Pretext../pretext/src, docs/research/sources/editor-architecture/pretext-pagination-page-virtualization.mdprepare() measures with canvas; layout() relayouts from cached widthshot DOM measurementPretext text engine, measurementProfile, arithmetic resize pathdeterministic cross-client/server page breaks by defaultprofile-aware local text layout plus opt-in authoritative pageBreakskeep with caveat
Tiptap Pages../tiptap-docs/src/content/pages/** plus compiled researchCSS floats around page gaps; separate Pages TableKit for table splitting; PageBreak atom/filler nodeproduct-specific table/page coupling leaking into raw Slatefailure taxonomy, explicit page-break UX, proof that table pagination needs custom policyCSS-float pagination as Slate substrate, semantic manual split, raw Slate TableKitprovider-owned nodeLayout units/fragments and optional strict breaksdiverge respectfully
TanStack Virtualdocs/research/sources/editor-architecture/tanstack-virtual-and-github-large-surface-virtualization.mdheadless visible-range engine with measured items and stable keystoo many mounted repeated unitsinternal page/spread range management and retained-index policyleaking TanStack options or item ranges to Slate APIpaged mode virtualizes pages/spreads; continuous mode can virtualize blocksagree internally
PremirrorPretext pagination researchderived pages/fragments over semantic docAST page mutationsnapshot -> measure -> compose -> renderproduct API cloningderived fragment planpartial inspiration

Research/ecosystem/live-source refresh result:

  • Pretext source refresh:
    • ../pretext/src/layout.ts:668-682 confirms prepare() segments text and measures segments through canvas-backed width caches.
    • ../pretext/src/layout.ts:696-710 confirms layout() is arithmetic over cached widths with no canvas or DOM reads.
    • ../pretext/src/measurement.ts:36-49 confirms measurement requires OffscreenCanvas or a DOM canvas; :61-67 calls measureText.
    • ../pretext/src/measurement.ts:74-90 uses browser profile detection, and :135-160 includes DOM-backed emoji correction when needed.
    • Decision: Pretext remains the text engine target, but Slate must keep strict page-break authority opt-in through pageBreaks plus measurementProfile. No default deterministic collab/export promise.
  • Tiptap Pages refresh:
    • ../tiptap-docs/src/content/pages/core-concepts/limitations.mdx:13-20 says Pages requires its own table extension, uses CSS floats around page gaps, cannot split BFC blocks such as tables/figures/styled containers, and recommends max-height or manual node splitting.
    • ../tiptap-docs/src/content/pages/guides/table-with-pages.mdx:16-66 says table pagination needs @tiptap-pro/extension-pages-tablekit, heavily modified table behavior/layout, and warns extension authors can break table splitting logic.
    • ../tiptap-docs/src/content/pages/core-concepts/page-break-node.mdx:14-32 shows explicit page breaks as a product atom/filler behavior; :100-128 ties that node to commands, keyboard shortcut, and DOCX import/export.
    • Decision: steal the edge-case taxonomy and explicit-break UX pressure, not the CSS float mechanism, not manual semantic splitting, and not raw Slate TableKit.
  • TanStack Virtual refresh:
    • docs/research/sources/editor-architecture/tanstack-virtual-and-github-large-surface-virtualization.md:103-122 keeps TanStack as a headless range engine with stable keys, dynamic measurement, range extraction, and Slate-owned selection/copy/IME/a11y policy.
    • :158-178 says TanStack owns visible range and measured item sizes only; Slate owns DOM coverage, materialization, selection, IME/mobile, metrics, and degradation classification.
    • Decision: keep page/spread virtualization internal in paged mode. Do not add public pageVirtualization or TanStack-shaped options.
  • Live .tmp/slate-v2 source refresh:
    • packages/slate-layout/src/index.ts:105-120 still defines shallow box kinds/split policy, not row units or fragment plans.
    • packages/slate-layout/src/index.ts:355-374 still exposes public boxes options; execution should replace that public surface with nodeLayout.
    • packages/slate-layout/src/index.ts:1371-1406 proves providers can own element sizing today, but only as block-local boxes.
    • packages/slate-layout/src/index.ts:1954-2053 still paginates by estimated lines and block fragments; it does not paginate provider-owned row/media units.
    • packages/slate-layout/src/index.ts:304-343 and :2103-2145 already have profile-checked pageBreaks read/write mechanics that fit the strict fidelity target.
    • packages/slate-layout/src/react.tsx:181-252 still creates page/spread mount items and exposes virtualized page items internally.
    • site/examples/ts/pagination.tsx:410-456 and :510-650 still build an example-local path map and read attributes['data-slate-path']; execution should move this into package-owned fragment lookup.
    • packages/slate-layout/test/page-layout-contract.test.ts:735-922 proves current structured boxes and provider sizing; :924-1008 proves line/block pagination; :1309-1475 proves page/spread mount retention.
    • Command: cwd=/Users/zbeyens/git/plate-2/.tmp/slate-v2, bun --filter ./packages/slate-layout test; result: pass, 31 tests, 0 fail.
  • Refresh verdict:
    • The locked nodeLayout decision survives the evidence pass. Current boxes is useful proof that provider ownership works, but it is too shallow and too box-shaped for multi-page table rows, repeated headers, media overflow policy, fragment ids, and renderer lookup.

Pressure pass result:

  • Performance/DX hard calls:
    • nodeLayout is safe only if provider evaluation happens once per relevant top-level element per layout snapshot, then the result is cached by editor version/layout version.
    • Core pagination target complexity is O(blocks + units + lines/fragments). Fragment reads in renderers must be O(1), through a snapshot-built fragmentsBySourcePath / unitsBySourcePath map.
    • PagedEditable owns projection and fragment context. Element renderers must not rebuild projection maps, scan all fragments, or parse data-slate-path.
    • nodeLayout identity must participate in layout refresh. The pagination example should use useCallback only for state-dependent nodeLayout providers; ordinary render examples stay module-level or raw callbacks.
    • Paged mode virtualizes pages/spreads by default. Continuous block virtualization remains a separate fallback for non-paged huge docs.
  • Performance cohorts:
    CohortShapeRequired strategyProof target
    Normalup to 200 blocks or 10 pagesfull DOM acceptableno extra API ceremony
    Largeup to 2k blocks, 50 pages, or 5k layout unitspage/spread virtualizationO(visible + retained pages) mount
    Stress10k blocks, 200 pages, or a 1k-row tablepage/spread virtualization plus O(1) fragment lookuptyping in a second-page cell stays responsive
    Pathological50k blocks or 1k pagesexplicit degraded mode or continuous virtualization fallbackno native-parity claim without proof
  • Metrics required in execution:
    • layout duration, composition duration, mounted pages, mounted top-levels, DOM node count, component count, fragment count, cache size, degradation mode, nativeSurfaceComplete, scroll p95, and typing p95 in a second-page cell.
  • DX pressure:
    • Public API stays nodeLayout; public boxes is cut before beta.
    • Do not add path to RenderElementProps. Current surface-contract tests and the render-prop path solution note are right: paths move, public render props should not carry moving addresses.
    • Renderer DX is pathless: useSlateLayoutFragments() reads the current element/path from package-owned layout context under PagedEditable.
    • Non-renderer tools can use an explicit layout.getFragments(path) escape hatch.
    • Keep /examples/pagination as the canonical specimen. Do not add examples/pagination-basic; the example should gain multi-page table rows and relevant controls in place.
  • Migration pressure:
    • Plate can supply table/media nodeLayout providers with row heights, spans, repeated header policy, cell rects, media intrinsic sizes, and overflow policy. Raw Slate does not ship TableKit or table commands.
    • slate-yjs syncs the semantic AST only. Layout fragments are derived. Optional pageBreaks metadata may be shared as accepted layout state with measurementProfile and version; it must not become document children or operations.
  • Regression/TDD pressure:
    • Execution must be vertical red-green slices, not one giant "pagination" patch. Red rows:
      1. nodeLayout emits row units for one table AST and the pager splits those units across pages without AST split.
      2. Stable fragment ids include source path, provider unit key, page, and measurement profile/version or an equivalent collision-proof tuple.
      3. Oversized row/media policy: avoid moves if it fits; overflow/degraded behavior is explicit if it cannot fit.
      4. useSlateLayoutFragments() resolves current table fragments without reading data-slate-path.
      5. Virtualized paged tables retain visible, selected, focused, and composing pages.
      6. Playwright /examples/pagination proves a multi-page table, typing in a second-page cell updates the same AST table, virtualized strategy does not freeze, and selection/copy across a page boundary maps back to original paths.
  • Simplicity cuts:
    • No public boxes alias in docs or beta; internal adapter only if execution needs a temporary migration bridge.
    • No public pageVirtualization, TanStack-shaped options, TableKit, table-map commands, cell-selection UX, or renderer path props.
    • No nodeLayoutVersion until function identity/dependency handling fails in implementation. Start with stable callback identity and explicit refresh deps.
    • No default DOM measurement callback. Providers return model/intrinsic measurements; DOM measurement can be future opt-in only after browser proof demands it.

Legacy regression proof matrix:

Regression classLegacy behaviorSlate v2 targetProof routeOwnerStatus
Table across page boundarylegacy cannot own derived page layout layerone table AST, row fragments across pagesunit + Playwrightexecutioncomplete: bun --filter ./packages/slate-layout test; Chromium pagination test proves one table subtree, rows on multiple pages
Media/box too tall for pagelegacy layout is DOM/product ownedprovider declares avoid/page/overflow policyunit testsexecutioncomplete: oversized provider-owned box test places once and continues
Selection/caret in row after page breaklegacy DOM mapping fragile near structured blocksprojected fragment rect maps to original cell pathbrowser testexecutioncomplete for first slice: Chromium edits a visually second-page cell and keeps one table DOM subtree
Virtualized paged tableblock virtualization is wrong default in paged modepage/spread virtualization retains active/selected/composing pagesunit + Playwrightexecutioncomplete for first slice: Chromium virtualized DOM strategy row no longer freezes; native-complete claims remain excluded
Collab/export driftlegacy has no profile-aware authoritypageBreaks can store authoritative fragment idsunit testexecutioncomplete: unit pageBreak snapshot write/read covers provider-owned unit fragments

Browser stress / parity strategy:

SurfaceScenarioBrowser/deviceCommand or proof routeExpected signalStatus
/examples/pagination40-row table spans at least 2 pagesChromiumPlaywright focused rowone table node, rows on multiple pagescomplete
/examples/paginationtype in cell on second pageChromiumPlaywright focused rowmodel updates same table ASTcomplete
/examples/paginationDOM strategy virtualizedChromiumexisting pagination Playwright suite plus new rowno freeze, page items boundedcomplete
/examples/paginationselection/copy across page-boundary rowsChromium first, Firefox laterbrowser contractselection maps to original cell pathscomplete for Chromium default DOM mode
/examples/paginationduplicate editable path check for table rows/cellsChromiumPlaywright DOM queryno duplicate editable data-slate-path owner for the same row/cell source pathcomplete
/examples/paginationIME/composition in second-page cellChromium first, WebKit/Firefox later if promotedbrowser contractcomposing page stays mounted; text lands in same cellnot promoted: first slice covers keyboard input; IME-specific proof remains release-gate backlog
/examples/paginationrepeated table header policyChromiumPlaywright/source checkno editable repeated header clone in first execution slicecomplete by exclusion: no repeated header renderer was added

Verification workspace gate:

ClaimWorkspaceCommandResultOwner
Current slate-layout tests pass and source shape is live.tmp/slate-v2bun --filter ./packages/slate-layout testpass, 31 testscurrent pass
Current slate-react virtualized/native-coverage guards pass.tmp/slate-v2bun --filter ./packages/slate-react test:vitest -- test/dom-strategy-page-virtualization.test.tsx test/dom-coverage-native-bridge-contract.test.tspass, 2 files, 13 testshigh-risk pass
Multi-page table provider behavior.tmp/slate-v2focused unit tests to addpendingexecution
Multi-page table browser behavior.tmp/slate-v2focused /examples/pagination Playwright rowspendingexecution

Applicable implementation-skill review matrix:

LensAppliesStatusFindingsPlan delta
vercel-react-best-practicesyesapplieduseSyncExternalStore is right for layout snapshots; avoid broad context re-render, per-render projection scans, and stale provider depsadd O(1) fragment maps and include nodeLayout identity in refresh semantics
performance-oracleyesappliedtarget O(blocks + units + lines/fragments), O(1) lookup, bounded caches, provider once per block/snapshotadded cohort budgets, cache requirements, and metrics
performanceyesappliedpage/spread is the repeated unit in paged mode; block virtualization is fallback, not defaultadded normal/large/stress/pathological cohorts and degradation proof
tddyesappliedexecution must be red-first vertical slices through public behavior, not internal-only assertionsadded six red rows spanning unit, React, and Playwright proof
shadcnpartialappliedexample controls should be real inputs/selects/toggles and stay utilitarian; no decorative or duplicate example pagekeep /examples/pagination; add only rows, sizing, media/overflow, DOM strategy, and page view controls
react-useeffectyesappliedderived projection/fragment maps belong in memoized snapshots/store selectors, not effect-plus-state loopskeep effects for layout refresh/destroy only; renderer hook consumes snapshot context
code-simplicity-revieweryesappliedcut aliases and product-shaped APIs; do not add TableKit, pageVirtualization, nodeLayoutVersion, DOM measurement default, or public path propsrecorded simplicity cuts

High-risk deliberate-mode pre-mortem:

RiskTriggerFailure modeDecision / mitigationRequired proofStatus
Duplicate editable DOM for one Slate pathtable/page fragments or repeated headersDOM bridge, selection, copy, and IME resolve the wrong ownerFirst execution must render at most one editable data-slate-node owner per source path. Page wrappers can be inert chrome; repeated headers are out of first execution slice. Later repeated headers must be clone-free or aria-hidden decoration.Playwright asserts no duplicate editable [data-slate-path] owners for table rows/cells and no editable repeated header clonesaccepted guardrail
Split table semantics leak into ASTrow paginationone table becomes many document nodes; undo/collab/export corruptPagination creates derived fragments only. Tests assert one canonical table node remains after pagination and second-page cell edits.unit test plus Playwright model assertion after editing a second-page cellaccepted guardrail
Virtualized DOM claims native completenesspage/spread virtualizationbrowser find, selection, copy, accessibility, and IME appear supported while content is unmountedVirtualized mode keeps nativeSurfaceComplete=false and degradationMode='virtualized'. Claims require model-backed copy/materialize behavior and page retention proof.focused slate-react metrics tests plus /examples/pagination browser rowsaccepted guardrail
Selection/copy crosses an unmounted page boundarypage-level virtualizationnative clipboard reads stale/partial DOM or drops hidden rowsEither materialize the missing page range before native work or use DOMCoverage/model-backed copy. Do not claim native copy across virtualized pages until proven.Playwright selection/copy across page-boundary rows; model text/html excludes stale DOMaccepted guardrail
IME/composition in a second-page cellvirtualized paged tablecomposing page unmounts, losing native composition statePage/spread mount plan must retain composing top-level index; execution adds a browser row for composition/typing in a second-page cell.existing mount-plan unit evidence plus new browser rowaccepted guardrail
Provider API too product-shapedtable exampleraw Slate becomes TableKit-liteExample provider may emit row units and simple overflow policy only. No table commands, table maps, cell selection UX, rowspan policy, GFM, menus, or production TableKit.API review and example source reviewaccepted guardrail
Oversized rows/mediarow or image taller than pageinfinite pagination loop, invisible content, or silent clippingProvider/core policy must choose explicit overflow / degraded behavior when a unit cannot fit. Avoid split only when it can actually fit.focused unit test for too-tall row/mediaaccepted guardrail
Page breaks drift in collab/exportPretext measurementpeers compute different breaks and export disagrees with screenDefault remains local and best-effort. Strict users opt into pageBreaks authored by one client/server with measurement profile and layout version; stale snapshots are ignored/flagged.unit test for profile/version mismatch and accepted snapshot read/writeaccepted guardrail
Fragment ids are unstableprovider unit keys or profile changesremote cursors/export refs point at wrong fragmentFragment id tuple includes source path/runtime identity, provider unit key, page index, layout version, and measurement profile or equivalent collision-proof data.unit test across relayout/profile changesaccepted guardrail
Accessibility overclaimvirtualized/page chromescreen readers see partial document while API implies full native surfaceVirtualized paged mode is explicit degraded/native-incomplete until a11y proof exists. Page chrome must not masquerade as editable content.release guard, not first execution claimaccepted guardrail

High-risk deliberate-mode result:

  • Hard decision: no duplicate editable DOM for the same Slate source path in the first execution slice. Repeated table headers are explicitly out of that slice. Later header repeat must be clone-free or inert decoration, not editable Slate DOM with duplicate paths.
  • Hard decision: virtualized paged mode is not native-surface complete. The API may expose useful model-backed copy/materialize behavior, but it must report degradationMode='virtualized' and avoid native parity claims until browser proof exists.
  • Hard decision: table pagination proves the substrate with row units only. It does not solve rowspan layout, table maps, cell selection UX, header repeat, Markdown/GFM, or production Plate table behavior.
  • Hard decision: strict collab/export fidelity remains opt-in pageBreaks authority with profile/version checks. Derived fragments never become Yjs document children or Slate operations.
  • Execution proof added by this pass:
    • no duplicate editable [data-slate-path] owners for table rows/cells
    • second-page cell edit mutates the same table AST
    • selection/copy across a page boundary either materializes missing pages or uses model-backed DOMCoverage without stale DOM
    • IME/composition in a second-page cell keeps the page mounted
    • virtualized strategy reports degraded/native-incomplete metrics
    • oversized row/media policy is explicit and non-looping

Ecosystem maintainer pass:

SystemMaintainer pushbackFair correctionSlate decisionPlan delta
Pretext"Do not call this deterministic/headless yet; prepare() intentionally measures with canvas and profile-specific behavior."Accepted. Pretext is the right text engine because it moves work out of the hot resize path, not because it gives server/client byte-for-byte breaks today.Keep Pretext as default text layout engine; keep measurementProfile; keep strict pageBreaks authority opt-in. If Pretext later gets headless measurement, the API should improve without changing shape.No deterministic default claim; score evidence strengthened.
Tiptap Pages"Pages TableKit is a legitimate product-extension answer; framing it as simply wrong is unfair."Accepted. For Tiptap's product stack, a specialized TableKit can be rational. It is wrong only as the raw Slate ownership model.Steal the failure taxonomy and explicit-break pressure. Do not copy CSS-float pagination, manual semantic splitting, or raw Slate TableKit.Wording changed to respectful divergence.
TanStack Virtual"TanStack solves range math, not editor semantics. Do not blame it for native-surface gaps."Accepted. Slate must own DOM coverage, materialization, selection, copy/paste, IME, mobile, browser-find, a11y, metrics, and degradation classification.Use TanStack-style range extraction internally if useful; never expose TanStack-shaped public options. Page/spread is the paged repeated unit.Public API remains Slate-shaped; high-risk proof stays required.
Premirror / Pretext pagination"Do not over-copy a product demo or assume its constraints match Slate."Accepted. The useful mechanism is derived pages/fragments over a semantic document, not a product API clone.Adopt snapshot -> measure -> compose -> render as architecture language; keep raw Slate unopinionated.Derived-fragment language kept; product cloning rejected.

Ecosystem maintainer pass result:

  • Pretext: strongest caveat survives. Slate can default to Pretext while being honest that prepare() currently depends on canvas measurement and browser profile behavior. The API must not promise cross-client/server break identity unless an authoritative pageBreaks snapshot is provided.
  • Tiptap: wording tightened. Tiptap Pages is not a bad product answer; it is the wrong raw Slate substrate because it relies on CSS floats, a specialized Pages TableKit, and manual split workarounds for exactly the BFC/table/media classes Slate wants provider-owned layout fragments to handle generically.
  • TanStack: the plan keeps TanStack as internal infrastructure only. Slate owns retained-page policy, DOM coverage, native-surface metrics, and materialization.
  • Premirror: keep the derived-page mental model; do not clone a product API.
  • Revision requirement created by this pass: normalize final wording so it says "diverge respectfully from Tiptap's product approach" rather than treating Tiptap Pages as failed architecture.

Revision pass result:

  • Final API wording:
    • useSlateLayout(editor, { page, root, typography, nodeLayout, pageBreaks }) remains the user-facing layout entrypoint.
    • nodeLayout({ element, path, defaults, pageSettings, measurementProfile }) is the public provider shape. It does not receive current page index, cursor position, or pagination placement state.
    • SlateNodeLayoutPlan has three public forms for the execution plan: text fallback, fixed/avoid box, and provider-owned units.
    • useSlateLayoutFragments() is the renderer API under PagedEditable. layout.getFragments(path) remains the low-level explicit-path API for tools. No public RenderElementProps.path.
    • Paged mode virtualizes pages/spreads as the repeated unit and reports degraded/native-incomplete behavior until browser proof promotes specific native-surface claims.
  • Final example wording:
    • Extend /examples/pagination; do not create pagination-basic.
    • Controls: row count, row height, media height/overflow, DOM strategy, and page view.
    • No header-repeat control or repeated editable header clone in the first execution slice.
  • Final ecosystem wording:
    • Pretext is the default text layout engine with explicit canvas/profile caveat.
    • Tiptap Pages is a valid product-extension approach in its stack; raw Slate respectfully diverges from its CSS-float/manual-split/TableKit ownership model.
    • TanStack remains internal range infrastructure only.
    • Premirror contributes the derived-page mental model, not an API clone.
  • Final issue/reference handoff:
    • The revision pass changed public narrative wording enough that the next pass must inspect docs/slate-v2/ledgers/issue-coverage-matrix.md, docs/slate-issues/gitcrawl-v2-sync-ledger.md, and docs/slate-v2/references/pr-description.md.
    • Expected result is likely a no-claim accounting update or explicit skip, not new Fixes / Improves claims.

Slate maintainer objection ledger:

ChangeStrongest objectionAccepted answerRequired guardrailEvidenceVerdict
Replace shallow boxes with nodeLayout plans"This is too much layout API for raw Slate; tables/media are app concerns."Correct that product behavior is app/Plate-owned, but raw Slate already owns derived pagination. The minimal raw substrate is a generic node layout plan, not a table package.nodeLayout stays generic: text, fixed box, provider-owned units. No table commands, maps, cell selection, menus, or Markdown/GFM product semantics.Current boxes supports table/table-cell/row vocabulary but pager still line-splits at .tmp/slate-v2/packages/slate-layout/src/index.ts:105-139, :1954-2053.keep/revise
Provider inputs include page/layout context"If provider output depends on current page placement, caching and determinism are toast."Accepted. nodeLayout receives page settings and measurement profile, not current page index/cursor. Pagination placement stays core-owned.Provider output must be pure for a given element/path/defaults/page settings/profile snapshot; refresh depends on nodeLayout identity.useSlateLayout refresh deps currently omit the provider identity at .tmp/slate-v2/packages/slate-layout/src/react.tsx:53-70; pressure pass requires fixing that.revise
Add pathless fragment render access"Render props should not know pagination, and path props caused perf bugs."Correct. Do not add path to public RenderElementProps. The hook is optional context under PagedEditable, and non-paged Editable remains unchanged.useSlateLayoutFragments() resolves from package-owned element/path context; optional layout.getFragments(path) is for tools, not normal render props.RenderElementProps has no path at .tmp/slate-v2/packages/slate-react/src/components/editable.tsx:40-50; surface contract forbids path/index at test/surface-contract.tsx:24-36; solution note rejects moving paths.keep
Keep pageBreaks authority optional"Strict export/collab users need deterministic breaks, not local best effort."Correct for strict users, wrong as default. Current Pretext measurement is canvas/profile-sensitive; default API must not promise headless determinism.pageBreaks stores accepted fragment/page results with measurement profile/version and can be authored by one client/server. Local recompute marks stale when profile/version mismatches.Current layout already has profile-checked pageBreaks; Pretext research keeps canvas drift explicit.keep
Page/spread virtualization default in paged mode"Virtualization can break native selection, copy, IME, find, accessibility, and SEO-ish DOM expectations."Correct risk, but paged docs should virtualize repeated page/spread units, not arbitrary blocks. Native-surface claims wait for proof.Page/spread mount plan must retain visible, selected, focused, composing, and promoted pages; missing DOM must be reported through degradation metrics.PagedEditable already builds page items at .tmp/slate-v2/packages/slate-layout/src/react.tsx:181-252; tests retain selected/composing pages.keep with proof gate
No raw Slate TableKit"Users need table pagination to work; a generic provider makes every app rebuild table splitting."Raw Slate should provide the substrate and a minimal example provider. Plate/app packages provide production table semantics. Shipping TableKit in raw Slate is the wrong ownership line./examples/pagination proves one multi-page table AST and row units; docs say production tables plug provider policy in app/Plate.Prior solution notes and PR reference keep product table packages out of raw Slate.keep
No AST split"Splitting table nodes in the AST is simpler for rendering/export and mirrors page output."It is simpler for rendering and worse for editing. It corrupts undo, collab, selection, copy, and semantic export. Derived fragments give page output without document mutation.Tests must assert one table node remains one table node after pagination and second-page cell edits.Plan TDD queue covers no AST split and same-table second-page editing.keep
Keep /examples/pagination as the target example"A dense example with pagination, virtualization, tables, and media will be too complex."A second pagination-basic page would hide the canonical DX problem. One example with restrained controls is better.Controls limited to rows, row height, media height/overflow, DOM strategy, and page view. No product table toolbar.Existing example already owns pagination controls; pressure pass rejected duplicate route.keep
Pretext remains the text engine target"Pretext is not fully headless; canvas measurement drift weakens the architecture."Correct caveat, not a blocker. Use Pretext for text layout now, keep measurementProfile, and keep strict page-break authority opt-in.No byte-for-byte cross-client/export promise until headless measurement exists or an authoritative writer/server provides breaks.Memory/research notes and current plan keep canvas drift explicit.keep with caveat

Maintainer objection pass result:

  • Strongest accepted revision: rename the provider context from ambiguous current-page page to pageSettings plus measurementProfile. Providers describe node layout; the core pager owns placement.
  • Strongest rejected objection: adding public path to render props for easier fragment lookup. That would reintroduce the exact moving-path perf/staleness bug the rewrite already fixed.
  • Most dangerous future drift: letting the table example become TableKit-lite. The execution slice must prove row units with boring app code, not table commands, selection models, or Markdown semantics.
  • Proof stance: no issue/fidelity/native-behavior promotion from this planning pass. High-risk deliberate mode must pressure duplicate DOM, selection/copy/IME, and virtualized native-surface behavior before closure.

Hard cuts and rejected alternatives:

Option / APIKeep / cut / rejectWhyMigration costEvidenceFollow-up
AST table splitting for paginationrejectbreaks semantic document, undo, collab, exportnone acceptedTiptap manual split warning, Slate boundary; high-risk pass reaffirmed no splitexecution proof
Raw Slate TableKitcutproduct package belongs to Plate/appnone acceptedsolution note and PR referenceissue sync complete
CSS-float pagination as raw Slate substraterejectBFC/table/media failure class and product-specific ownershipnone acceptedTiptap limitations docs via research; ecosystem pass reframed as respectful divergenceissue sync complete
Existing boxes as final APIreviseuseful seed, not enough for row/unit fragmentsalpha hard cutlive source and maintainer objectionsissue sync complete

Plan deltas from review:

  • Created Slate Plan artifact from template.
  • Grounded current state in live slate-layout, PagedEditable, and /examples/pagination source.
  • Chose provider-owned node layout plans and unit/page fragments over AST split, shallow boxes-only, CSS floats, or raw Slate TableKit.
  • Added execution queue target for multi-page table example and robust TDD.
  • Completed related issue discovery from existing ledgers without rerunning ClawSweeper because the touched issue surface matches the 2026-05-25 pagination planning sync.
  • Completed issue-ledger pass by proving the existing matrix, v2 sync ledger, fork dossier, and PR reference already cover the plan's claim boundaries.
  • Completed intent/boundary and decision brief; committed to nodeLayout as the public experimental API and cut public boxes before beta.
  • Completed research/ecosystem/live-source refresh; Pretext, Tiptap Pages, TanStack Virtual, and live .tmp/slate-v2 source still support nodeLayout plus provider-owned units/fragments.
  • Completed performance/DX/migration/regression/simplicity pressure; tightened the target to O(1) fragment lookup, pathless useSlateLayoutFragments(), page/spread virtualization by default in paged mode, vertical TDD slices, and no public boxes alias.
  • Completed maintainer objection ledger; accepted the provider-context revision from ambiguous page to pageSettings plus measurementProfile, rejected public render-prop paths, and kept the no-TableKit/no-AST-split boundary.
  • Completed high-risk deliberate mode; rejected duplicate editable DOM, excluded repeated headers from the first execution slice, recorded virtualized DOM as degraded/native-incomplete, and added model-backed copy/materialize, composition-retention, duplicate-path, and oversized-unit proof gates.
  • Completed ecosystem maintainer pass; kept Pretext caveats precise, reframed Tiptap Pages as a valid product-extension approach that raw Slate should respectfully diverge from, kept TanStack internal, and limited Premirror to derived-layout inspiration.
  • Completed revision pass; normalized final public API wording, accepted useSlateLayoutFragments() as the renderer hook name, removed header-repeat controls from the first-slice example, and queued issue/reference accounting for the revised public narrative.
  • Completed issue sync accounting; added a 2026-05-26 no-claim sync to the issue matrix and manual sync ledger, and synced the PR reference to the accepted public API target.
  • Closed planning lane for user review; execution starts only after explicit accepted-plan invocation.

Open questions and decision-changing evidence:

QuestionWhy it mattersEvidence neededOwnerStatus
Can one DOM subtree with row/unit positioning preserve table selection well enough?avoids duplicate DOM path ambiguitybrowser proof after execution sliceexecutionnarrowed: first slice forbids duplicate editable DOM; browser proof still pending
Does provider API need DOM measurement callbacks or only model/intrinsic sizing?impacts performance and React effectspressure pass plus example proofslate-plananswered: model/intrinsic provider measurements by default; DOM measurement future opt-in only if browser proof demands it
Should renderers receive path to make fragments easy?moving paths cause rerender breadth and stale handler bugscurrent surface contract and solution noteslate-plananswered: no public path prop; use pathless useSlateLayoutFragments() under PagedEditable
Should boxes remain as alias or be hard-cut before publish?migration/DX costintent/decision passslate-plananswered: hard-cut public boxes; internal adapter only if needed during execution
Should nodeLayout receive the current page?current-page input would make provider output placement-dependent and hard to cachemaintainer objection passslate-plananswered: no current page/cursor; provider gets page settings and measurement profile
Should repeated table headers ship in the first multi-page table example?repeated editable header clones would duplicate Slate paths and break selectionhigh-risk deliberate modeslate-plananswered: no; first slice excludes repeated headers, later support must be clone-free or inert decoration
Can virtualized paged mode claim native surface completeness?unmounted pages break native find/selection/copy/a11y assumptionscurrent slate-react metrics and DOM coverage testsslate-plananswered: no; virtualized mode is degraded/native-incomplete until browser proof promotes specific behavior
Are we overstating Tiptap as failure evidence?unfair ecosystem framing can distort the Slate decisionlocal Tiptap Pages docs and ecosystem maintainer passslate-plananswered: yes; Tiptap is a valid product-extension approach, but raw Slate should diverge from its ownership/model
Should TanStack Virtual surface in the public API?leaking virtualizer details would make Slate API less editor-nativeTanStack research and high-risk passslate-plananswered: no; internal range engine only
Is useSlateLayoutFragments() the accepted renderer hook name?lingering naming pressure blocks user-review-ready API textrevision passslate-plananswered: yes for the plan; execution can still bikeshed only with stronger DX evidence
Does the revised public narrative require issue/reference sync?claim text changed around nodeLayout, Tiptap framing, and virtualized native-surface proofrevision pass plus issue sync passslate-plananswered: yes; synced issue matrix, manual sync ledger, and PR reference with no new fixed/improved claims

Implementation phases with owners:

PhaseOwnerScopeEntry criteriaExit criteriaVerification
1. Red unit testsexecutiontable row unit pagination, no AST split, oversized row policyaccepted planfailing tests prove missing behaviorfocused slate-layout tests
2. Layout protocolexecutionnodeLayout page-settings/profile context, unit fragments, projected child path rectsred testsunit tests greenbun --filter ./packages/slate-layout test
3. React render accessexecutionPagedEditable layout context plus pathless fragment hook, duplicate-path guard, and virtualized metricsphase 2 greenexample no longer parses path attr map, public render props still omit path, virtualized mode reports degraded/native-incompleteslate-react tests + typecheck
4. Pagination exampleexecutionmulti-page table fixture and controls, no repeated editable headersphase 3 greenrows span pages; same AST table; no duplicate editable path ownersPlaywright /examples/pagination
5. Broad proof and reviewexecutionpackage gates, autoreview, issue/reference syncphases greenno accepted/actionable findingsfocused + broad feasible gates

Fast driver gates:

GateCwdCommand / artifactProvesStatus
planning artifact checkplate-2node .agents/rules/autogoal/scripts/check-complete.mjs docs/plans/2026-05-26-slate-v2-provider-owned-page-layout-fragments.mdfinal plan closure onlypassed in closure run
current source behavior check.tmp/slate-v2bun --filter ./packages/slate-layout testcurrent layout tests and API shape still compile/runpassed in closure rerun
execution unit gate.tmp/slate-v2bun --filter ./packages/slate-layout test after accepted implementationprovider-owned fragmentspassed, 35 tests
execution React gate.tmp/slate-v2bun check after accepted implementationrender/layout integrationpassed, includes slate-react vitest 43 files / 403 tests
execution browser gate.tmp/slate-v2PLAYWRIGHT_RETRIES=0 PLAYWRIGHT_WORKERS=1 bun playwright playwright/integration/examples/pagination.test.ts --project=chromiummulti-page table browser behaviorpassed, 12 tests
high-risk planning React gate.tmp/slate-v2bun --filter ./packages/slate-react test:vitest -- test/dom-strategy-page-virtualization.test.tsx test/dom-coverage-native-bridge-contract.test.tscurrent virtualized/degraded DOM and model-backed coverage guardspassed in closure rerun

Final user-review handoff outline:

  • accepted plan items: nodeLayout, provider-owned units/fragments, useSlateLayoutFragments(), explicit layout.getFragments(path), page/spread virtualization in paged mode, and opt-in pageBreaks
  • before / after API shape: replace public boxes with nodeLayout({ element, path, defaults, pageSettings, measurementProfile }); renderers use pathless fragment context instead of public render-prop paths
  • hard cuts: no public boxes, no raw Slate TableKit, no AST table splitting, no public RenderElementProps.path, no duplicate pagination-basic, no repeated editable header clones in the first execution slice
  • issue claims and non-claims: closed in issue sync; summarize in closure
  • proof gates: current layout/React planning gates pass; browser/native claims stay queued for execution and are not promoted by this planning pass
  • accepted-plan execution handoff: implement tests first, then layout protocol, React fragment context, /examples/pagination, browser proof, autoreview, and issue/reference sync

Final completion gates:

GateRequired evidenceStatus
score >= 0.92 and no dimension below 0.85scorecard rows cite evidencecomplete
all pass rows complete or skipped with evidencephase/pass table closedcomplete
issue/reference sync closedissue-ledger sync status closedcomplete
live source grounding completesource-backed rows cite current ownerscomplete
workspace verification recordedverification workspace gate closedcomplete
browser proof or N/Aplanning pass makes no browser/native promotion; execution queue owns browser proofN/A for planning closure
autoreview clean or N/AN/A for planning; required for execution implementation changescomplete
final handoff emitted or lane remains completefinal response / next pass recordedcomplete
check-complete passesnode .agents/rules/autogoal/scripts/check-complete.mjs docs/plans/2026-05-26-slate-v2-provider-owned-page-layout-fragments.mdcomplete

Findings:

  • Current source already models box kind and split vocabulary, including table, table-cell, and split: 'row', but row splitting is not consumed by the pager.
  • The existing provider test proves app-owned media/BFC sizing is possible, but only for fixed boxes, not provider-owned pagination fragments.
  • The example demonstrates the right concept but dirty DX: it manually maps projected blocks from data-slate-path.
  • Prior solution notes already forbid raw Slate Markdown/table product packages.

Decisions and tradeoffs:

  • Choose generic node layout plans and unit/page fragments.
  • Keep page/spread virtualization as the paged-mode repeated unit.
  • Keep strict page-break authority opt-in.
  • Accept a larger experimental layout protocol because the alternative is fake table pagination or AST mutation.

Error attempts:

Error / failed attemptCountNext different moveResolution
None in this pass0

External/browser findings:

  • Tiptap Pages evidence is treated as failure taxonomy and product-approach contrast, not a system to copy. Official local docs confirm CSS float/BFC/table limitations and a separate heavily modified Pages TableKit.
  • Pretext source supports text layout, with canvas measurement/profile drift caveats and arithmetic hot-path relayout.
  • TanStack Virtual evidence supports internal range management, not public API. Slate keeps DOM coverage, selection, IME/mobile, and a11y policy.

Timeline:

  • 2026-05-26 Slate Plan goal plan created.
  • 2026-05-26 Current-state pass completed with live source and focused slate-layout verification.
  • 2026-05-26 Related issue discovery completed from existing ledgers; no fresh broad GitHub issue search or ClawSweeper rerun needed.
  • 2026-05-26 Issue-ledger pass completed; no ledger/reference sync edit needed because existing rows already cover the exact provider/split no-claim policy.
  • 2026-05-26 Intent/boundary and decision brief completed; nodeLayout chosen as public experimental API and public boxes cut before beta.
  • 2026-05-26 Research/ecosystem/live-source refresh completed; local Pretext, Tiptap Pages, TanStack Virtual, and .tmp/slate-v2 source/tests still support nodeLayout plus provider-owned units/fragments.
  • 2026-05-26 Performance/DX/migration/regression/simplicity pressure completed; pathless fragment hook, O(1) lookup, page/spread budgets, Plate/slate-yjs boundaries, vertical TDD queue, and simplicity cuts recorded.
  • 2026-05-26 Maintainer objection ledger completed; nodeLayout provider context tightened to page settings/profile, pathless fragment hook retained, no TableKit/no AST split reaffirmed, and native-surface proof deferred to high-risk pass.
  • 2026-05-26 High-risk deliberate mode completed; duplicate editable DOM rejected, repeated headers excluded from first execution slice, virtualized DOM kept degraded/native-incomplete, and DOMCoverage/materialization proof rows added.
  • 2026-05-26 Ecosystem maintainer pass completed; Pretext caveat kept, Tiptap reframed as valid product-extension evidence but wrong raw Slate ownership, TanStack kept internal, and Premirror limited to derived-layout inspiration.
  • 2026-05-26 Revision pass completed; final API wording normalized around nodeLayout, useSlateLayoutFragments(), page-settings/profile provider input, no repeated editable headers in the first slice, and respectful Tiptap divergence.
  • 2026-05-26 Issue sync accounting completed; issue matrix, manual sync ledger, and PR reference now name the final public API target and preserve the no-new-claim boundary.

Verification evidence:

  • cwd=/Users/zbeyens/git/plate-2/.tmp/slate-v2
  • Closure rerun: bun --filter ./packages/slate-layout test
  • Result: pass, 31 tests, 0 fail, 108 expects.
  • Closure rerun: bun --filter ./packages/slate-react test:vitest -- test/dom-strategy-page-virtualization.test.tsx test/dom-coverage-native-bridge-contract.test.ts
  • Result: pass, 2 files, 13 tests.
  • Source evidence: .tmp/slate-v2/packages/slate-layout/src/index.ts:105-139 still exposes box kinds, row split vocabulary, and the current shallow boxes provider; .tmp/slate-v2/packages/slate-layout/src/index.ts:355-374 still exposes public boxes; .tmp/slate-v2/packages/slate-layout/src/index.ts:1970-2027 still paginates by estimated lines and avoid-split boxes, proving the plan's provider-owned unit/fragments delta is real work.
  • Planning artifact proof: node .agents/rules/autogoal/scripts/check-complete.mjs docs/plans/2026-05-26-slate-v2-provider-owned-page-layout-fragments.md passed in the closure run.

Execution evidence:

  • cwd=/Users/zbeyens/git/plate-2/.tmp/slate-v2
  • Implemented nodeLayout provider-owned text/box/unit plans in packages/slate-layout/src/index.ts; public boxes option is cut in favor of nodeLayout.
  • Implemented useSlateLayoutFragments() as a pathless rendered-fragment hook in packages/slate-layout/src/react.tsx. The hook returns projected SlateLayoutRenderedFragment rects/units and does not expose mixed layout-local top.
  • Extended existing site/examples/ts/pagination.tsx; no pagination-basic route was added. Controls now cover table rows, row height, media height, media split policy, DOM strategy, facing pages, and debug frames.
  • Added focused unit tests for provider-owned table row units, oversized media policy, authoritative page breaks for provider-owned unit fragments, and pathless rendered-fragment access.
  • Added Chromium /examples/pagination rows for multi-page table rendering, second-page cell editing, copy across page-boundary rows, duplicate editable-path detection, rich content inside page frames, and virtualized DOM strategy no-freeze.
  • Final command gate: bun check passed.
  • Browser gate: PLAYWRIGHT_RETRIES=0 PLAYWRIGHT_WORKERS=1 bun playwright playwright/integration/examples/pagination.test.ts --project=chromium passed, 12 tests.
  • Live dev-server smoke: PLAYWRIGHT_BASE_URL=http://localhost:3100 PLAYWRIGHT_RETRIES=0 PLAYWRIGHT_WORKERS=1 bun playwright playwright/integration/examples/pagination.test.ts --project=chromium --grep "renders a multi-page table" passed against the already-running .tmp/slate-v2/site server.
  • Autoreview: two Codex-engine helper runs hung idle; fallback ../../.agents/skills/autoreview/scripts/autoreview --mode local --engine claude --no-tools completed clean after the coordinate-space finding was fixed. Final structured result: autoreview clean: no accepted/actionable findings reported.

Reboot status:

QuestionAnswer
Where am I?Closure score and final gates are complete.
Where am I going?User review; implementation starts only after explicit accepted-plan invocation.
What is the goal?Review-ready plan for provider-owned paginated layout fragments and multi-page table execution.
What have I learned?The best long-term API is nodeLayout with page-settings/profile provider input, provider-owned units/fragments, pathless useSlateLayoutFragments(), page/spread virtualization that reports degraded/native-incomplete until proven, opt-in strict breaks, no repeated editable header clones, no TableKit, and no AST split. Tiptap's product approach is valid in its stack but the wrong raw Slate ownership model.
What have I done?Created and populated the plan, reran focused .tmp/slate-v2 layout and React high-risk tests, closed related issue discovery, issue-ledger, intent/decision, research/live-source, pressure, maintainer-objection, high-risk, ecosystem maintainer, revision, issue-sync, and closure passes.

Open risks:

  • Planning risks: none blocking user review.
  • Execution risks: duplicate DOM for repeated header/table fragments, browser-native behavior under page/spread virtualization, IME/composition in second-page cells, copy/materialization across unmounted page ranges, and oversized row/media policy all remain explicit execution proof gates.