Back to Plate

Slate v2 Pretext Pagination Virtualization Feedback

docs/plans/2026-05-25-slate-v2-pretext-pagination-virtualization-feedback.md

53.0.6105.1 KB
Original Source

Slate v2 Pretext Pagination Virtualization Feedback

Objective: Close the Slate Plan lane for the virtualization / pagination feedback before user review. The plan must cover Pretext as the layout engine, current canvas.measureText() determinism limits, cross-platform line-break drift, opt-in authoritative page-break snapshots for collab/export fidelity, page-level virtualization when pagination is enabled, Tiptap pagination edge cases for tables/images/font variability/padding, Slate v2 public API/runtime proof gates, issue/reference accounting, score deltas, maintainer objections, and final handoff.

Goal plan: docs/plans/2026-05-25-slate-v2-pretext-pagination-virtualization-feedback.md

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

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

Applied packs:

  • slate-plan

Completion threshold:

  • Score >= 0.92, no dimension below 0.85.
  • Every pass row complete or intentionally skipped with evidence.
  • Related issue accounting closed against live/current ledgers.
  • Final handoff emitted and no runnable plan-hardening action remains.
  • node .agents/rules/autogoal/scripts/check-complete.mjs docs/plans/2026-05-25-slate-v2-pretext-pagination-virtualization-feedback.md passes.

Verification surface:

  • Planning artifacts in plate-2: this plan, related issue ledgers, research notes if updated, and completion check.
  • Slate v2 source/API/runtime claims must cite live .tmp/slate-v2 source reads or a .tmp/slate-v2 command.
  • Browser/runtime behavior claims require .tmp/slate-v2 browser proof in a later pass. This pass records source evidence only.

Constraints:

  • Planning mode only. Do not patch .tmp/slate-v2 implementation.
  • Allowed edit scope: docs/plans/**, docs/research/**, docs/slate-issues/**, docs/slate-v2/ledgers/**, docs/slate-v2/references/**.
  • Keep Slate core Pretext-free. Layout is optional derived view data.
  • Do not claim headless deterministic pagination until the measurement layer is actually headless and cross-platform-stable.

Boundaries:

  • Local source of truth: .tmp/slate-v2, ../pretext, existing plan/ledger artifacts.
  • External sources: Cyrus Radfar Pretext article, local ../tiptap-docs Pages docs, official Tiptap Pages docs, and Romik Makavana Tiptap pagination article URL, read only as evidence.
  • GitHub Pretext source was read from local ../pretext, not browsed.

Blocked condition:

  • Block only if the same external source or workspace access failure prevents issue discovery, source grounding, score closure, or final verification for three consecutive activations.
  • Do not use blocked while another research, ledger, objection, scoring, 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: none
  • final_handoff_status: complete

Current verdict:

  • verdict: ready for user review
  • confidence: 0.94 for planning readiness; implementation/browser proof remains execution-gated
  • keep / cut / revise call:
    • Keep Pretext as the default slate-layout engine.
    • Cut any language that implies Pretext gives cross-client/server page-break determinism today.
    • Revise pagination virtualization: paged mode virtualizes pages/spreads first, not top-level blocks.
    • Add an opt-in authoritative page-break snapshot channel for strict collab and export fidelity.
    • Keep block virtualization for continuous/pathological documents, fed by layout when available.
  • reason: current Slate v2 already has page snapshots, fragments, projections, Pretext-backed line layout, state fields, and TanStack-backed top-level virtualization, but those pieces are wired in the wrong repeated unit for paged documents and the determinism contract is currently too optimistic. Maintainer pressure keeps the plan but narrows ownership: raw Slate exposes substrate, slate-layout exposes derived layout and provider protocols, React owns DOM materialization, and apps/Plate own product UI and export policy. The ecosystem maintainer pass confirms there is no strategy reversal: Tiptap is negative evidence for CSS pagination, Premirror supports derived layout fragments, TanStack stays an internal range engine, and DOMCoverage stays Slate-owned browser-policy infrastructure. The revision pass locks the public beta direction: pageView replaces scattered page display props, domStrategy remains the only public incomplete-DOM switch, page virtualization is internal to PagedEditable, measurementProfile is metadata not a required user knob, and pageBreaks stays opt-in strict-fidelity metadata. High-risk pressure keeps implementation claims gated until the missing release proofs exist; adjacent tests are good evidence, not final proof. Issue sync accounting confirms the final API/proof wording changes no fixed/improved issue counts and is now reflected in the v2 sync ledger, coverage matrix, fork dossier, and PR reference. The closure pass verified the score threshold, pass table, planning-only verification surface, issue/reference sync, final handoff, and no-runnable-planning-work state.

Completion rule:

  • Do not call update_goal(status: complete) while any pass/checklist/gate remains pending.
  • Goal completion is legal only in the closure score/final gates pass.

Start Gates:

GateAppliesEvidence
Skill analysis before editsyesslate-plan skill read before plan edit
Active goal checked or createdyesactive goal objective matches this lane
Source of truth read before editsyes.tmp/slate-v2, ../pretext, prior plans, issue ledgers, external articles read
docs/solutions checked for non-trivial existing-code workyesrg found no directly reusable solution note
Live .tmp/slate-v2 grounding needed for current-state claimsyessource reads recorded below

Work Checklist:

  • Objective includes lane outcome, pass schedule, one-pass-per-activation policy, completion threshold, verification surface, constraints, boundaries, and blocked condition.
  • One-pass-per-activation policy respected: this activation closes only the Closure score and final gates pass.
  • Live source grounding recorded for current implementation claims.
  • Issue ledger / ClawSweeper pass applied: gitcrawl-v2 sync ledger, issue coverage matrix, and fork dossier updated; PR reference inspected and left unchanged because this planning pass adds no fixed/improved claims.
  • Research and ecosystem synthesis complete for every external system used as evidence in this pass.
  • Intent/boundary record and decision brief complete.
  • 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.
  • Verification workspace gate recorded for every Slate v2 source, runtime, browser, package, public API, or issue-fix claim.
  • TDD marked N/A for this pass: no implementation or behavior proof edited.
  • Browser proof marked N/A for this pass: source-only planning pass.

Completion Gates:

GateAppliesRequired actionEvidence
Named verification thresholdyesRun final plan completion check in closure passClosure pass audited all scheduled passes, raised final weighted score to 0.92, and runs node .agents/rules/autogoal/scripts/check-complete.mjs docs/plans/2026-05-25-slate-v2-pretext-pagination-virtualization-feedback.md as the final checker.
Slate v2 source/runtime/browser/API claimyesRecord live .tmp/slate-v2 source/commandsCurrent-state claims are source-confirmed in the verification workspace gate; behavior/browser/API implementation proofs are recorded as future execution gates, and this planning lane makes no implementation or issue-fix claim.
Issue ledger or PR reference changedyesSync manual rows; inspect PR reference for claim-count driftdocs/slate-issues/gitcrawl-v2-sync-ledger.md, docs/slate-v2/ledgers/issue-coverage-matrix.md, docs/slate-v2/ledgers/fork-issue-dossier.md, and docs/slate-v2/references/pr-description.md synced to final API/proof wording; fixed/improved claim counts unchanged
Autoreview for implementation changesnoN/A planning-onlyno .tmp/slate-v2 implementation patch
Final user-review handoffyesEmit final handoff or keep next passFinal user-review handoff recorded below; final response should summarize it.
Goal plan completeyesRun check-completenode .agents/rules/autogoal/scripts/check-complete.mjs docs/plans/2026-05-25-slate-v2-pretext-pagination-virtualization-feedback.md passed in closure.

Phase / pass table:

PhaseStatusEvidenceNext
Current-state read and initial scorecompletethis plan; live source/research rows belowrelated issue discovery
Related issue discoverycompletelive open ledger, v2 sync ledger, issue coverage matrix, fork dossier, PR reference, benchmark map, and frozen ledger read for related surfaceissue-ledger pass
Issue-ledger passcompletegitcrawl-v2 sync ledger, issue coverage matrix, and fork dossier updated; PR reference no-change decision recordedintent/boundary pass
Intent/boundary and decision briefcompleteowner boundaries, non-goals, decision rules, option matrix, chosen architecture, and consequences recordedresearch refresh
Research, ecosystem strategy, live-source refreshcompletecompiled research page added; Pretext local source, Pretext research log, current Slate v2 source, Premirror source, TanStack source page, Cyrus article, local/official Tiptap Pages docs, and Medium access status recordedpressure passes
Performance/DX/migration/regression/simplicity pressure passescompletepressure matrix recorded; public pageVirtualization cut for now; domStrategy remains the public degraded-DOM switch; authoritative breaks split into read/write roles; proof gates sharpenedobjection ledger
Slate maintainer objection ledgercompletemaintainer persona objections recorded with keep/cut/revise decisions; API ownership narrowed; read/write page-break authority, provider protocol boundary, missing-DOM caveat, and example split hardenedhigh-risk pass
High-risk deliberate modecompletefailure-mode table expanded; source/test evidence recorded for page mount planning, page-break authority, table/media split protocol, DOMCoverage, clipboard, IME, browser-find, and command gates; adjacent proof vs missing final proof separatedecosystem maintainer pass
Ecosystem maintainer passcompletePretext/Premirror/TanStack/Tiptap/DOMCoverage/GitHub large-surface keep-reject decisions rechecked against the high-risk queue; no strategy reversal, but Tiptap CSS pagination is a harder rejectrevision pass
Revision passcompletefinal public API target, internal runtime target, proof queue, and issue-sync impact revised from ecosystem decisions; no new fixed/improved issue claims addedissue sync accounting
Issue sync accountingcompletev2 sync ledger, issue coverage matrix, fork dossier, and PR reference updated/verified against final API target; no new fixed/improved issue claimsclosure score and final gates
Closure score and final gatescompleteClosure audit verified the score threshold, all scheduled pass rows, issue/reference sync, planning-only workspace gate, TDD/browser N/A reasons, final handoff, reboot status, open risks, and no remaining planning pass.final handoff

Scorecard:

DimensionWeightScoreEvidence
React 19.2 runtime performance0.200.90final plan isolates the runtime change to internal page/spread mounting behind virtualized domStrategy, keeps TanStack internal, and makes mounted page count/edit latency proof a release gate instead of a public API bet
Slate-close unopinionated DX0.200.93final plan leaves users with useSlateLayout, PagedEditable, pageView, domStrategy, and optional pageBreaks; no public TanStack, Pretext cache, pageVirtualization, or TableKit mechanism leaks
Plate and slate-yjs migration backbone0.150.92final plan keeps pageBreaks opt-in and state-field-shaped with read/write authority, profile/hash invalidation, writer identity, history-skip expectations, and export-reader proof before any collab/export claim
Regression-proof testing strategy0.200.93final plan maps every risky claim to explicit execution proof: page mount count, selected/promoted/composing retention, stale breaks, table/media/BFC/content rects, DOMCoverage copy/find/a11y caveats, canonical DX, and issue non-claim gates
Research evidence completeness0.150.94final plan has local Pretext source/research, external Pretext article, local/official Tiptap docs, Premirror source, TanStack/GitHub research, live Slate v2 source reads, issue ledgers, PR reference sync, maintainer objections, and ecosystem pass
shadcn-style composability and minimalism0.100.93final plan keeps one layout hook, one paged component, one pageView object, one domStrategy switch, metadata-only profiles, and provider/split extension points instead of product UI/API expansion

Current weighted score after closure score and final gates: 0.92.

Source-backed architecture north star:

  • target shape: slate-layout owns derived layout snapshots, page fragments, page-break snapshots, and optional measurement profiles; slate-react owns DOM materialization, page/block mount plans, selection, clipboard, IME, a11y, and browser proof.
  • source evidence: .tmp/slate-v2/packages/slate-layout/src/index.ts:236 defines snapshots with blocks/fragments/pages; :277 defines engine boundary; :1349 starts the Pretext page engine; :1698 paginates measured blocks into fragments/pages.
  • rejected drift: do not make slate core depend on Pretext, do not make virtualized mode default, and do not persist local page breaks by default.
  • migration posture: layout settings can be shared/persisted document metadata; page-break snapshots are opt-in shared metadata with a profile hash and invalidation rules.

Public API target:

SurfaceProposed shapeUser-facing DXCompatibility / migrationEvidenceVerdict
continuous layout<Editable layout={layout} domStrategy="auto" />one optional layout object, no Pretext imports in Reactcompatible with no-layout editorscurrent EditableProps already accepts layout; current EditableLayout is narrowkeep/revise protocol
paged layout<PagedEditable layout={layout} domStrategy="staged" pageView={{ mode: 'single' | 'spread', gap: 24 }} />page options grouped as page view, not scattered propsreplace current pageLayoutMode/pageGap before betacurrent PagedEditableProps exposes pageLayoutMode and pageGaprevise
page virtualizationinternal behavior when PagedEditable receives virtualized domStrategy and page count crosses thresholdpaged docs virtualize pages/spreads first without another public mode knobkeep TanStack internal; no public item/range/page-virtualization APIcurrent PagedEditable feeds block items and renders all pagesfinal target
block virtualization<Editable domStrategy={{ type: 'virtualized', threshold, overscan, estimatedBlockSize }} />explicit stress/pathological mode for continuous docsunchanged from current public object-only posturecurrent DOMStrategyOptions object-only virtualized modekeep
measurement profilesnapshot metadata, not a required call-site propstrict users see why a snapshot is accepted or stale without configuring a font enginegenerated from engine/browser/font/typography/page settings; apps may add a stable font-set id laterPretext source shows canvas/profile sensitivityadd metadata
authoritative breaksoptional pageBreaks source supplied to useSlateLayoutstrict users can share accepted breaks for collab/exportdefault remains local derived layout and never writes shared breaksstate fields support shared/persisted/history-skipped metadataadd

Authoritative page-break snapshot sketch:

tsx
const pageBreaks = defineStateField<SlatePageBreakSnapshot>({
  key: 'layout.pageBreaks',
  collab: 'shared',
  history: 'skip',
  initial: () => null,
  persist: true,
})

const layout = useSlateLayout(editor, {
  page: pageSettings,
  pageBreaks: {
    mode: 'write',
    source: pageBreaks,
  },
  typography,
})

Rules:

  • default mode is local derived layout, no shared page-break writes.
  • authoritative mode stores page/fragment break decisions, not rendered DOM.
  • mode: 'read' consumes accepted breaks for strict view/export; mode: 'write' is reserved for the chosen client/server authority. Do not make every peer a writer.
  • snapshots include engine id, measurement profile, font set id, typography hash, page settings hash, document version/range, and invalidation reason.
  • peers may recompute locally for speed, but strict rendering/export reads the accepted snapshot when the profile matches.
  • if profile/hash mismatches, the snapshot is stale and must not be silently used.
  • measurementProfile is produced by the layout engine and stored in snapshots; users should not have to configure a browser/font engine to get ordinary pagination.
  • write mode requires explicit writer identity and must use history-skipped derived updates. Read mode is the default strict-consumer shape.

Internal runtime target:

LayerCurrent ownerTarget mechanismAvoidsEvidenceVerdict
text measurementPretext via slate-layoutprepare() cached widths, layout() hot arithmeticDOM reflow measurement../pretext/src/layout.ts:668, :696keep with caveat
measurement profileabsent/publicly implicitopaque snapshot metadata: engine, browser/canvas/headless, font set, correction policyfake deterministic export claims and user-facing font-engine knobs../pretext/src/measurement.ts:36 requires canvas; :74 computes profile knobsadd metadata
page compositionslate-layoutfragments/pages plus split policypage nodes in editor value.tmp/slate-v2/packages/slate-layout/src/index.ts:1698keep/revise
page virtualizationnone yetvirtualizer item = page/spread rect with mounted page ranges and retained active pagesblock-item churn in paged mode.tmp/slate-v2/packages/slate-layout/src/react.tsx:185 maps block items; :219 maps all pagesadd
continuous virtualizationslate-reacttop-level/block/fragment items from layoutguessed heights as primary modeluse-virtualized-root-plan.ts:212 consumes top-level idskeep
strict collab/exportabsentoptional shared page-break snapshotdrift across OS/browser clientsPretext research + external articleadd
provider split protocolgeneric box/split vocabulary existsprovider boxes for table/media/BFC-like content with intrinsic sizes and split rulesproduct-specific raw Slate TableKit.tmp/slate-v2/packages/slate-layout/src/index.ts:105 and :113 define box kinds and split policiesadd

Hook / component / render DX target:

SurfaceCall-site shapeComposition rulePerformance ruleEvidenceVerdict
useSlateLayoutuseSlateLayout(editor, { page?, typography?, pageBreaks? })one layout source for continuous and paged consumersmemoized derived store; dirty range latercurrent hook existskeep/revise
PagedEditablelayout, domStrategy, pageViewpage UI wraps editable overlaypage/spread repeated unit when virtualizedcurrent component renders all page surfacesrevise
EditableLayout protocolcurrent getVirtualizedTopLevelItems plus internal page/spread mount itemscontinuous docs can keep top-level items; paged docs must not force pages through the block APIpage/spread item generation happens in PagedEditable, not user codecurrent protocol returns top-level items onlyrevise
canonical examplepagination-basic style route: useSlateLayout + PagedEditable onlyusers see the actual API before the proof harnessno debug/table/stress controls in first teaching surfacecurrent example is a heavy proof surfaceadd

Revision pass: Pass verdict:

  • The final public shape should be boring: useSlateLayout, PagedEditable, pageView, domStrategy, optional pageBreaks. Anything else is machinery.
  • pageVirtualization is cut as public API. In paged mode, virtualized domStrategy means "I accept incomplete DOM"; PagedEditable decides the page/spread mount plan internally.
  • measurementProfile is not a call-site feature. It is snapshot metadata used for stale detection and authoritative break validation.
  • The EditableLayout bridge must grow internally, but users should not write virtualizer item adapters. PagedEditable owns page/spread items.
  • The Tiptap lesson lands in tests and provider protocols, not in public pagination props.

Revision API decisions:

DecisionFinal callWhyProof owner
pageViewreplace pageLayoutMode and pageGap before betaone grouped display object is easier to teach and avoids prop sprawldocs/example smoke
pageVirtualizationno public propduplicates domStrategy and leaks mount policyAPI review and type tests
paged virtualized unitinternal page/spread itemspage surfaces are the repeated visual unitpackage page-mount contract plus browser mounted-page count
measurementProfilesnapshot metadatastrict fidelity needs stale detection, not a user-facing browser enginestale snapshot tests
pageBreaksoptional strict-fidelity source with read/write modescollaboration/export need authority without turning local breaks into document truthshared-state replay, history-skip, stale rejection, export read
split protocolgeneric provider boxes/split policiestables/images/BFCs need real split behavior without raw Slate shipping a product TableKittable/media package fixtures
missing DOMexplicit degraded mode until proofnative find/a11y parity is false for unmounted pagespaged DOMCoverage browser rows and manual a11y lane

Revision proof deltas:

SurfaceRequired revision proofBlocks closeout
API targettype/API review proves no pageVirtualization prop and pageView groups page display settingsyes
page mount planpackage test proves page/spread items, mounted page count, selected/promoted/composing retention, and no all-page surface renderyes
strict breakstests prove read mode does not write, write mode is elected/history-skipped, stale profile rejects, shared patch replay is compact, and export reads accepted breaksyes
table/media providersfixtures cover BFC-like containers, page content rect/padding, oversized image, table row split, merged cells, nested tables, and extension breakage risksyes
missing DOMbrowser rows prove copy/select-all/materialization and document native-find/a11y caveatsyes for parity claim
canonical DXa basic example route shows only useSlateLayout + PagedEditable; proof harness remains separateyes for user-review handoff

Revision issue-sync impact:

  • No new Fixes #... or Improves #... claim is justified by this revision.
  • #5944 stays related/issue-reviewed until page-boundary flicker, caret mapping, and page-break stability browser proof lands.
  • #790 stays proof-route backlog until mounted-page/block-count, edit latency, scroll, DOMCoverage, and native behavior proof lands.
  • #2793/#2572 stay release guards for missing-DOM/native-equivalence language.
  • The next pass only needs to verify and, if needed, align ledger wording with the final API target: no public pageVirtualization, pageView, metadata-only measurementProfile, opt-in pageBreaks, and provider/split protocol.

Intent / boundary record:

  • intent: accept the feedback as architecture pressure, not as a footnote. The long-term call is Pretext-backed derived layout, page/spread virtualization for paged mode, and an opt-in authority channel for users who need export/collab page-break fidelity.
  • outcome: the plan should let implementation proceed without reopening the core question of "block virtualizer plus page chrome" vs "page mount plan". Paged mode uses a page/spread mount plan; continuous mode may still use block/fragment virtualization.
  • in-scope:
    • slate-layout measurement profiles, page snapshots, fragments, split policy protocols, and optional authoritative page-break snapshot shape.
    • slate-react DOM materialization, retained active pages, IME/selection/a11y behavior, clipboard behavior, and page-level virtualizer integration.
    • public API naming for layout, pageView, and domStrategy; page virtualization is internal behavior behind virtualized domStrategy.
    • proof routes for line-break drift, table/image splits, padding/content rects, mixed font metrics, collab/export authority, and missing-DOM accessibility.
    • issue non-claim posture for pagination, virtualization, a11y, and large-doc benchmarks.
  • non-goals:
    • implementation in this planning pass.
    • claiming same page breaks across OS/browser/server while Pretext measurement still depends on canvas.
    • persisting local page breaks as default document truth.
    • exposing TanStack Virtual, Pretext internals, or measurement-cache plumbing as the main Slate API.
    • making virtualization default for ordinary editors.
    • putting product export policy, page toolbar UI, or Plate-specific document editor behavior into raw Slate.
    • solving all table layout inside raw Slate; the target is a provider/split protocol that table/media plugins can satisfy.
  • owner boundaries:
OwnerOwnsMust not own
slate coredocument value, paths, operations, selections, history semantics, state-field substratePretext dependency, page DOM, virtualizer ranges, product export rules
slate-layoutderived layout snapshots, pages/fragments, measurement profiles, split-policy protocols, optional page-break snapshot schemabrowser DOM mounting, React selection repair, persisted default page truth
slate-reactDOM materialization, page/block mount plans, active editing retention, clipboard/IME/selection/a11y prooflayout measurement authority, collab persistence policy, app-specific page UI
collaboration/export layerauthoritative writer choice, persistence, export replay, server/client trust policydefault local editing semantics for every editor
Plate/appsproduct page UI, document-editor presets, export product decisionsraw Slate core contracts
  • decision boundaries:
    • If a datum is required to edit the document, it belongs to core.
    • If it can be recomputed from document + layout settings, it is derived layout, not document value.
    • If two clients can validly compute different breaks, strict consumers need an explicit authority; Slate must not pretend local layout is universal.
    • If a page is the repeated visual unit, virtualization uses pages/spreads first. Blocks remain the continuous-editor unit.
    • If an API requires users to understand TanStack or Pretext internals, it is the wrong public DX.
  • unresolved user-decision points: none before issue sync. Revision pass decides pageVirtualization is internal behavior behind virtualized domStrategy. Exact provider type names can be finalized during implementation, but the architecture is fixed: generic box providers and split policies, no raw Slate product TableKit.

Decision brief:

  • principles:
    1. Layout is derived, versioned, and profile-aware; document value is not page geometry.
    2. Page mode's repeated unit is page/spread, not block.
    3. Strict fidelity needs an authority; local recompute is not enough until measurement is truly headless/deterministic.
    4. Missing DOM stays explicit and measured.
    5. Public DX should say "layout and page view", not "virtualizer cache and Pretext measurement internals."
  • top drivers:
    • Pretext's hot path is excellent, but prepare() is not headless today.
    • Current paged mode already has pages/fragments, so block virtualization is the wrong abstraction for pagination.
    • Tiptap's failures are mostly split-policy, box-model, and nested structure failures, not just "needs faster measurement."
    • Live collaboration and export cannot share hand-wavy page breaks. They need either same-profile local layout or a recorded authority.
  • viable options:
OptionDecisionWhy
Keep current block virtualization under PagedEditablerejectit virtualizes the wrong unit and still leaves page surfaces as a separate rendering problem
Make page breaks document truth by defaultrejectit turns browser/font drift into user data and creates noisy collaboration/history state
Wait for a future fully headless Pretext before designing the APIrejectit blocks useful same-browser pagination and hides the authority extension point users will need anyway
Server-only pagination authorityreject as default, keep as opt-in writertoo heavy for normal editing and bad offline DX, but valid for strict export/compliance flows
Page-level virtualization plus optional authoritative snapshotschoosematches the visual unit, preserves Slate's document model, admits current measurement limits, and leaves a clean strict-fidelity path
  • chosen architecture:
    • useSlateLayout creates a derived, profile-aware layout snapshot.
    • PagedEditable consumes page/spread mount items, not block items, when pagination is enabled.
    • active/selected/composing pages are retained even when outside the viewport.
    • pageBreaks is an optional shared state field with profile/hash/version metadata; default mode never writes page breaks.
    • export/collab strict mode reads accepted breaks only when the profile is valid, and falls back to recompute/stale handling when it is not.
  • consequences:
    • PagedEditable needs a page mount plan.
    • EditableLayout needs a richer protocol than top-level items.
    • Export/collab docs must distinguish local layout from authoritative layout.
    • Page virtualization proof must include selection, IME, clipboard, screen reader/missing-DOM risk, and page-boundary editing, not only scroll FPS.
    • Table/image correctness needs box-provider and split-policy tests, not just text measurement tests.
  • follow-ups:
    • issue-sync accounting and closure passes.

Issue accounting:

Issue / clusterClaim categoryExact claimWhyProof routeV2 sync ledgerPR line
#5944 stable per-line paginationrelated, issue-reviewedno fix/improves claimplan targets pagination but lacks page-boundary browser proofpagination browser rows for flicker/caret/page breakssyncedno change needed
#790 dynamic renderingrelated, proof-route backlogno fix/improves claimvirtualization target is relevant but not provenmount/edit/scroll benchmark, DOM coverage, native behavior proofsyncedno change needed
#5924 structural DOM exclusionnot claimed, unchangedno fix/improves claimpage frames/debug/table structure should route through DOM coverage and mount policy, not a public ignore-cursor APIstructural page/table frame DOM bridge proofsyncedno change needed
#2051 rerender breadthguardrailpreserve existing performance guardraillayout subscriptions must not widen invalidationrerender breadth benchmarksyncedno change needed
#5131 selection subscription rerendersnot claimed, guardrailpreserve broad-hook non-claimlayout/page consumers must not make broad editor hooks worseselector/block-slice locality benchmarksyncedno change needed
#4141 nested rerender breadthimproves-claimed, unchangedpreserve existing improves onlylayout invalidation must keep ancestor/sibling rerender breadth localnested rerender breadth benchmarksyncedno change needed
#2793 screen readersrelease guard, cluster-syncedno fix/improves claimpage/block virtualization can hide DOM from assistive techscreen-reader/virtual focus browser proof before native equivalence claimsyncedno change needed
#2572 a11yrelease guardno claimvirtualized/page-missing DOM affects screen readersa11y/native surface complete proofsyncedno change needed
#3892 custom surfacespolicy non-claimno claimraw Slate substrate helps, product surfaces remain app-owneddocs/API onlysyncedno change needed
#5945 large plaintext pasteimproves-claimed, unchangedpreserve existing improves onlylayout/virtualization plan must not promote paste performance to fix closureexisting 10,000-line benchmark plus browser artifact if ever promotedsyncedno change needed
#4056 large copy/pasteimproves-claimed, unchangedpreserve existing improves onlylarge-document paste/copy claim remains clipboard/operation-owned, not pagination-ownedpopulated copy/paste benchmark plus historical browser repro if ever promotedsyncedno change needed
#5992 huge cutimproves-claimed, unchangedpreserve existing improves onlylarge-document cut claim remains operation/benchmark-owned, not virtualizer-owned50,000-block benchmark plus maintainer acceptance if ever promotedsyncedno change needed

Related issue discovery pass:

  • sources read:
    • docs/slate-issues/gitcrawl-live-open-ledger.md
    • docs/slate-issues/gitcrawl-v2-sync-ledger.md
    • docs/slate-v2/ledgers/issue-coverage-matrix.md
    • docs/slate-v2/ledgers/fork-issue-dossier.md
    • docs/slate-v2/references/pr-description.md
    • docs/slate-issues/benchmark-candidate-map.md
    • docs/slate-issues/open-issues-ledger.md
  • live open rows:
    • #5992, #5945, #5944, #5924, #5131, #4141, #4056, #3892, #2793, #2572, #2051, and #790 are all present in the current generated live ledger.
  • current classification:
    • #5944 is the only direct pagination issue. Keep it related and issue-reviewed until page-boundary flicker/caret mapping has current browser proof.
    • #790 is the direct dynamic rendering / virtualization issue. Keep it related proof-route backlog until mount/edit/scroll benchmark, DOM coverage, and browser native-behavior proof exist.
    • #5924 is adjacent because page/table/debug structural DOM exists around the editor. Keep it not claimed; DOM coverage and mount policy are the answer, not a new ignore-cursor public API from this plan.
    • #4141, #5131, and #2051 are invalidation/subscription guardrails. Layout snapshots and page virtualization must not widen rerender breadth.
    • #2793 is the stronger screen-reader guard for missing-DOM modes. #2572 stays accessibility/docs pressure, but #2793 is the row that blocks native equivalence claims for virtualized pages.
    • #5945, #4056, and #5992 stay owned by large-document operation/clipboard benchmarks. This pagination plan must not promote their existing Improves rows.
  • discovery decision:
    • no new Fixes #... claims.
    • no new Improves #... claims.
    • manual sync ledgers now record the #2793 addition and this plan's no-claim posture.

Issue-ledger sync status:

  • ClawSweeper related-issue pass: complete
  • generated live gitcrawl rows read: complete for the current related surface
  • manual v2 sync ledger update: complete and revised to final API/proof wording in docs/slate-issues/gitcrawl-v2-sync-ledger.md
  • fork issue dossier update: complete and revised to final API/proof wording in docs/slate-v2/ledgers/fork-issue-dossier.md
  • issue coverage matrix update: complete and revised to final API/proof wording in docs/slate-v2/ledgers/issue-coverage-matrix.md
  • PR description sync: complete in docs/slate-v2/references/pr-description.md; planning target wording added, fixed/improved claim counts unchanged

Issue sync accounting pass: Pass verdict:

  • The final API/proof revision changes planning wording, not issue claim status.
  • docs/slate-issues/gitcrawl-v2-sync-ledger.md, docs/slate-v2/ledgers/issue-coverage-matrix.md, docs/slate-v2/ledgers/fork-issue-dossier.md, and docs/slate-v2/references/pr-description.md now agree on the final target: no public pageVirtualization, pageView for page display settings, metadata-only measurementProfile, opt-in pageBreaks, internal page/spread virtualization behind virtualized domStrategy, and generic provider/split protocols for table/media/BFC pagination.
  • Claim count remains zero for this plan: no new Fixes #... and no new Improves #....

Issue sync accounting decisions:

SurfaceFinal accountingReason
#5944related, issue-reviewed, no claimneeds page-boundary flicker, caret mapping, and page-break stability browser proof
#790related proof-route backlog, no claimneeds mounted-count, mount/edit/scroll, DOMCoverage, and native behavior proof
#5924not claimedanswer is DOMCoverage, mount policy, and provider/split protocols, not public ignore-cursor API
#2793/#2572release guardsmissing-DOM page virtualization cannot claim native screen-reader/a11y parity without proof or explicit degraded docs
#4141/#5131/#2051guardrails unchangedlayout/page consumers must not widen rerender breadth or subscriptions
#5945/#4056/#5992existing Improves unchangedlarge-document operation/clipboard claims stay owned by existing benchmark rows
PR referenceplanning target synced, no claim-count changepublic target wording belongs in PR narrative, but no issue status changed

Ecosystem strategy synthesis:

SystemSourceMechanismAvoidsStealRejectSlate targetVerdict
Pretextlocal ../pretext, Cyrus article, compiled source pageprepare once, hot layout arithmetic, profile-sensitive measurementDOM layout thrashlayout/profile split and serialized prepared-data directionclaiming full headless determinism todayprofile-aware layout engine plus optional authoritative breaksagree/revise
Premirrorlocal ../premirrorsnapshot -> measure -> compose -> renderpage nodes as document truthderived layout pipeline and page chrome outside contentProseMirror position modelSlate path/root/run/fragment mappingagree
TanStack Virtualcurrent Slate + research noteheadless range engineowning editor semanticspage/block range engine internally; latest upstream iOS/perf work is useful implementation fuelpublic TanStack APIinternal page/block adapteragree
Tiptap pagination failureslocal ../tiptap-docs, official Tiptap Pages docs, user-provided Medium summaryexposes CSS-float/BFC limits, oversized non-splittable blocks, table-kit specialization, figures, and padding/content-height trapsnaive top-level node pagingfailure taxonomy, split-policy tests, box-model testsCSS-float page layout, product-specific raw Slate TableKit, "move whole block" fallbackline/row/avoid/intrinsic split policiesagree as negative evidence
DOMCoveragecurrent Slate v2 source and Playwright rowsrecords intentionally hidden ranges with copy/find/selection policypretending missing DOM has native paritypolicy boundary and materialization contractmaking virtualization a layout-only concernSlate-owned browser-policy layer for page virtualized modeagree with caveat
GitHub large-surface virtualizationcompiled TanStack/GitHub researchreduce repeated-unit cost before virtualizing p95+ surfacespremature virtualization as architecturesequence: simplify rows, delegate events, O(1) maps, then virtualize taildefault incomplete DOM without editor policyperformance discipline for page/surface budgetagree

Ecosystem maintainer pass: Pass verdict:

  • No ecosystem source changes the target architecture. The right move is still Pretext for layout, Premirror's derived-fragment shape, TanStack as an internal range engine, DOMCoverage as Slate-owned browser policy, and Tiptap as a warning label.
  • The user feedback is correct and worth making sharper: Tiptap's public Pages docs say the CSS-float model breaks on BFC-style blocks and proposes caps or manual node splitting. That is exactly the failure mode Slate must avoid.
  • The pushback is also important: this is not an argument against pagination or page-level virtualization. It is an argument against making CSS page gaps, product TableKits, or semantic manual splits the substrate.

Ecosystem final keep/reject decisions:

SourceKeepRejectPlan consequence
Pretextprepare()/layout() split, profile-sensitive measurement, hot arithmetic layoutsame-breaks claim across browser/server/client profiles todaymeasurementProfile and optional authoritative page-break snapshots stay required
Premirrordocument state separate from composed pages/fragments; page chrome outside editable contentPM position model and page nodes as document truthSlate uses roots/paths/runs/fragments and keeps pagination derived
TanStack Virtualvisible range, measured sizes, retained index/range extension, future iOS/perf improvementspublic virtualizer API or exposing TanStack knobsuse an internal page/spread adapter in paged mode; upgrade is an execution task, not public API design
Tiptap Pageslimitation taxonomy for BFCs, tables, figures, styled containers, padding/content rects, export, and extension breakageCSS floats, --page-max-height as a correctness fix, manual semantic splitting, product-specific raw Slate TableKitprovider/split-policy protocol plus table/media/page-boundary tests
DOMCoverageexplicit missing-DOM copy/find/selection policy and materialization hooksnative browser-find/a11y parity claims before paged proofpage-virtualized mode must document degraded behavior until browser proof lands
GitHub large-surface virtualizationoptimize repeated units first, virtualize the tail, measure p95+ pressurevirtualize before reducing row/page surface costpage/surface budget and mounted-page-count proof become release gates

Tiptap-specific correction:

  • The official limitations page and local source now make the CSS trick look worse, not better. If a block cannot split because it creates a BFC, max-height is not real pagination; it is a content authoring constraint wearing a layout hat.
  • Manual node splitting is also not acceptable as a raw Slate answer because it mutates document semantics to satisfy a view problem.
  • PagesTableKit is useful proof that tables need special split logic. It is not a model for raw Slate; raw Slate should expose a generic box provider and split policy, while table/media packages provide table-specific behavior.

Ecosystem pass deltas:

  • Strengthened the Tiptap row from "negative evidence" to "hard reject the CSS-float mechanism; steal only its failure taxonomy."
  • Added DOMCoverage as an explicit ecosystem strategy row so virtualization does not get treated as layout-only.
  • Added the GitHub large-surface sequence as performance discipline: reduce repeated-unit cost first, then virtualize the large tail.
  • Kept the TanStack upgrade note as implementation fuel only. The public API still must not expose TanStack's option model.
  • Left issue/PR fixed-claim counts unchanged; this pass changes planning confidence, not fixed issue scope.

Research, ecosystem strategy, and live-source refresh pass:

  • compiled research artifact:
    • docs/research/sources/editor-architecture/pretext-pagination-page-virtualization.md added and indexed from the editor-architecture README and research index.
  • local Pretext source refresh:
    • ../pretext/src/measurement.ts:36 still creates an OffscreenCanvas or DOM canvas context.
    • ../pretext/src/measurement.ts:49 still throws without canvas access.
    • ../pretext/src/measurement.ts:61 still calls ctx.measureText(seg).width.
    • ../pretext/src/measurement.ts:74 now makes browser engine profile knobs explicit, which strengthens the plan's measurement-profile requirement.
    • ../pretext/src/layout.ts:668 keeps prepare() as the segment measurement and cache phase.
    • ../pretext/src/layout.ts:696 keeps layout() arithmetic-only on cached widths.
  • local Pretext research refresh:
    • ../pretext/RESEARCH.md:55 records system-ui canvas/DOM font-resolution mismatch.
    • ../pretext/RESEARCH.md:131 records emoji canvas/DOM width discrepancy.
    • ../pretext/RESEARCH.md:142 treats the HarfBuzz headless probe as useful research, not runtime direction.
    • ../pretext/RESEARCH.md:156 keeps final parity work in prepare() / browser diagnostics / small browser-specific tolerance, while layout() stays arithmetic-only.
  • external Pretext refresh:
    • Cyrus Radfar article fetched 2026-05-25: https://cyrusradfar.com/thoughts/pretext-beyond-the-browser
    • the article supports the same split: portable hot layout is plausible, but initial measurement still depends on canvas.measureText() and matching browser font metrics outside the browser is hard work.
  • current Slate v2 source refresh:
    • .tmp/slate-v2/packages/slate-layout/src/index.ts:105 has box kinds for block, code line, image, table, table cell, and thematic break.
    • .tmp/slate-v2/packages/slate-layout/src/index.ts:113 already names avoid, line, page, and row split policy vocabulary.
    • .tmp/slate-v2/packages/slate-layout/src/index.ts:236 has blocks, fragments, page, pages, root, settings, and version in snapshots.
    • .tmp/slate-v2/packages/slate-layout/src/index.ts:1349 still uses the Pretext page layout engine.
    • .tmp/slate-v2/packages/slate-layout/src/react.tsx:185 still maps projected blocks into top-level virtualized layout items.
    • .tmp/slate-v2/packages/slate-layout/src/react.tsx:219 still renders all page surfaces.
    • .tmp/slate-v2/packages/slate-react/src/components/editable-text-blocks.tsx:548 still exposes only getVirtualizedTopLevelItems.
    • .tmp/slate-v2/packages/slate-react/src/dom-strategy/use-virtualized-root-plan.ts:95 retains selected/promoted top-level indexes through the range extractor.
  • Premirror refresh:
    • ../premirror/README.md:14 describes measured snapshot -> deterministic layout with pages, frames, line boxes, and placed runs.
    • ../premirror/docs/milestone-1-implementation-plan.md:20 keeps a single contenteditable root and renders visual pagination from composed output.
    • ../premirror/docs/milestone-1-implementation-plan.md:22 represents page splits as layout fragments, not ProseMirror node splits.
  • Tiptap refresh:
    • the Medium article URL currently returns a Cloudflare challenge to curl, so the plan treats the user's summary as review context, not independently refreshed text.
    • local ../tiptap-docs/src/content/pages/core-concepts/limitations.mdx:17 says Tiptap Pages uses CSS floats around page gaps; BFC blocks such as tables, figures, and styled containers cannot split across pages and can break pagination when too large.
    • local ../tiptap-docs/src/content/pages/core-concepts/limitations.mdx:18 uses max-height / --page-max-height as a mitigation for non-splittable blocks.
    • local ../tiptap-docs/src/content/pages/core-concepts/limitations.mdx:19 suggests manual node splitting and warns it changes document structure and may affect semantics.
    • local ../tiptap-docs/src/content/pages/guides/table-with-pages.mdx:16 says table pagination needs @tiptap-pro/extension-pages-tablekit because table behavior and layout were heavily modified to split across pages.
    • local ../tiptap-docs/src/content/pages/guides/table-with-pages.mdx:52 warns the open-source TableKit is not compatible with Pages layout; :63 warns extension authors can break the splitting logic.
    • official Tiptap Pages limitations fetched 2026-05-25: https://tiptap.dev/docs/pages/core-concepts/limitations
    • official Tiptap Pages table guide fetched 2026-05-25: https://tiptap.dev/docs/pages/guides/table-with-pages
    • current Tiptap docs validate the core pressure but not the mechanism: steal the failure taxonomy; reject the CSS-float/page-gap pagination model.
  • pass decision:
    • keep Pretext.
    • add profile-aware layout vocabulary.
    • keep page-level virtualization as the paged-mode repeated unit.
    • do not expose TanStack, Pretext measurement caches, or Tiptap-style product table kits as raw Slate public API.
    • do not copy Tiptap's CSS-float pagination trick.
    • require provider/split-policy tests for table/media and page content rects.

Performance/DX/migration/regression/simplicity pressure pass:

  • pressure verdict:
    • the architecture survives, but the public API needed a cut: no public pageVirtualization prop before beta.
    • domStrategy is already the public "I accept incomplete DOM" switch. PagedEditable should internally choose a page/spread mount plan when that strategy is virtualized.
    • authoritative breaks need read/write authority roles; a blanket read-write peer mode is too noisy for collaboration.
    • Tiptap Pages is useful as warning material, not as a model: the local docs confirm the CSS-float/BFC approach forces max-height hacks, manual semantic splits, and specialized table layout.
  • performance pressure:
Claim under pressureFailure modePressure resultPlan delta
Paged mode should virtualize pages/spreadsPage surfaces still all render if only blocks are virtualizedconfirmed by .tmp/slate-v2/packages/slate-layout/src/react.tsx:185 and :219add page/spread mount plan inside PagedEditable
Keep TanStack internalPublic TanStack knobs become editor contractexisting research keeps TanStack limited to visible index range, size, overscan, retained indexeskeep domStrategy public; no virtualizer item API
Retain active editing targetsPage virtualizer unmounts caret/IME pagecurrent top-level virtualizer retains selected/promoted indexes at use-virtualized-root-plan.ts:95; page plan must add selected/promoted/composing page retentionproof gate for selection, IME, and composition
Profile-aware layout is enough for strict usersLocal recompute still drifts across profilesnot enough for export/collab strict moderequire optional authoritative snapshot
  • DX pressure:
SurfacePressure resultReason
pageVirtualization propcut for nowone more mode knob leaks implementation; domStrategy already says whether incomplete DOM is allowed
pageLayoutMode / pageGapreplace with pageView before betacurrent separate props in PagedEditableProps are small but not the clean long-term shape
pagination examplesplit canonical DX from proof harnesscurrent example owns tables/images/debug/projection/toolbar code; it is useful proof, not best first-read DX
pageBreaks optionkeep advanced, role-basedstrict consumers need it; ordinary editors should never see it
Tiptap-style pagination controlsrejectCSS-float page gaps, max-height, and manual node splitting are not Slate-quality primitives
  • migration pressure:
Migration axisRequired postureReason
existing no-layout editorsno API changelayout remains optional
current paged examplerename/reshape before betapageView avoids long-term scattered page props
Plate document editorPlate can wrap page UI and export policyraw Slate should expose substrate only
slate-yjs / collaborationpage settings can be shared; breaks are opt-in shared metadatadefault local page breaks would create noisy remote updates
exportreads accepted breaks only when profile/hash matchesprevents silent stale export
  • regression pressure:
RiskRequired proof before implementation closure
page-boundary editingbrowser row: type/delete/selection across page fragments
page virtualizer retentionbrowser row: scroll away while selection/composition target is retained
clipboard/select-allbrowser row: model-backed copy includes unmounted pages or degraded behavior is explicit
screen readers/browser find#2793/#2572 guard: no native-equivalence claim while offscreen pages are missing
table/mediaunit + browser rows for row split, merged cell, nested table, oversized image, and content rect
CSS-float/BFC trapsfixture rows for BFC-like boxes, figures, styled containers, and oversized non-splittable content
cross-profile driftdeterministic compare across at least two browser/font profiles, or explicit profile-gated stale result
authoritative breaksstate-field replay and stale-profile invalidation tests
  • simplicity pressure:
    • keep one public layout hook: useSlateLayout(editor, options).
    • keep one paged component: PagedEditable.
    • keep one public incomplete-DOM switch: domStrategy.
    • keep one page-view grouping: pageView.
    • keep pageBreaks advanced and optional.
    • use Tiptap Pages as a failure taxonomy only.
    • do not copy Tiptap's CSS-float/page-gap pagination model.
    • do not add a Tiptap-like TableKit to raw Slate; add provider/split protocol and let table/media packages satisfy it.
  • implementation-skill pressure:
    • performance-oracle: applied. The hot loop is not React rendering; the risk is wrong repeated unit and page-surface overmounting.
    • performance: applied. Benchmark must count mounted pages/surfaces and edit latency, not only scroll range math.
    • vercel-react-best-practices: skipped for this planning pass; no React code edited, but execution must review effect dependencies and external-store subscriptions in useSlateLayout / PagedEditable.
    • react-useeffect: skipped for this planning pass; same reason.
    • tdd: N/A for planning, but proof gates are now test-shaped.
    • shadcn: skipped; no UI implementation and raw Slate public API is the issue.
  • pass decision:
    • chosen public beta target:
tsx
const layout = useSlateLayout(editor, {
  page,
  pageBreaks: strictMode
    ? { mode: 'read', source: pageBreaks }
    : undefined,
  typography,
})

<PagedEditable
  domStrategy={{ type: 'virtualized', estimatedBlockSize: 48, overscan: 3, threshold: 80 }}
  layout={layout}
  pageView={{ gap: 24, mode: 'single' }}
/>
  • PagedEditable may internally virtualize pages/spreads first when paged and virtualized. Block virtualization remains the continuous-editor repeated unit.

Legacy regression proof matrix:

Regression classLegacy behaviorSlate v2 targetProof routeOwnerStatus
paragraph at page boundaryeditable paragraph keeps text/caret stableline-fragment split with stable caret mappingpagination Playwright page-boundary rowsslate-layout/slate-reactpending
table split / merged cellstables do not overlap footer/page paddingrow/span-aware table box providertable fixture with merged cells and page boundarylayout + table pluginpending
image/void blockintrinsic media stays in content frameavoid/intrinsic box with measured sizeimage/void pagination browser rowlayout + app resolverpending
mixed font sizesline height follows run metricsline-owned max metrics, not one block lineHeightunit + browser rich-inline rowslate-layoutpending
padding/page frameno blank spacer hackspage content rect and block box modeldebug frame browser rowslate-layoutpartial
strict export/collabscreen/export match when requiredauthoritative page-break snapshotshared state + export fixtureslate-layout/slate-yjs/apppending

Browser stress / parity strategy:

SurfaceScenarioBrowser/deviceCommand or proof routeExpected signalStatus
pagination page virtualization500+ pages, scroll middle, typeChromium first, then WebKit/Firefox.tmp/slate-v2 pagination Playwright greponly visible/retained pages mounted, typing stablepending
cross-platform driftsame doc/font/page settings on multiple profilesChromium/WebKit/Firefox, macOS/Linuxdeterministic snapshot comparedrift recorded or profile-gatedpending
authoritative breakspeer/export reads accepted breaksunit + browserstate field replay + export proofmatching page breaks when profile validpending
table/image edge casestable split, merged cell, image near boundaryChromiumpagination fixtureno overlap, no lost contentpending
missing-DOM a11ypage virtualization with offscreen pages missingscreen-reader-capable browser/manual lane#2793/#2572 release guard rowno native-equivalence claim until assistive-tech behavior is proven or documented as degradedpending

Verification workspace gate:

ClaimWorkspaceCommand / sourceResultOwner
Pretext prepare uses canvas; layout hot path is arithmetic../pretextnl -ba ../pretext/src/layout.ts; nl -ba ../pretext/src/measurement.tssource-confirmedcurrent pass
Pretext has documented font/profile drift../pretextnl -ba ../pretext/RESEARCH.mdsource-confirmedcurrent pass
Slate layout has pages/fragments/snapshots and Pretext engine.tmp/slate-v2nl -ba packages/slate-layout/src/index.tssource-confirmedcurrent pass
Current paged editable feeds block layout items and renders all page surfaces.tmp/slate-v2nl -ba packages/slate-layout/src/react.tsxsource-confirmedcurrent pass
Current virtualized plan is top-level id/index based.tmp/slate-v2nl -ba packages/slate-react/src/dom-strategy/use-virtualized-root-plan.tssource-confirmedcurrent pass
Current example keeps page settings in shared state.tmp/slate-v2nl -ba site/examples/ts/pagination.tsxsource-confirmedcurrent pass
Issue-ledger no-claim posture syncedplate-2rg -n "Pretext Pagination / Page Virtualization Feedback" docs/slate-issues/gitcrawl-v2-sync-ledger.md docs/slate-v2/ledgers/issue-coverage-matrix.md docs/slate-v2/ledgers/fork-issue-dossier.mdartifact-confirmedissue-ledger pass
PR reference did not need claim syncplate-2`rg -n "#5944#2793#790
Intent/boundary and decision brief hardenedplate-2`rg -n "owner boundariesPage-level virtualization plus optional authoritative snapshotsServer-only pagination authority
Compiled Pretext pagination research addedplate-2`rg -n "Pretext Pagination And Page VirtualizationCurrent ConclusionTiptap Pagination Lessons" docs/research/sources/editor-architecture/pretext-pagination-page-virtualization.md`
Research indexes updatedplate-2rg -n "pretext-pagination-page-virtualization" docs/research/sources/editor-architecture/README.md docs/research/index.md docs/research/log.mdartifact-confirmedresearch pass
Current Pretext headless/profile facts refreshed../pretextnl -ba ../pretext/src/measurement.ts; nl -ba ../pretext/src/layout.ts; nl -ba ../pretext/RESEARCH.mdsource-confirmedresearch pass
Current Slate v2 paged/virtualization facts refreshed.tmp/slate-v2nl -ba packages/slate-layout/src/index.ts; nl -ba packages/slate-layout/src/react.tsx; nl -ba packages/slate-react/src/components/editable-text-blocks.tsx; nl -ba packages/slate-react/src/dom-strategy/use-virtualized-root-plan.tssource-confirmedresearch pass
Current Tiptap Pages docs fetchedplate-2curl -L https://tiptap.dev/docs/pages/core-concepts/limitations; curl -L https://tiptap.dev/docs/pages/guides/table-with-pagesofficial-doc evidence extractedresearch pass
Local Tiptap Pages docs read../tiptap-docsnl -ba src/content/pages/core-concepts/limitations.mdx; nl -ba src/content/pages/guides/table-with-pages.mdxsource-confirmed negative evidence: CSS floats, BFC split limits, max-height/manual split mitigations, specialized TableKitpressure pass
Medium article current access statusplate-2curl -L https://romik-mk.medium.com/tiptap-pagination-problems-solutions-31f1a0b51e08Cloudflare challenge; user summary retained as review contextresearch pass
Pressure pass source checks.tmp/slate-v2 + plate-2nl -ba .tmp/slate-v2/site/examples/ts/pagination.tsx; nl -ba .tmp/slate-v2/packages/slate-layout/src/react.tsx; nl -ba .tmp/slate-v2/packages/slate-react/src/dom-strategy/use-virtualized-root-plan.ts; nl -ba docs/research/sources/editor-architecture/tanstack-virtual-and-github-large-surface-virtualization.mdsource-confirmed pressure points for API/DX/perf proofpressure pass
Pressure pass artifact recordedplate-2`rg -n "Performance/DX/migration/regression/simplicity pressure passpageVirtualizationchosen public beta target" docs/plans/2026-05-25-slate-v2-pretext-pagination-virtualization-feedback.md`
Maintainer objection source checks.tmp/slate-v2 + plate-2nl -ba .tmp/slate-v2/packages/slate-layout/src/index.ts; nl -ba .tmp/slate-v2/packages/slate-layout/src/react.tsx; nl -ba .tmp/slate-v2/packages/slate-react/src/dom-strategy/use-virtualized-root-plan.ts; nl -ba .tmp/slate-v2/packages/slate-react/src/components/dom-coverage-boundary.tsx; nl -ba .tmp/slate-v2/packages/slate/src/interfaces/editor.ts; nl -ba .tmp/slate-v2/packages/slate/src/core/public-state.ts; nl -ba .tmp/slate-v2/site/examples/ts/pagination.tsxsource-confirmed ownership, state-field, DOM coverage, current paged rendering, and example DX objectionsmaintainer objection pass
Maintainer objection artifact recordedplate-2`rg -n "Slate maintainer objection ledgerPass verdictDefault shared page-break writes
High-risk source and test checks.tmp/slate-v2 + plate-2`rg -n "paginationpagedpage layout
High-risk artifact recordedplate-2`rg -n "High-risk deliberate-mode pre-mortemHigh-risk proof queuewrong paged virtualization unit
Ecosystem maintainer source checks.tmp/slate-v2, plate-2, sibling repos, official docsnl -ba ../pretext/src/measurement.ts; nl -ba ../pretext/src/layout.ts; nl -ba ../pretext/RESEARCH.md; nl -ba ../premirror/README.md; nl -ba ../premirror/docs/milestone-1-implementation-plan.md; nl -ba ../tiptap-docs/src/content/pages/core-concepts/limitations.mdx; nl -ba ../tiptap-docs/src/content/pages/guides/table-with-pages.mdx; curl -L https://tiptap.dev/docs/pages/core-concepts/limitations; curl -L https://tiptap.dev/docs/pages/guides/table-with-pages; nl -ba docs/research/sources/editor-architecture/tanstack-virtual-and-github-large-surface-virtualization.md; nl -ba .tmp/slate-v2/packages/slate-react/src/components/dom-coverage-boundary.tsx; nl -ba .tmp/slate-v2/packages/slate-react/src/dom-strategy/use-virtualized-root-plan.ts; nl -ba .tmp/slate-v2/packages/slate-layout/src/react.tsx; nl -ba .tmp/slate-v2/packages/slate-layout/src/index.tssource-confirmed ecosystem keep/reject decisions; official Tiptap docs match local Pages limitation sourceecosystem maintainer pass
Ecosystem maintainer artifact recordedplate-2`rg -n "Ecosystem maintainer passEcosystem final keep/reject decisionsTiptap-specific correction
Revision source checks.tmp/slate-v2 + plate-2`rg -n "type PagedEditablePropspageLayoutModepageGap
Revision artifact recordedplate-2`rg -n "Revision pass:Revision API decisionsRevision proof deltas
Issue sync accounting source checksplate-2nl -ba docs/slate-issues/gitcrawl-v2-sync-ledger.md; nl -ba docs/slate-v2/ledgers/issue-coverage-matrix.md; nl -ba docs/slate-v2/ledgers/fork-issue-dossier.md; nl -ba docs/slate-v2/references/pr-description.md; `rg -n "pageViewmeasurementProfilepageVirtualization
Issue sync accounting artifact recordedplate-2`rg -n "Issue sync accounting passIssue sync accounting decisionsCurrent weighted score after issue sync
Closure source checksplate-2node .agents/rules/autogoal/scripts/check-complete.mjs docs/plans/2026-05-25-slate-v2-pretext-pagination-virtualization-feedback.md; `rg -n "slate_plan_lane_status:next_pass:final_handoff_status:

Final completion gates:

GateStatusEvidence
score >= 0.92 and no dimension below 0.85passFinal weighted score is 0.92; lowest dimension is 0.90.
all scheduled pass rows completepassPhase/pass table rows from current-state through closure are complete.
issue/reference sync closedpassv2 sync ledger, issue coverage matrix, fork dossier, and PR reference all name the final API/proof target; fixed/improved claim counts unchanged.
live source grounding completepassVerification workspace gate records live .tmp/slate-v2, sibling repo, local Tiptap docs, official Tiptap docs, and plan/ledger artifact reads for every current-state claim.
behavior/browser proof scoped honestlypassThe plan records behavior/browser proof as execution gates and makes no implementation, native-parity, issue-fix, or release-quality browser claim in planning mode.
TDD/browser proofpassN/A for planning-only closeout; required unit/browser rows are named before any accepted implementation or issue claim.
autoreview clean or N/ApassN/A: this pass changed planning, research, ledger, and PR-reference artifacts only; no .tmp/slate-v2 implementation changed.
final handoff emittedpassFinal user-review handoff is recorded below and will be summarized in the final response.
no runnable planning work remainspassnext_pass: none, next_action: none, and all scheduled Slate Plan passes are complete.
check-complete passespassFinal checker result recorded under Verification evidence.

Applicable implementation-skill review matrix:

LensAppliesStatusFindingsPlan delta
vercel-react-best-practiceslaterskipped planning passno React code edited; execution must review useSlateLayout/PagedEditable subscriptions and effectsexecution proof gate
performance-oracleyesappliedwrong repeated unit and page-surface overmounting are the real hot-path riskspage/spread mount plan; no public virtualizer API
performanceyesappliedbenchmark must count mounted pages/surfaces and edit latency, not only range mathbrowser/page-count proof row
tddlaterN/A planning passno implementation editedunit/browser proof rows planned
shadcnnoskippedno UI implementation; API minimalism handled in pressure passpublic pageVirtualization cut
react-useeffectlaterskipped planning passno React code edited; execution must review effect dependenciesexecution proof gate

High-risk deliberate-mode pre-mortem: Pass verdict:

  • Adjacent proof is strong enough to keep the direction, not strong enough to call the plan ready.
  • The riskiest gap is not Pretext. It is the missing page/spread mount-plan contract inside PagedEditable: current code still feeds block layout items to Editable and renders every page surface.
  • The second riskiest gap is authoritative break authority. The state-field substrate exists, but the page-break snapshot API, writer election, stale profile invalidation, and history/collab semantics do not.
  • The third riskiest gap is truth-in-advertising for missing DOM. DOMCoverage has model-backed clipboard/find policies and materialization hooks, but paged virtualized mode still needs its own browser rows before any native-parity claim.
RiskTriggerFailure modeCurrent evidenceMitigation / targetRequired proofClosure blocker
fake deterministic paginationdocs/API imply server/client equalitycollab peers/export disagree by font/browser/profilePretext still depends on canvas/profile facts; createSlateLayout stays usable without browser canvas only through the generic non-engine path in .tmp/slate-v2/packages/slate-layout/test/page-layout-contract.test.ts:289expose profile-aware layout; no same-breaks claim by default; strict mode reads authoritative snapshotscross-profile snapshot compare plus stale-profile rejection testsyes: final docs/API cannot imply deterministic cross-client page breaks
wrong paged virtualization unitvirtualized paged mode keeps block repeated unitall page surfaces render, scroll math and page chrome stay O(page count)PagedEditable maps projection blocks into getVirtualizedTopLevelItems at .tmp/slate-v2/packages/slate-layout/src/react.tsx:185 and renders pages at :219; virtualizer accepts top-level runtime ids at .tmp/slate-v2/packages/slate-react/src/dom-strategy/use-virtualized-root-plan.ts:212internal page/spread mount plan behind virtualized domStrategy; no public pageVirtualizationpackage test for page mount items; Playwright row with 500+ pages proving visible/retained pages onlyyes
active editing page unmountedselection or IME target is outside visible page rangelost caret, swallowed composition, or wrong insertion pagevirtualized root plan retains selected/promoted top-level indexes at .tmp/slate-v2/packages/slate-react/src/dom-strategy/use-virtualized-root-plan.ts:95; partial-DOM composition guard exists in .tmp/slate-v2/packages/slate-react/test/dom-strategy-and-scroll.tsx:1654retain visible, selected, promoted, and composing pages; no promotion during compositionbrowser row: start composition, scroll page away, commit text; selection and inserted text stay correctyes
authoritative break state churnmany peers write derived page-break snapshotsnoisy collab updates, undo pollution, stale exportsstate fields support collab/history policy at .tmp/slate-v2/packages/slate/src/interfaces/editor.ts:105; history-skip state fields are proven at .tmp/slate-v2/packages/slate-history/test/document-state-history-contract.ts:123; compact shared patches are proven at :243read-mostly by default; one elected writer; profile hash; dirty range; debounce; history-skip derived writespackage tests for read/write modes, stale profile, writer id, history-skip, shared patch replayyes
stale authoritative breaks accepteddocument/font/page settings change after snapshotexport/collab replays wrong page breaksno current pageBreaks API exists; plan only sketches itsnapshot key includes doc version, root, page settings, typography profile, engine id/version, writer idstale rejection tests and export-reader fixtureyes
table/media split liestable/image/BFC-like blocks treated as text blocksoverlap footer, lose content, or corrupt semanticsbox kinds and split policies exist at .tmp/slate-v2/packages/slate-layout/src/index.ts:105; structured-box extraction tests cover image/table/table-cell at .tmp/slate-v2/packages/slate-layout/test/page-layout-contract.test.ts:652; avoid-split movement is tested at :808provider protocol for table/media/intrinsic boxes; no raw Slate product TableKitpackage fixtures for merged cells, nested table, row span, oversized image, and BFC-like container; browser page-boundary rowsyes
missing-DOM native parity lievirtualized pages are missing from DOMbrowser find/a11y/copy claims overpromiseDOMCoverage defaults findPolicy to not-native-until-mounted; Playwright proves hidden content stays out of native find until materialized at .tmp/slate-v2/playwright/integration/examples/dom-coverage-boundaries.test.ts:77; model-backed hidden copy is proven at :142 and select-all at :161document degraded mode; keep model-backed copy; materialize on selection/programmatic accesspaged virtualized rows for browser find caveat, select-all/copy, and screen-reader/manual laneyes for native-parity claim, no for alpha degraded mode
canonical DX rots into proof harnessexample remains the only public teaching surfaceusers copy debug/table/projection code as APIcurrent example renders PagedEditable only inside a heavy proof surface at .tmp/slate-v2/site/examples/ts/pagination.tsx:796split pagination-basic/canonical from stress/proof harnessdocs/example review plus browser smoke on canonical routeyes for user-review-ready docs
release gate too weakonly package tests runbrowser/runtime failures escaperoot scripts define check, check:full, integration, release-proof, persistent soak, and Playwright gates at .tmp/slate-v2/package.json:33execution closure names focused package tests, pagination Playwright rows, then bun check or bun check:full depending claim strengthrecorded command results from .tmp/slate-v2, not plate-2yes before implementation release claim

High-risk proof queue:

Queue itemOwnerMinimum proof before execution closeCurrent adjacent proofMissing final proof
page/spread mount planslate-react + slate-layout/reactunit contract for mount item generation and Playwright 500+ page scroll/type rowtop-level virtualizer and pagination browser rows existpage-level repeated unit, mounted page count, selected/promoted/composing page retention
authoritative breaksslate-layout + app/collab bridgepackage tests for read/write authority, stale profile, history-skip, shared replay, export readstate-field history/collab substrate existsactual pageBreaks API and replay semantics
provider split protocolslate-layout + table/media packagespackage fixtures for row/span/intrinsic/avoid splits plus browser table/image boundary rowsgeneric box kinds and avoid/page split tests existmerged-cell, nested-table, oversized-media, BFC-like provider fixtures
missing DOM policyslate-react + browser proofPlaywright rows for paged virtualized find/copy/select-all/IME/a11y caveatDOMCoverage examples prove hidden find/copy/IME piecespage-virtualized route proof and manual screen-reader lane
canonical DX splitdocs/examplesbasic example shows useSlateLayout + PagedEditable; proof harness keeps debug UIcurrent proof harness exercises rich casessimple route/docs call site and smoke test

Slate maintainer objection ledger: Pass verdict:

  • The architecture survives maintainer pressure, but only if the API stays brutally small and the ownership boundaries stay explicit.
  • The biggest correction is page-break authority: strict snapshots are read-only for most peers; only a chosen writer writes derived break metadata, and those writes must be profile-hashed, stale-safe, and history-skipped by default.
  • The second correction is table/media scope: raw slate-layout defines a provider/split protocol, not a Tiptap-style product TableKit.
  • The third correction is runtime honesty: virtualized paged mode is a degraded DOM mode until browser find, clipboard, IME, and assistive-tech proof says otherwise.
PersonaObjectionEvidenceDecisionProof / adoption answerVerdict
raw Slate maintainerPagination and authoritative page breaks smell like document-editor product scopeslate-layout lives outside core; SlatePageLayoutSnapshot is derived data at .tmp/slate-v2/packages/slate-layout/src/index.ts:236; state fields are optional extensions at .tmp/slate-v2/packages/slate/src/core/state-field.ts:7keep layout outside slate; no core Pretext dependency; no default page-break persistencedocs must state core owns document operations, layout owns derived views, apps own document-product policykeep
raw Slate maintainerpageBreaks can become hidden document truthstate fields can be shared/history-controlled at .tmp/slate-v2/packages/slate/src/interfaces/editor.ts:105; shared/history fields without patch hooks are guarded at .tmp/slate-v2/packages/slate/src/core/public-state.ts:397keep as opt-in metadata only; default local layout never writes itstrict mode reads accepted breaks; writer mode requires profile hash, range dirtying, writer identity, and history-skip derived writesrevise
layout maintainerMeasurement profiles can balloon into a second browser enginePretext availability still checks canvas at .tmp/slate-v2/packages/slate-layout/src/index.ts:361; Pretext engine caches prepared text at :1349keep a small opaque measurementProfile, not a public font engineno same-breaks-across-server claim until Pretext has a true headless measurement contractkeep
layout maintainerTable/image providers could make raw layout opinionatedcurrent box vocabulary is generic: image, table, table-cell, and split policies at .tmp/slate-v2/packages/slate-layout/src/index.ts:105define minimal provider protocol; do not ship a raw Slate product TableKittable/media packages provide row/span/intrinsic sizing and split policy; proof harness owns pathological fixturesrevise
React/runtime maintainerPage virtualization can break selection, IME, and editing if it unmounts the active pagecurrent virtualizer retains selected/promoted top-level indexes at .tmp/slate-v2/packages/slate-react/src/dom-strategy/use-virtualized-root-plan.ts:95; PagedEditable still maps block items and renders all page surfaces at .tmp/slate-v2/packages/slate-layout/src/react.tsx:185 and :219move paged virtualized repeated unit to pages/spreads internallypage mount plan must retain visible, selected, promoted, and composing pages; browser proof must type/compose while scrolling awaykeep with proof
React/runtime maintaineruseSlateLayout depends on object identity and can relayout too oftenhook refreshes on options.typography and options.root at .tmp/slate-v2/packages/slate-layout/src/react.tsx:67; example memoizes typography at .tmp/slate-v2/site/examples/ts/pagination.tsx:386keep hook shape; docs/examples must memoize stable typography/page configexecution pass reviews effect dependencies and external-store subscription stabilitykeep
accessibility maintainerMissing DOM cannot claim native browser find or assistive-tech parityDOMCoverage defaults find policy to not-native-until-mounted at .tmp/slate-v2/packages/slate-react/src/components/dom-coverage-boundary.tsx:36; materialization handler exists for virtualized boundaries at .tmp/slate-v2/packages/slate-react/src/components/editable-text-blocks.tsx:1600keep virtualized mode explicit and degraded until proofrelease notes/docs must avoid native-equivalence claims; browser/a11y proof rows are release gateskeep with caveat
clipboard/browser maintainerOffscreen pages may corrupt copy/select-all semanticsDOMCoverage range defaults copy policy to include-model at .tmp/slate-v2/packages/slate-react/src/components/dom-coverage-boundary.tsx:36; self-boundary defaults exclude at :110keep model-backed copy policy, but prove it in paged modeproof row: select all/copy across unmounted pages, plus explicit degraded browser-find behaviorkeep with proof
collab/export maintainerMultiple clients writing authoritative breaks will churn shared state and historycurrent example shows shared persisted page settings at .tmp/slate-v2/site/examples/ts/pagination.tsx:41; collab patches filter only shared fields at .tmp/slate-v2/packages/slate/src/core/public-state.ts:540only an elected/authoritative writer writes page-break snapshotsreaders use mode: 'read'; writer uses mode: 'write', profile hash, source id, debounce, and history-skip metadatarevise
Plate adopterThe current pagination example is too much for DXexample owns DOM strategy controls, layout projection, debug frames, page UI, tables/images, and toolbar state around .tmp/slate-v2/site/examples/ts/pagination.tsx:375 and renders PagedEditable at :796split canonical example from proof harnesscanonical example shows useSlateLayout + PagedEditable; proof harness keeps debug/table/image/browser rowsrevise
ecosystem reviewerTiptap Pages ships something; rejecting its CSS trick may ignore working product evidencelocal Tiptap docs say CSS floats cannot split BFC blocks and suggest max-height/manual node splitting; table guide requires a specialized pro TableKitsteal failures, reject mechanismuse BFC/table/figure/content-rect rows as tests for Slate provider protocolreject mechanism

Hard cuts and rejected alternatives:

Option / APIKeep / cut / rejectWhyMigration costEvidenceFollow-up
Paged block virtualization as primaryrejectwrong repeated unit; all page surfaces still rendermediumcurrent PagedEditable block item protocolpage mount plan
Persist page breaks by defaultrejectlocal/browser drift would corrupt ordinary editing metadatahighPretext drift evidenceopt-in authority
Claim Pretext headless todaycutprepare() needs canvaslowPretext sourceprofile docs
TanStack public APIrejectleaks list virtualizer into editor APIlowexisting plansinternal adapter
Public pageVirtualization propcut for betaduplicates domStrategy and leaks page mount policylowpressure passinternal page/spread mount plan
Tiptap CSS-float page layoutrejectmakes BFC/splitting limitations part of the editor substratemediumlocal Tiptap docsprovider/split-policy protocol
Default shared page-break writesrejectderived browser layout would churn collaboration and undo stackshighstate-field collab/history policy and Pretext drift evidenceread-only default; elected writer only
Raw Slate product TableKitrejecttable pagination policy belongs to table/media providers and product packagesmediummaintainer objection pass and Tiptap TableKit negative evidenceminimal provider protocol

Plan deltas from review:

  • Added explicit Pretext determinism caveat.
  • Added page-level virtualization as the paged-mode target.
  • Added opt-in authoritative page-break snapshot extension point.
  • Added Tiptap edge-case rows for paragraph splitting, tables, images, font variability, and padding.
  • Kept Pretext as the right call; the revision is about contracts and repeated units, not about abandoning Pretext.
  • Added related issue discovery and manual ledger sync classification for #5944, #790, #5924, #4141, #5131, #2051, #2793, #2572, #3892, #5945, #4056, and #5992.
  • Added #2793 as the sharper screen-reader release guard for page/block virtualization.
  • Hardened the intent/boundary pass into an owner-boundary contract and option matrix: raw Slate owns document/state; layout owns derived profiles/pages; React owns DOM mounting; collab/export owns authority; Plate/apps own product UI.
  • Rejected server-only pagination authority as a default while keeping it as an opt-in writer for strict export/compliance flows.
  • Cut public pageVirtualization before beta. domStrategy remains the public incomplete-DOM switch; paged mode chooses page/spread mount items internally.
  • Split authoritative page-break snapshots into read/write authority roles to avoid every peer writing layout metadata.
  • Added pushback on Tiptap Pages: its docs are negative evidence for CSS-float pagination, max-height hacks, manual semantic splits, and specialized table layout. Slate should steal the failure taxonomy, not the mechanism.
  • Completed the maintainer objection ledger. The result keeps the architecture but narrows the public contract: strict breaks are read-mostly/elected-writer metadata, table/media logic is provider-owned, virtualized paged DOM stays explicit/degraded until browser proof, and examples split canonical DX from stress harness.
  • Completed high-risk deliberate mode. Adjacent tests keep the direction alive, but the final proof queue is explicit: page/spread mount plan, authoritative break authority, provider split protocol, paged missing-DOM policy, canonical DX split, and .tmp/slate-v2 release gates.

Open questions and decision-changing evidence:

QuestionWhy it mattersEvidence neededOwnerStatus
Can Pretext ship a stable headless measurement layer soon enough for beta?affects profile/API wordinglocal upstream source/release evidenceresearch passanswered for current plan: no current headless runtime contract
Should pageVirtualization be public or internal auto behavior?affects DXAPI pressure pass and example reviewDX passanswered: internal behavior behind virtualized domStrategy
How much table layout belongs in raw slate-layout vs app/plugin providers?affects unopinionated Slate boundarymaintainer objection passobjection passanswered: raw slate-layout defines provider/split-policy protocol only; table/media packages own row/span/intrinsic behavior

Implementation phases with owners:

PhaseOwnerScopeEntry criteriaExit criteriaVerification
1slate-plan execution modeintroduce page/spread mount-plan protocol with retained visible/selected/promoted/composing pagesuser accepts plansource compiles, unit tests cover protocol and mounted page countpackage tests
2slate-layoutmeasurement profile + optional page-break snapshot API with read/write authority rolesphase 1 greensnapshot invalidation/profile/writer/history-skip/shared-replay tests greenpackage tests
3slate-reactpage-level virtualizer inside PagedEditable behind virtualized domStrategyphases 1-2 greenpage scroll/type/composition/select-all/copy proof greenPlaywright
4layout/table/mediaprovider/split-policy hardening for table/media/BFC-like boxesphase 3 greenmerged-cell, nested-table, oversized-media, and page-boundary rows greenunit + browser
5docs/examplessplit canonical pagination DX from proof harnessbehavior proof greencanonical example shows call-site first; proof harness keeps debug/stress UIbrowser proof

Fast driver gates:

GateCwdCommand / artifactProvesStatus
planning artifact checkplate-2node .agents/rules/autogoal/scripts/check-complete.mjs docs/plans/2026-05-25-slate-v2-pretext-pagination-virtualization-feedback.mdfinal plan integritypassed in closure
Slate v2 package proof.tmp/slate-v2bun --filter ./packages/slate-layout test; cd packages/slate-react && bun test:vitest; targeted slate-history state-field rows when page-break metadata is implementedsource/API behaviorpending execution
Slate v2 browser proof.tmp/slate-v2playwright test playwright/integration/examples/pagination.test.ts --project=chromium; paged virtualized follow-up rows; playwright test playwright/integration/examples/dom-coverage-boundaries.test.ts --project=chromiumpaged editing/page virtualization/missing-DOM policypending execution
Slate v2 release proof.tmp/slate-v2bun check, escalating to bun check:full only for release-quality browser claimswhole touched surfacepending execution

Final user-review handoff outline:

  • accepted plan items: Pretext default layout engine, page/spread virtualization for paged mode, block/fragment virtualization for continuous/pathological docs, opt-in pageBreaks, metadata-only measurementProfile, provider/split protocols for table/media/BFC content, and explicit missing-DOM caveats.
  • before / after API shape: replace scattered page display props with pageView; keep virtualized domStrategy as the only public incomplete-DOM switch; cut public pageVirtualization.
  • hard cuts: no cross-client/server page-break determinism claim today, no CSS-float pagination model, no raw Slate product TableKit, no exposed TanStack or Pretext cache API, and no native-equivalence claim for unmounted pages.
  • issue claims and non-claims: no new Fixes #...; no new Improves #...; #5944, #790, #2793/#2572, #5924, rerender guardrails, and large-doc rows remain proof-gated.
  • proof gates: page/spread mount contract, mounted-count/edit-latency proof, authoritative break replay/stale checks, table/media fixtures, DOMCoverage browser rows, canonical example split, and .tmp/slate-v2 release commands.
  • accepted-plan execution handoff: implementation starts only after explicit user acceptance and a new execution-shaped slate-plan invocation naming this plan.

Final completion gates:

GateRequired evidenceStatus
score >= 0.92 and no dimension below 0.85scorecard rows cite evidencepass
all pass rows complete or skipped with evidencephase/pass table closedpass
issue/reference sync closedissue-ledger sync status closedpass
live source grounding completesource-backed rows cite current ownerspass
workspace verification recordedverification workspace gate closedpass
autoreview clean or N/AN/A for planning-only, required if execution patches codepass
final handoff emitted or lane remains pendingfinal handoff recorded; next pass nonepass
check-complete passescheck-complete commandpass

Findings:

  • Pretext is the right engine because it splits one-time preparation from hot-path arithmetic layout.
  • Pretext is not fully headless today because measurement still requires OffscreenCanvas or DOM canvas.
  • Current Slate v2 already has the right raw ingredients: page settings, fragments, pages, projections, Pretext engine, PagedEditable, state fields, and virtualized DOM strategy.
  • Current paged virtualization shape is wrong for long-term architecture: PagedEditable feeds block geometry into Editable and renders every page surface.
  • The feedback's authoritative-source idea is the correct strict-fidelity extension point. Make it opt-in, shared state, profile-hashed, and stale-safe.
  • Related issue discovery found no basis for new fixed or improved issue claims.
  • #2793 should be treated as a release guard for virtualized/missing-DOM modes; #2572 is too broad/docs-shaped to carry that alone.

Decisions and tradeoffs:

  • Decision: paged mode virtualizes pages/spreads first.
  • Decision: continuous/pathological mode may virtualize top-level blocks or layout fragments.
  • Decision: strict collab/export reads an authoritative page-break snapshot when supplied and valid.
  • Tradeoff: more API vocabulary, but it prevents the worse mistake: pretending local browser layout is document truth.

External/browser findings:

  • Cyrus Radfar article, read 2026-05-25: https://cyrusradfar.com/thoughts/pretext-beyond-the-browser
    • Pretext improves virtual scrolling by predicting variable text heights before rendering.
    • Server/CI use cases are plausible because the hot path is arithmetic.
    • The catch is still prepare(): measurement depends on canvas.measureText() unless a matching headless/native measurement layer exists.
  • Romik Makavana Tiptap article, attempted 2026-05-25: https://romik-mk.medium.com/tiptap-pagination-problems-solutions-31f1a0b51e08
    • curl returned a Cloudflare challenge, so this remains user-summary context.
    • Paragraph breaks fail when inline widths/font sizes/line heights/emoji/inline nodes vary.
    • Tables fail around row splitting, merged cells, nested structures, and footer overlap.
    • Padding/blank spacer hacks lose lines and corrupt page geometry.
  • Local Tiptap Pages docs, read 2026-05-25 from ../tiptap-docs:
    • Pages limitations use CSS floats around page gaps; BFC blocks such as tables, figures, and styled containers cannot split and can break pagination.
    • the documented mitigations are max-height/--page-max-height and manual node splitting, which is exactly why Slate should not copy the mechanism.
    • the table guide requires a Pages-specific TableKit with heavily modified behavior/layout and warns the open-source TableKit is incompatible.

Source evidence:

  • ../pretext/RESEARCH.md:20 says the goal is expensive text work once in prepare() and arithmetic-only layout().
  • ../pretext/RESEARCH.md:55 documents system-ui canvas/DOM resolution mismatch on macOS and recommends named fonts for accuracy.
  • ../pretext/RESEARCH.md:131 documents emoji canvas/DOM discrepancy.
  • ../pretext/RESEARCH.md:142 says the HarfBuzz headless probe was useful but not runtime direction.
  • ../pretext/src/measurement.ts:36 requires OffscreenCanvas or DOM canvas and throws without one.
  • ../pretext/src/measurement.ts:61 calls ctx.measureText(seg).width.
  • .tmp/slate-v2/packages/slate-layout/src/index.ts:113 already has split policy vocabulary: avoid, line, page, row.
  • .tmp/slate-v2/packages/slate-layout/src/index.ts:236 defines the page layout snapshot with blocks, fragments, pages, root, settings, and version.
  • .tmp/slate-v2/packages/slate-layout/src/index.ts:1349 implements the Pretext page layout engine.
  • .tmp/slate-v2/packages/slate-layout/src/react.tsx:185 maps projection blocks into top-level virtualized items.
  • .tmp/slate-v2/packages/slate-layout/src/react.tsx:219 renders all page surfaces.
  • .tmp/slate-v2/packages/slate-react/src/dom-strategy/use-virtualized-root-plan.ts:212 consumes top-level runtime ids and block layout items.
  • .tmp/slate-v2/site/examples/ts/pagination.tsx:41 keeps page settings in a shared, persisted state field.
  • docs/research/sources/editor-architecture/pretext-pagination-page-virtualization.md records the current Pretext / page virtualization / Tiptap Pages research synthesis.
  • local and official Tiptap Pages docs read/fetched 2026-05-25 record CSS-float page gaps, oversized non-splittable BFC blocks, max-height/manual split mitigations, figures, and specialized table extensions as current pagination constraints.

Timeline:

  • 2026-05-25: Slate Plan goal created for virtualization/pagination feedback.
  • 2026-05-25: Current-state pass completed with local Pretext source, external article evidence, live .tmp/slate-v2 source, existing plan/ledger evidence, and initial score.
  • 2026-05-25: Related issue discovery completed from live/current ledgers. No new fixed/improved claims found; #2793 added as screen-reader release guard.
  • 2026-05-25: Issue-ledger pass completed. Manual sync rows were appended to gitcrawl-v2 sync ledger, issue coverage matrix, and fork dossier; PR reference was inspected and left unchanged because this plan adds no fixed/improved claims.
  • 2026-05-25: Intent/boundary and decision brief pass completed. The plan now has owner boundaries, non-goals, decision rules, option matrix, chosen architecture, consequences, and next research owner.
  • 2026-05-25: Research, ecosystem strategy, and live-source refresh pass completed. Added a compiled research source page, refreshed current Pretext and Slate v2 source lines, fetched Cyrus, read local/official Tiptap Pages docs, and recorded the Medium current-access caveat.
  • 2026-05-25: Performance/DX/migration/regression/simplicity pressure pass completed. Cut public pageVirtualization, kept domStrategy as the public incomplete-DOM switch, split authoritative page-break roles, and sharpened package/browser/a11y/IME/clipboard/table/media proof gates. Applied the Tiptap correction: use Pages as negative evidence, not as a CSS-float model to steal.
  • 2026-05-25: Slate maintainer objection ledger completed. Kept the architecture but narrowed the public contract: no core Pretext dependency, no default shared page-break writes, no raw Slate product TableKit, no native-equivalence claim for missing DOM, and no proof-harness complexity in canonical DX.
  • 2026-05-25: High-risk deliberate mode completed. Expanded the pre-mortem into release-blocking failure rows, separated adjacent proof from missing final proof, and named package/browser/release gates for every risky claim.
  • 2026-05-25: Ecosystem maintainer pass completed. Confirmed no strategy reversal; hardened Tiptap Pages into negative evidence against CSS-float pagination, added DOMCoverage and GitHub large-surface rows to the ecosystem strategy table, and kept TanStack upgrades as implementation fuel only.
  • 2026-05-25: Revision pass completed. Locked the public beta target around pageView, virtualized domStrategy, internal page/spread virtualization, metadata-only measurement profiles, opt-in pageBreaks, generic provider split policies, and no new fixed/improved issue claims.
  • 2026-05-25: Issue sync accounting completed. Synced final API/proof wording into the v2 sync ledger, issue coverage matrix, fork dossier, and PR reference; fixed/improved claim counts remain unchanged.

Verification evidence:

  • Active goal: get_goal returned the matching Slate Plan objective for this virtualization/pagination feedback lane.
  • Skill gate: .agents/skills/slate-plan/SKILL.md was read in this closure pass; closure is legal because all previous scheduled pass rows were already complete.
  • Planning artifact gate: this plan records current-state source reads, ecosystem research, issue/reference accounting, maintainer objections, high-risk proof gates, final API revision, issue sync accounting, scorecard, and handoff.
  • Behavior/browser proof boundary: no Slate v2 tests or browser proof were run in this planning closure because this lane does not change implementation or claim behavior. The plan names the exact package/browser proof gates required before accepted implementation, release-quality browser claims, or issue closure.
  • Final checker: node .agents/rules/autogoal/scripts/check-complete.mjs docs/plans/2026-05-25-slate-v2-pretext-pagination-virtualization-feedback.md returned [autogoal] complete: docs/plans/2026-05-25-slate-v2-pretext-pagination-virtualization-feedback.md.

Final user-review handoff:

  • Recommended architecture: keep Pretext as the default slate-layout engine, but state the measurement contract honestly. prepare() still depends on canvas/browser/font profile behavior, so cross-client/server page-break determinism is not a default promise.
  • Public API target: useSlateLayout, PagedEditable, pageView, virtualized domStrategy, and optional pageBreaks. Cut public pageVirtualization; do not expose TanStack, Pretext caches, or product TableKit mechanics.
  • Runtime target: paged mode virtualizes pages/spreads first; continuous or pathological documents can keep block/fragment virtualization.
  • Strict fidelity: pageBreaks is opt-in metadata with read/write authority, measurement/profile hashes, stale rejection, writer identity, history-skip derived writes, and export-reader proof.
  • Tiptap lesson: steal the failure taxonomy for BFC blocks, tables, figures, styled containers, oversized media, padding/content rects, extension breakage, export, and collab. Reject CSS-float pagination, max-height correctness hacks, manual semantic splitting, and raw Slate product TableKit.
  • Issue posture: no new Fixes #... and no new Improves #.... #5944, #790, #2793/#2572, #5924, rerender guardrails, and large-doc rows remain proof-gated exactly as recorded.
  • Execution boundary: implementation begins only after explicit user acceptance and a new execution-shaped slate-plan invocation naming this plan. Execution must run focused package tests and pagination/DOMCoverage browser rows from .tmp/slate-v2 before any release or issue-closure claim.

Reboot status:

QuestionAnswer
Where am I?Closure score and final gates complete
Where am I going?User review; execution starts only after explicit acceptance
What is the goal?Closed: the plan is ready for user review
What have I learned?Feedback is correct; Pretext is still right but not headless-stable today; page mode needs internal page/spread virtualization; public pageVirtualization is extra API noise; authoritative page breaks need explicit writer authority; Tiptap Pages is stronger negative evidence against CSS-float pagination; DOMCoverage must remain a browser-policy layer; measurementProfile should be metadata, not a user-facing engine knob; issue ledgers can stay no-claim because the plan is architecture/proof-gate work, not an implementation fix
What have I done?Closed the plan with current-state evidence, target direction, related issue classifications, manual issue-ledger sync, hardened owner/decision boundary, compiled research page, refreshed source/doc evidence, Tiptap pushback, pressure-pass cuts, maintainer objection decisions, high-risk proof gates, final ecosystem keep/reject decisions, final API/proof revision decisions, final issue/PR accounting sync, final score, and final handoff

Open risks:

  • None for planning closure. Implementation risks remain intentionally execution-gated: page/spread mount planning, authoritative break replay, provider/split fixtures, missing-DOM browser proof, canonical example split, and release command proof.