Back to Plate

pagination fast scroll virtualization

docs/plans/2026-05-28-pagination-fast-scroll-virtualization.md

53.0.8102.4 KB
Original Source

pagination fast scroll virtualization

Objective: Close a Slate Plan for /examples/pagination fast-scroll virtualization: prove the real broken scroll path from the user video, steal the right test mechanics from ../virtual and ../pierre, choose the long-term page/table virtualization architecture, and stop at user-review-ready planning before any new implementation execution.

Goal plan: docs/plans/2026-05-28-pagination-fast-scroll-virtualization.md

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

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

Applied packs:

  • slate-plan

Completion threshold:

  • Planning is done only when score >= 0.92, no dimension is below 0.85, every pass row is complete or intentionally skipped with evidence, issue/reference sync is closed, final handoff is emitted, and node .agents/rules/autogoal/scripts/check-complete.mjs docs/plans/2026-05-28-pagination-fast-scroll-virtualization.md passes.
  • Current activation closes the closure score and final gates pass. Planning goal may complete only after the final checker passes.

Verification surface:

  • Planning checks run in plate-2.
  • Slate v2 source/runtime/browser claims must cite live .tmp/slate-v2 files.
  • Execution proof, when accepted later, runs from .tmp/slate-v2 and must cover browser scroll replay, frame/long-task budget, DOM/page/row/cell budget, bounded memory/element churn, typecheck, lint, and focused Playwright rows.

Constraints:

  • Planning mode may edit only planning, research, issue-ledger, and PR-reference artifacts.
  • Do not touch .tmp/slate-v2 implementation again until this plan is ready and the user explicitly accepts execution mode.
  • Keep core Slate unopinionated. The example can expose stress controls; the package API should stay small and Slate-shaped.
  • Performance claims need measured browser proof, not vibes.

Boundaries:

  • Allowed planning edit scope: docs/plans/**, docs/research/**, docs/slate-issues/**, docs/slate-v2/ledgers/**, docs/slate-v2/references/**.
  • Current live source owner: .tmp/slate-v2/packages/slate-layout/src/react.tsx.
  • Current example owner: .tmp/slate-v2/site/examples/ts/pagination.tsx.
  • Current browser proof owner: .tmp/slate-v2/playwright/integration/examples/pagination.test.ts.
  • Video evidence: /Users/zbeyens/Library/Application Support/CleanShot/media/media_p2a87hYOIr/2026-05-28 at 09.18.04.mp4.

Blocked condition:

  • Block only if the same blocker repeats for three goal turns and no video, sibling-repo, source, issue-ledger, 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; if the user accepts this plan, start a separate execution goal for the implementation queue in .tmp/slate-v2
  • final_handoff_status: complete

Current verdict:

  • verdict: ready for user review
  • confidence: 0.94
  • keep / cut / revise call: accept the planning architecture for user review; execute only after the user accepts this plan.
  • reason: the final plan chooses one Slate-owned page-window authority plus a content-unit corridor, rejects the bad public/API/model alternatives, closes issue accounting without overclaiming, and names the exact browser/unit proof gates needed to fix the user-video failure in execution mode.

Completion rule:

  • Do not call update_goal(status: complete) while any required checklist item remains unchecked. If an item does not apply, check it and add N/A: <reason>.
  • Do not call update_goal(status: complete) until every Slate Plan completion gate below is satisfied and node .agents/rules/autogoal/scripts/check-complete.mjs docs/plans/2026-05-28-pagination-fast-scroll-virtualization.md passes.
  • Do not create hook state for this goal. This file plus the active goal are the durable state.

Start Gates:

GateAppliesEvidence
Skill analysis before editsyesslate-plan read from .agents/skills/slate-plan/SKILL.md.
Active goal checked or createdyesGoal created in the first activation; current activation rechecked the active goal before this pass.
Source of truth read before editsyesRead user video metadata/contact sheet, research index/log, issue references, live .tmp/slate-v2 source/tests, ../virtual, and ../pierre.
docs/solutions checked for non-trivial existing-code workyesRead docs/solutions/workflow-issues/2026-05-23-slate-v2-issue-claims-need-exact-browser-proof-and-honest-input-contracts.md.
Live .tmp/slate-v2 grounding needed for current-state claimsyesSource rows below cite exact .tmp/slate-v2 files/lines.

Work Checklist:

  • Objective includes lane outcome, full pass schedule, one-pass-per- activation policy, completion threshold, verification surface, constraints, boundaries, and blocked condition.
  • One-pass-per-activation policy respected for this activation.
  • Live source grounding recorded for current implementation claims.
  • Related issue discovery / ClawSweeper pass applied with concrete evidence.
  • Full issue-ledger/reference pass applied; sync ledger updated and matrix/dossier/PR reference left unchanged with evidence.
  • Research and ecosystem synthesis complete for every external system used as evidence.
  • Intent/boundary record and decision brief completed for the current plan target.
  • Scorecard recorded with evidence; total score >= 0.92 and no dimension below 0.85 before closure.
  • Applicable implementation-skill review matrix seeded.
  • Slate maintainer objection ledger complete for every breaking/paradigm change, or marked N/A with reason.
  • Verification workspace gate recorded for every Slate v2 source, runtime, browser, package, public API, or issue-fix claim.
  • TDD target recorded for behavior/proof changes.
  • Browser proof captured for the current browser-surface failure claim: current video evidence is recorded; replayable Playwright proof is an execution acceptance gate, not a planning-closure claim.

Completion Gates:

GateAppliesRequired actionEvidence
Named verification thresholdyesRun plan check at closurefinal closure pass runs node .agents/rules/autogoal/scripts/check-complete.mjs docs/plans/2026-05-28-pagination-fast-scroll-virtualization.md; result recorded in Verification evidence
Slate v2 source, runtime, browser, package, public API, or issue-fix claimyesRecord live .tmp/slate-v2 source reads now; execution proof after accepted implementationsource read complete; execution proof explicitly deferred
Issue ledger or PR reference changedyesSync issue/reference rows after issue passfinal v2 sync ledger note updated; matrix/dossier/PR unchanged after audit because existing rows already match
Autoreview for uncommitted implementation changesexecution onlyRun from .tmp/slate-v2 after accepted implementationN/A for planning closure; no implementation files were edited in this final planning pass
Final user-review handoffyesEmit after closure passfinal handoff outline completed below and summarized in final response
Goal plan completeyesRun check-completefinal closure checker command recorded in Verification evidence

Phase / pass table:

PhaseStatusEvidenceNext
Current-state read and initial scorecompletevideo read, live source read, sibling repo scan, initial scorerelated issue discovery
Related issue discoverycompletegitcrawl status/doctor/search, #790/#5944 threads and neighbors, existing coverage/sync/dossier rows reusedissue-ledger pass
Issue-ledger passcompleteappended 2026-05-28 sync note; audited live ledger, v2 sync ledger, frozen ledger, clusters, fork dossier, coverage matrix, and PR referenceintent/boundary pass
Intent/boundary and decision briefcompleteclarified owner split, invariants, hard non-goals, selected two-window architecture, and rejected alternativesresearch refresh
Research, ecosystem strategy, live-source refreshcompleterefreshed TanStack Virtual API/source, Pretext source/status, Tiptap Pages docs, ../virtual browser tests, ../pierre tree/diff virtualization tests, and current .tmp/slate-v2 ownerspressure passes
Performance/DX/migration/regression/simplicity pressure passescompleteapplied performance, performance-oracle, Vercel React, react-useeffect, tdd, and simplicity lenses; revised plan toward one shared page-window authority, indexed mount helpers, real scroll INP/DOM budgets, and eager hidden-child rendering proofobjection ledger
Slate maintainer objection ledgercompletetested core objections against public API minimalism, shared window authority, renderer child windowing, native-behavior degradation, browser-proof scope, collab/export fidelity, and example ownership; demoted public child-window slot to last resort behind an internal EditableLayout child-range planhigh-risk pass
High-risk deliberate modecompletestress-tested the plan against blank-page races, false green scroll tests, overscan overfitting, dual-window drift, eager child allocation, native-behavior degradation, sparse-fixture confusion, selection retention, proof flakiness, and CI/runtime costecosystem maintainer pass
Ecosystem maintainer passcompleterechecked TanStack, Pretext/Premirror, Tiptap Pages, ../virtual, and ../pierre after high-risk hardening; no new public API, AST mutation, product TableKit dependency, or strict collab/export claim is justifiedrevision pass
Revision passcompletecollapsed the plan to one final architecture, removed public renderer-slot candidate language, made planning vs execution proof boundaries explicit, and kept the execution acceptance gates as the handoff contractissue sync accounting
Issue sync accountingcompletefinal sync note added to 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 audited with no claim changeclosure score and final gates
Closure score and final gatescompletescorecard total is 0.935 with no dimension below 0.92; workspace and autoreview gates are resolved for planning; final handoff is complete; checker command is recordedfinal handoff

Scorecard:

DimensionWeightScoreEvidence
React 19.2 runtime performance0.200.93Plan readiness is high because the final architecture names the hot owners and repair strategy: raw scroll geometry moves to refs/rAF/window keys, page/spread range math moves to private indexed helpers, and hidden child work moves to internal EditableLayout child ranges. This is not a runtime-fixed claim; browser trace proof stays an execution acceptance gate.
Slate-close unopinionated DX0.200.94Public API stays at domStrategy={{ type: 'virtualized', overscan }}; pageVirtualization, public TanStack options, public renderer child-window slots, AST table splitting, and product TableKit dependency are rejected from the accepted plan.
Plate and slate-yjs migration backbone0.150.92Page windows, child ranges, and mounted ranges remain derived/local; Plate can layer row/unit policy later; slate-yjs syncs document ops and optional page-break snapshots, not viewport churn or client page windows.
Regression-proof testing strategy0.200.94Execution acceptance requires wheel/continuous-scroll replay, visible-label/no-blank assertions after every burst, shared window coherence, child-range materialization counters, selected-row editing, event-to-paint/long-task sampling, DOM/page/row/cell budgets, and native-behavior classification.
Research evidence completeness0.150.95TanStack Virtual, Pretext/Premirror, Tiptap Pages, ../virtual, ../pierre, current .tmp/slate-v2 runtime/test owners, issue ledgers, and PR reference were rechecked after high-risk hardening and revision.
shadcn-style composability and minimalism0.100.92Example controls stay URL-backed, virtualized-only, and proof-driven; core gets no product settings, no public virtualizer manager, and no UI policy beyond the existing Slate-shaped DOM strategy.

Final weighted score: 0.935. This is the plan-readiness score, not a shipped runtime performance claim.

Source-backed architecture north star:

  • target shape: paged mode has two explicit windows: page shell window and content-unit window. Page shells mount only visible pages plus small overscan; expensive units inside a multi-page block mount only for the visible content corridor plus selected/composing/promoted paths.
  • source evidence: current context already carries visiblePageIndexes, visibleContentRange, and selectedPaths in .tmp/slate-v2/packages/slate-layout/src/react.tsx:46-61.
  • source evidence: current useSlateLayoutFragments filters units by visible content range or selected path overlap in .tmp/slate-v2/packages/slate-layout/src/react.tsx:217-226.
  • rejected drift: do not split table nodes in the AST just to paginate; derived layout fragments/pages stay separate from model nodes.
  • migration posture: Plate/table plugins should provide row/unit layout policy; slate-yjs should replicate document ops, not page-window churn.

Public API target:

SurfaceProposed shapeUser-facing DXCompatibility / migrationEvidenceVerdict
Editable domStrategyKeep Slate-shaped type: 'virtualized', overscan, threshold, estimatedBlockSize; do not leak TanStack optionsExisting DX remains familiar and compactNo public migration if runtime internals change.tmp/slate-v2/site/examples/ts/pagination.tsx:995-1005keep
Pagination example controlsKeep URL-backed stress controls; execution may add a fast-scroll preset only to stabilize the repro URLExample is inspectable without hidden fixturesExample-only, not core API.tmp/slate-v2/site/examples/ts/pagination.tsx:98-137, 1264-1291revise
Scroll test harnessAdd internal Playwright helper, not public runtime APITests express user paths: wheel, jump, continuous scroll, no blanksTest-only../virtual/packages/react-virtual/e2e/app/test/scroll.spec.ts:54-112add

Internal runtime target:

LayerCurrent ownerTarget mechanismAvoidsEvidenceVerdict
Shared page-window authorityPagedEditable plus Editable virtualized planCompute one page-window snapshot keyed by visible page item indexes and reuse it for page chrome, editable top-level rows, and page-derived content rangeIndependent windows drifting under fast scroll and producing blank page chrome or row/chrome mismatch.tmp/slate-v2/packages/slate-layout/src/react.tsx:550-620; .tmp/slate-v2/packages/slate-react/src/dom-strategy/use-virtualized-root-plan.ts:407-422add
Page mount plan.tmp/slate-v2/packages/slate-layout/src/page-mount-plan.tsIndex fragments by page once, build page/spread items in O(pages + fragments + units), and derive visible item ranges by page-window key/binary range instead of filtering every item on every raw scroll tickO(pageGroups * fragments) plan creation and O(pageItems) viewport filtering as the default hot scroll pathlines 57-125 and 177-205revise
Scroll viewport update.tmp/slate-v2/packages/slate-layout/src/react.tsxStore raw scroll geometry in refs, schedule one rAF update, and set React state only when the page-window key or content corridor changesPixel-level React renders during fast scrolllines 509-536; React/useEffect lensrevise
Fragment unit materialization.tmp/slate-v2/packages/slate-layout/src/react.tsxUnit corridor based on shared page window plus selected/composing/promoted paths; keep selected deep rows mounted through indexed path-to-page lookupFull multi-page table materialization or selected row unmountlines 217-226 and 609-620; use-virtualized-root-plan.ts:371-397keep/revise
Renderer child windowingEditableLayout plus EditableDescendantNode internal child rangesAdd a layout-owned child-range plan so core creates only visible child ranges plus selected/composing/promoted paths; public renderer child-window API is not part of the accepted planReact element allocation proportional to table row count on every visible table render, without widening unstable renderer API.tmp/slate-v2/site/examples/ts/pagination.tsx:228-290; .tmp/slate-v2/packages/slate-react/src/components/editable-text-blocks.tsx:1030-1148; docs/slate-v2/references/pr-description.md:1327-1418revise
Stress document tail.tmp/slate-v2/site/examples/ts/pagination.tsxStress pages should render clear content or deliberate sparse diagnostic placeholders, and the proof must assert visible stress labels after fast scrollFalse positives where blank pages look like virtualization failurelines 426-431 and 614-622revise

Hook / component / render DX target:

SurfaceCall-site shapeComposition rulePerformance ruleEvidenceVerdict
PagedEditablelayout, pageView, domStrategy, renderPagePage chrome belongs outside document contentReact projects layout; Slate model stays source of truth.tmp/slate-v2/site/examples/ts/pagination.tsx:1346-1352keep
useSlateLayoutFragmentsrenderer reads fragments for its own pathRenderers should not compute global paginationReturned fragments must already be windowed.tmp/slate-v2/packages/slate-layout/src/react.tsx:197-235keep

Plate migration-backbone target:

PressureSlate substrate targetPlate adaptation routeNon-goalEvidenceVerdict
Tables spanning pagesDerived row/unit fragments, not AST table splittingPlate table can provide row sizes/split rules laterRaw Slate TableKit and product table commandsPretext/Tiptap research row belowkeep
Stress controlsExample-level controls onlyPlate can build richer UI over core propsCore owning product pagination settingscurrent example controlskeep

slate-yjs migration-backbone target:

PressureSlate substrate targetCollaboration routeNon-goalEvidenceVerdict
Page/window churnKeep as derived client view stateDo not sync mounted page/window state through YjsCross-client identical page breaks by defaultPretext drift researchkeep
Strict pagination fidelityOptional authoritative page-break snapshot laterSync snapshot/profile only when app opts inPromise strict export/collab fidelity by defaultdocs/research/sources/editor-architecture/pretext-pagination-page-virtualization.md; .tmp/slate-v2/packages/slate-layout/src/index.ts:2369-2413keep

Intent / boundary record:

  • intent: make /examples/pagination?strategy=virtualized survive real fast user scrolling through a large paged document with a table spanning about 10 pages, then into a roughly 1000-page stress tail.
  • outcome: visible pages never render as accidental blanks, visible table rows and cells stay materialized inside the viewport corridor, selected/composing content stays mounted, DOM/page/row/cell counts stay bounded, and the proof fails on the attached video class.
  • user workflow boundary: the proof path is open example, choose virtualized DOM strategy, use real wheel/trackpad-like scroll through table pages into the stress tail, then inspect/edit selected table content.
  • owner split:
    • slate-layout owns derived layout snapshots, page geometry, page mount items, fragment/unit filtering, and viewport-derived windows.
    • slate-react Editable owns the generic domStrategy bridge and layout item exposure, not pagination-specific product settings.
    • the example owns URL-backed stress controls, clear fixture content, and inspection counters.
    • Playwright owns video-class replay, geometry checks, and frame/DOM budgets.
  • in-scope: internal page/spread virtualization for paged mode, a second content-unit corridor for expensive split blocks, selected/composing/promoted retention, example-only stress knobs, and browser scroll proof.
  • non-goals: AST table splitting, product TableKit, public pageVirtualization, public TanStack option passthrough, strict cross-client page break fidelity, native browser find/a11y parity for unmounted content, and broad scrollSelectionIntoView/mobile closure.
  • invariants:
    • Slate model nodes are never split, reordered, or duplicated for layout.
    • Page shells may unmount only outside the visible page window plus overscan.
    • Fragment units may unmount only outside the visible content corridor unless selected, composing, or explicitly promoted.
    • Virtualized paged mode is a degraded native surface until browser proof promotes exact native behavior.
    • A visible blank page is a test failure unless the fixture deliberately marks it as sparse diagnostic content.
  • decision boundaries: this plan may choose internal runtime/test architecture and example controls; new public API, native parity claims, or product table semantics need separate maintainer-objection and proof rows.
  • unresolved user-decision points: none.

Decision brief:

  • chosen architecture: Slate-owned two-window materialization. Paged mode uses a page shell window; expensive multi-page content inside those pages uses a content-unit window. Both are derived from layout state and selected paths.
  • public API call: do not add pageVirtualization. Page/spread virtualization is the internal paged-mode behavior when domStrategy is { type: 'virtualized' }; example controls may expose overscan and stress size, but core keeps the Slate-shaped domStrategy surface.
  • principles:
    • User-scroll proof beats synthetic scrollTop proof.
    • Layout projection must not mutate the document model.
    • Public API stays small; engine details stay internal.
    • Repeated-unit budgets are explicit: page surfaces, rows, cells, DOM nodes, frame time, and long tasks.
    • Selection/composition retention beats giant overscan.
  • top drivers: fast-scroll blank-window risk, 1000-page DOM pressure, multi-page table unit cost, and avoiding a public API that bakes in today's virtualizer.
  • viable options:
    • A. Slate-owned page shell window plus content-unit window, with real scroll tests and coalesced viewport updates.
    • B. Let TanStack Virtual own page virtualization directly.
    • C. Render placeholders while scrolling and hydrate content after scroll.
    • D. Hide the failure with large overscan.
  • selected option: A.
  • rejected alternatives:
    • B leaks the virtualization engine into Slate's public mental model.
    • C makes an editor feel broken unless a product deliberately opts into a preview/degraded mode.
    • D spends DOM and memory to mask a scheduler/windowing bug.
  • consequences:
    • Runtime work should gate viewport updates by animation frame and/or window index, not every raw scroll event.
    • Tests need wheel/continuous-scroll replay, visible-content assertions, and DOM/page/row/cell counters.
    • Table/media pagination should use derived fragments or provider-owned units, never AST splitting.
    • Strict collab/export fidelity stays optional through authoritative page-break snapshots, not default client state.
  • follow-ups: research refresh, performance pressure pass, maintainer objection pass, and final proof budgets.

Issue accounting:

Issue / clusterClaim categoryExact claimWhyProof routeV2 sync ledgerPR line
#5944 stable per-line paginationissue-reviewed, direct relatedNo fixed/improved claimThe plan targets page-boundary stability, but no current browser proof covers flicker, caret mapping, or stable edits across page fragments.Replay typing/editing across page boundaries, assert no page-break oscillation, caret maps to the same logical point, visible fragment content remains mounted.Current 2026-05-28 sync row added; matrix/dossier rows already cover this surface.related matrix only
#790 dynamic rendering / virtualizationrelated proof-route backlogNo fixed/improved claimPage/spread virtualization directly targets the requested dynamic rendering pressure, but a single jump test is nowhere near enough.Mount/edit/scroll benchmark, mounted-count proof, DOM coverage proof, browser-native behavior proof, continuous fast-scroll replay.Current 2026-05-28 sync row added; matrix/dossier rows already cover this surface.related matrix only
#5131, #2051 subscription/rerender breadthguardrailsNo claim changeLayout snapshots, page windows, and fragment hooks must not widen selection subscriptions or leaf rerender breadth.Subscription/rerender counters around scroll, selection, and simple typing in virtualized pagination.Preserve existing guardrail rows.guardrail only
#4141, #3656, #4210, #5349 rerender-performance neighborsrelated guardrailsNo claim changegitcrawl neighbors/search surface repeated render pressure; this plan only owns paged scroll/materialization proof.Keep render-count budgets in focused tests; do not promote unless exact repro/proof is added.No ledger change.not claimed
#5992, #5945, #4056 large-document operation/clipboard rowspreserve existing improvesNo promotion from this planPagination fast-scroll does not prove large cut, paste, or clipboard operation closure.Existing benchmark owners stay authoritative; add no new claim here.Preserve existing Improves rows.unchanged
#2195, #2405 dirty/normalization perfrelated guardrailsNo claim changeFast-scroll work must not add avoidable dirty/normalization work during viewport churn.Operation/normalization counters only if runtime changes touch this path.No ledger change.not claimed
#5826 long-editor refocus/scrollregression floorDo not broadenExisting fixed scroll behavior is a floor; fast-scroll pagination must not regress selection scroll restoration.Selection-scroll regression row after runtime changes.Preserve existing fixed row exactly.unchanged
#4995, #5088, #5473, #4590, #4837, #4844, #5639 scroll-selection/mobile scroll clusteradjacent, not claimedNo claim changeThese are scroll ownership and mobile/native behavior pressures, not solved by page virtualization planning.Exact browser/device proof required before any promotion.No ledger change.not claimed
#5924, #2793, #2572, #3892 DOM/a11y/custom-surface policy rowspolicy non-claims / release guardsNo claim changeMissing-DOM modes and custom layout surfaces need explicit degradation or assistive-tech proof.DOM coverage policy, a11y proof, and custom-surface documentation only in later passes.Preserve existing policy rows.not claimed

Issue-ledger sync status:

  • ClawSweeper related-issue pass: complete for this surface via local gitcrawl archive plus existing ledger rows.
  • generated live gitcrawl rows read: complete for the direct query and direct threads #790, #5944, #5992, #5131, #2051, #2195, #2405.
  • manual v2 sync ledger update: complete; added 2026-05-28 Pagination Fast-Scroll Virtualization Planning Sync to docs/slate-issues/gitcrawl-v2-sync-ledger.md.
  • final revision issue-sync check: complete; appended a final no-claim-change note to the 2026-05-28 sync entry after auditing the final architecture against the issue/reference surfaces.
  • fork issue dossier update: no change; existing Pretext layout/pagination and provider-owned page fragment sections already cover #5944 and #790.
  • issue coverage matrix update: no change; existing related/proof-backlog rows already cover #5944, #790, #5131, #2051, and preserved improves.
  • PR description sync: no change; production-ready virtualization remains unclaimed.

Ecosystem strategy synthesis:

SystemSourceMechanismAvoidsStealRejectSlate targetVerdict
TanStack Virtual../virtual/docs/api/virtualizer.md:67-83, 143-149, 314-334, 480-561; ../virtual/packages/virtual-core/src/lazy-measurements.ts:1-44Headless measured range engine with onChange(sync), overscan, stable keys, rangeExtractor, one-shot measurement snapshots, lazy single-lane materialization, and iOS scroll-write deferralFull repeated-unit DOM and per-item object allocation in huge listsUse as internal range discipline: stable item keys, visible range extraction, measurement snapshot restore, explicit isScrolling, and no default override of scroll-position correctionPublic TanStack option passthrough; default useAnimationFrameWithResizeObserver; smooth-scroll behavior as editor proofSlate-owned page/unit policy with a small domStrategy API; TanStack can inspire internals, not define public DXagree
Pretext/Premirror../pretext/RESEARCH.md:20-37, 55-70; ../pretext/src/measurement.ts:36-111; ../pretext/src/layout.ts:668-710; ../pretext/STATUS.md:1-31Two-phase prepare/layout: expensive segmentation and canvas measurement once, hot layout path arithmetic-only, browser-profile-aware tuningDOM reflow pagination and AST splitsKeep derived layout snapshots and cheap resize relayout; model strict fidelity as measurement profile plus optional authoritative page-break snapshotPromise headless/cross-client page-break determinism by default while measureText() and browser profile matterLocal fast pagination first; strict collab/export fidelity is opt-in state, not core Slate behavioragree
Tiptap Pages../tiptap-docs/src/content/pages/core-concepts/limitations.mdx:11-20; ../tiptap-docs/src/content/pages/guides/table-with-pages.mdx:16-66CSS-float page gaps plus product-specific Pages TableKit for table splittingOwning full document layout in ProseMirror coreSteal failure taxonomy: BFC blocks, tables, print/export, templates, and semantic risk of manual node splittingCSS float trick; manual AST splitting; raw Slate depending on a product TableKitProvider-owned unit/split policy over derived fragments; no AST mutation for layoutagree
../virtual browser tests../virtual/packages/react-virtual/e2e/app/test/scroll.spec.ts:54-112, measure-element.spec.ts:3-42, stale-index.spec.ts:3-38Playwright tests start at offset, perform user-like scroll, assert visible indexes and contiguous geometry, then stress resize/delete/stale-key pathsPassing only programmatic scrollToIndex or one scrollTop jumpAdd video-class wheel/repeated-scroll rows, no gaps/overlaps, visible-content assertions, dynamic resize/delete proof, and stale path/key guardsFixed sleeps as primary proof; list-only assumptionsPoll geometry, DOM counts, frame/long-task budgets, and editor state after real scroll sequencesagree
../pierre trees../pierre/packages/trees/test/file-tree-virtualization-window.test.ts:38-85, 87-188, 189-329, 331-443, 445-532Deterministic unit tests for visible window math, selected/focused retention, scroll-to-path without DOM focus theft, offsets, invisible projection paths, sticky ancestors, scrolling state, and collapse coherenceBrowser-only tests hiding broken range mathAdd pure page-mount/window tests for viewport/overscan, selected/composing/promoted retention, invisible projection no-ops, sticky/page chrome offsets, and collapse/edit coherenceTreat jsdom/unit proof as browser perf proofSplit math tests from browser replay: fast unit tests prove range decisions; Playwright proves human scroll and renderingagree
../pierre diffs../pierre/packages/diffs/src/components/Virtualizer.ts:20-38, 76-117, 288-385, 387-437; ../pierre/packages/diffs/src/react/CodeView.tsx:762-800Pragmatic large diff virtualizer: passive scroll listeners, queued render, dirty flags, big pixel overscan, scroll anchors/repair, and user intent cancellationProgrammatic scroll fighting user scroll; Safari blanking in huge code viewsKeep scroll ownership explicit, queue/coalesce range recompute, anchor visible content when measurements shift, cancel pending programmatic scroll on real user intentBlindly copy 1000px overscan/Safari hacks into core Slate; expose debug globalsCore proof should make blanking impossible by window correctness first; browser-specific hacks stay gated fallback evidencepartial
.tmp/slate-v2 current owners.tmp/slate-v2/packages/slate-layout/src/react.tsx:46-61, 197-226, 509-620; .tmp/slate-v2/packages/slate-layout/src/page-mount-plan.ts:57-205; .tmp/slate-v2/playwright/integration/examples/pagination.test.ts:660-691Existing Slate code already has page indexes, content range filtering, selected-path retention, derived page mount plans, and bounded-DOM one-jump proofStarting from scratchPreserve the owner split and add coalesced viewport/window-index updates plus stronger browser testsTreat the current one-jump test as release proofExecute against existing slate-layout/example/test owners after user accepts the planrevise

Ecosystem maintainer pass:

SystemHigh-risk pressure checkedKeepTightenReject / do not copyResult
TanStack VirtualOverscan, scroll state, stable keys, retained ranges, and measurement snapshotsInternal range discipline: stable keys, rangeExtractor, isScrolling, measurement snapshot/restore conceptsTreat overscan as a tradeoff and diagnostic, never as the architecture; map retained editor paths through Slate-owned windowsPublic TanStack passthrough, smooth scroll as proof, or blindly enabling measurement rAF behaviorno strategy change
Pretext/PremirrorPagination fidelity, text measurement drift, and export/collab claimsDerived layout snapshots and cheap hot-path relayoutKeep strict page-break fidelity as optional authoritative snapshot/profile stateDefault cross-client byte-identical page breaks while canvas/browser-profile measurement remains in playno strategy change
Tiptap PagesTable spanning pages and BFC limitsFailure taxonomy for tables/media/print/exportSlate table/media split policy must be provider-owned derived layout, not model mutationCSS-float pagination, product TableKit dependency, or manual AST splitting in core Slateno strategy change
../virtual browser testsCurrent one-jump proof missed the user video pathUser-scroll-like browser proof plus visible geometry/content assertionsAdd repeated wheel/continuous-scroll replay, no blank/gap/overlap checks, and stale key/path guardsFixed sleeps or list-only assumptions as primary prooftightened proof gates
../pierre treesRange math, focused/selected retention, scroll-to-path, sticky offsets, collapse coherenceFast deterministic window math as unit coverageAdd page-window and child-range unit contracts before relying on browser tracesTreat jsdom/unit proof as browser perf prooftightened unit-test target
../pierre diffsFast-scroll blanking, queued renders, scroll anchors, user-intent cancellationQueue/coalesce render range work and anchor visible content when measurements shiftKeep large overscan and browser-specific repair as fallback evidence onlyCopy debug globals, 1000px overscan default, or broad DOM fallback into core Slatepartial only
React runtimeVisible editor content must stay urgent during scrollPassive listeners, refs for transient scroll geometry, stable state keysrAF/window-key gating is allowed only when visible content remains synchronizedstartTransition, Activity, or deferred rendering as the editor-body fixno new React substrate

Research refresh conclusions:

EvidenceWhat it changedPlan effect
TanStack docs/sourceOverscan is a blanking tradeoff, not the architecture; snapshots and stable keys matter for restoration and churnKeep overscan as a small control, but make window correctness and measurement retention the real fix
Pretext source/statusPretext is fast because the hot layout path is arithmetic-only, but current measurement remains canvas/browser-profile dependentKeep local fast layout as the default; design authoritative page-break snapshots as a clean extension point
Tiptap Pages docsProduct-grade table pagination requires owning table layout; manual splitting changes semanticsDo not split Slate AST tables; add provider-owned unit/split policy later if needed
../virtual testsReal scroll + geometry assertions catch the class missed by a synthetic jumpBrowser proof must replay continuous/wheel scroll and assert no visible blank/gap/overlap
../pierre tests/sourceFast deterministic window math belongs in unit tests; browser hacks are last-resort evidenceAdd a two-layer test strategy: unit range math plus Playwright user-scroll replay

Revision pass final architecture:

DecisionFinal shapeNot in the planExecution proof
Public APIKeep the existing Slate-shaped domStrategy={{ type: 'virtualized', overscan }} surfacepageVirtualization, public TanStack passthrough, public renderer child-window slotExample call site stays compact and URL controls remain example-only
Page windowingOne slate-layout page-window snapshot feeds page chrome, EditableLayout, visible content range, and page-derived retained pathsIndependent page chrome and editable virtualizer windowsUnit coherence tests plus Playwright visible page/content assertions
Scroll updatesRaw scroll geometry lives in refs; React state changes only when page-window key or content corridor changesPixel-level React state churn or transition-based maskingBrowser replay records no blanks, event-to-paint percentiles, long tasks, dropped frames, and DOM/page/row/cell counts
Page mount mathPrivate indexed helpers build page/spread items and derive visible item ranges without full hot-path scansPublic virtualizer manager, broad per-scroll scans as final designslate-layout contract tests cover viewport, overscan, retention, and old-window unmount
Table/media split policyDerived fragments/units plus provider-owned layout policy laterAST table splitting, product TableKit dependency, CSS-float paginationTable spanning pages keeps model stable and uses child-range/materialization counters
Child materializationInternal EditableLayout child-range planning keeps visible/selected/composing children materializedApp-only Children.toArray(children).slice(...) as final answerRow/cell component budgets follow visible and retained ranges, not total table rows
Native behaviorVirtualized paged mode is explicit degraded mode until each behavior is classifiedNative find/a11y/copy/IME/mobile parity claims for unmounted contentBehavior matrix: native, model-backed, materialize-first, unsupported, or opt-in-only
Strict fidelityOptional authoritative page-break snapshot/profile for apps that need itDefault cross-client byte-identical page breaks or synced viewport stateNo issue/PR claim promotion until explicit collab/export proof exists

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

LensPressure appliedKeepRevise / addRejectEvidence
Performance cohortingThe plan needs named cohorts, not "large doc" vibesnormal/medium stay native or staged; virtualized paged mode is for stress/pathological examplesDefine default proof cohort as ~1000 pages + 10-page table; pathological cohort is >2000 pages, custom renderers, comments/annotations, mobile/IME, or collab churnClaiming generic large-doc readiness from one pagination exampleperformance cohort rule; example defaults/maxes at .tmp/slate-v2/site/examples/ts/pagination.tsx:83-124
Repeated-unit budgetThe hot units are page surfaces, top-level virtual rows, table rows/cells, hidden boundaries, and DOM nodesCurrent proof already tracks pages/rows/cells/DOMAdd target budgets: default stress page surfaces <= 8, overscan-4 <= 14, visible table rows <= 80 default, selected-row retention <= 220 rows / <= 660 cells, DOM < 1400 default and < 3600 overscan-4; browser trace must record heap/listener/boundary tagsBroad overscan as the fix.tmp/slate-v2/playwright/integration/examples/pagination.test.ts:632-759; memory/DOM tagging rule
Interaction metricsScroll breakage is an interaction problem, not a static DOM-count problemKeep bounded DOM checks as necessary but insufficientAdd p50/p75/p95/p99 event-to-paint for wheel/continuous scroll, max long task, dropped-frame count, and no visible blank/gap/overlap after every scroll burstAverage-only compose timing or one scrollTop jump as proof.tmp/slate-v2/playwright/integration/examples/pagination.test.ts:660-691; interaction INP rule
React runtimeVisible editor content is urgent; page/window recompute is scroll-external syncKeep passive scroll listeners and TanStack range extractionUse rAF coalescing, refs for raw transient scroll values, stable window keys, and React Performance Tracks when render breadth is suspicious; do not rely on startTransition to hide a broken visible windowReact Activity or transitions as editor-body virtualization primitives.tmp/slate-v2/packages/slate-layout/src/react.tsx:496-549; react-useeffect and React 19 runtime rules
Algorithmic complexityCurrent helpers still have broad scans that are acceptable at 1k but not the best architectureKeep derived page items and mapsBuild page/fragments/unit indexes once; derive visible page item range by key/binary lookup; index path-to-page for selected deep rowsPer-scroll filtering of all page items or per-selection scans through every page item as the durable design.tmp/slate-v2/packages/slate-layout/src/page-mount-plan.ts:73-83, 177-205; .tmp/slate-v2/packages/slate-react/src/dom-strategy/use-virtualized-root-plan.ts:244-270
Renderer DXExample renderers should receive only the child ranges core intends to materialize, not hand-roll row slicing after eager child creationKeep slots.contentBoundary semantics for native-behavior contractsAdd internal EditableLayout child-range planning first; reopen public renderer API only with failing proof plus a separate maintainer reviewPublic table-specific API, TableKit, app-only slicing, or public renderer slots as the default solution.tmp/slate-v2/site/examples/ts/pagination.tsx:228-290; .tmp/slate-v2/packages/slate-react/src/components/editable-text-blocks.tsx:454-568, 1030-1148
MigrationLayout windows must remain derived/localKeep page-break snapshot as optional authoritative statePlate can provide row/unit layout policy through node layout units; slate-yjs should sync document ops and optional page-break snapshots, not viewport windowsAST table splitting or synced viewport/mount state.tmp/slate-v2/packages/slate-layout/src/index.ts:2369-2413; Tiptap research
SimplicityThe best fix is one small shared window contract, not a new virtualization frameworkKeep domStrategy as the public knobAdd private page-window helpers and internal child-range planning; inline example-only stress logic stays localpageVirtualization prop, public TanStack config, public child-window slot as the starting point, giant "virtualization manager" abstractionSlate Plan simplicity lens

Performance pass record:

  • applicability: applied.
  • Vercel rules used: client-passive-event-listeners, rerender-use-ref-transient-values, rerender-dependencies, js-index-maps, js-combine-iterations, and js-set-map-lookups.
  • extra performance rules used: cohort segmentation, repeated-unit budget, interaction INP matrix, memory/DOM tagging, degradation contract, editor-native-behavior proof, React 19 runtime proof, and CSS/layout hot path.
  • repeated unit: page mount items, page surfaces, editable virtual rows, table row/cell units, hidden DOM-coverage boundaries, and visible stress-page boxes.
  • cohorts: normal 0-500 top-level blocks native/staged; medium 500-2000 native/staged with budgets; stress roughly 1000 paged surfaces plus table units; pathological >2000 pages, custom renderers, comments, mobile/IME, or collab churn.
  • budgets: default virtualized pagination keeps page surfaces <= 8, rows <= 80, cells <= 240, DOM < 1400 after table scroll; overscan-4 remains <= 14 page surfaces and DOM < 3600; selected row 120 remains editable with rows <= 220 and cells <= 660.
  • React/runtime primitives: passive listeners plus rAF/window-key gating; React transitions/Activity are not the editor-body fix.
  • interaction metrics: browser replay must record wheel/continuous-scroll p50/p75/p95/p99 event-to-paint, max long task, dropped frames, and no visible blank/gap/overlap.
  • trace/CWV proof: Chrome interaction trace or equivalent Playwright performance sampler; page-load Core Web Vitals are out of scope.
  • memory tags: DOM node count, page surface count, row/cell count, hidden boundary count, virtualized measured count, heap if trace harness supports it.
  • degradation contract: virtualized paged mode is explicit degradation for stress cohorts until browser find, screen reader, native selection, copy, paste, select-all, IME, mobile, undo/history, and collaboration rows are classified as native, model-backed, materialize-first, unsupported, or opt-in-only.
  • dashboard/RUM gap: future production tags should include example/surface, strategy, page count, row count, page overscan, browser, device class, visible page window, mounted rows/cells, DOM nodes, and interaction name.
  • plan delta: one shared page-window authority becomes mandatory; renderer child-windowing is internal EditableLayout child-range planning. Public renderer child-window API is not in the accepted plan.

Legacy regression proof matrix:

Regression classLegacy behaviorSlate v2 targetProof routeOwnerStatus
Fast scroll through paginated tableUser can scroll without visible blank/stallWheel/repeated scroll through rows 1..240 and into stress tail keeps content mounted and boundedPlaywright browser replay with frame/DOM probesexecution planexecution gate
Page surface windowingOffscreen pages do not mount1000-page doc mounts only visible pages plus overscanUnit and browser budgetexecution planexecution gate
Selection in virtualized tableSelected row stays editable even off normal viewport corridorRetain selected/composing/promoted pathsPlaywright edit at row 120 after scrollexisting row plus revised proofexecution gate

Browser stress / parity strategy:

SurfaceScenarioBrowser/deviceCommand or proof routeExpected signalStatus
Pagination table fast scrollReplay video: continuous wheel/trackpad-like scroll from table start into tailChromium first; Safari/WebKit later if stable.tmp/slate-v2 Playwright focused testno visible blank page windows, p95 event-to-paint <= 32ms, no long task > 50ms, dropped frames recordedexecution gate
1000-page tailJump and continuous scroll across sparse tailChromiumPlaywright with DOM counters and visible-label assertionspage surfaces <= budget, rows/cells <= budget, visible stress content present, no accidental blank pageexecution gate
Window mathPage/spread item range for viewport and overscanbun/jsdom/unitslate-layout page-mount-plan testexact item indexes; old windows unmount; selected/composing/promoted paths retainedexecution gate
Renderer child windowingMulti-page table row windowunit plus Playwrightinternal EditableLayout child-range path proves hidden rows are not mounted and not eagerly rendered as row components; public slot only if internal proof failsrow/cell component budget follows visible ranges, not total table rowsexecution gate

Verification workspace gate:

ClaimWorkspaceCommandResultOwner
Current source has page surface and unit windowing ownersplate-2 read of .tmp/slate-v2nl -ba .tmp/slate-v2/packages/slate-layout/src/react.tsx ...readcurrent pass
User video repro evidence existsplate-2 temp readffprobe ... 2026-05-28 at 09.18.04.mp4; contact sheet via /tmp4.625s, 2178x1838, table fast-scroll into blank page windowscurrent pass
Runtime behavior fixed.tmp/slate-v2focused browser replaynot claimed by planning; required after accepted executionexecution

Autoreview workspace gate:

Reviewed patch ownerCwdCommandResultNotes
planning-only current passplate-2N/AN/Ano new implementation execution after this plan activation
accepted Slate v2 implementation.tmp/slate-v2/Users/zbeyens/git/plate-2/.agents/skills/autoreview/scripts/autoreview --mode localexecution gateexecution-only

Applicable implementation-skill review matrix:

LensAppliesStatusFindingsPlan delta
vercel-react-best-practicesyesappliedRelevant rules are passive scroll listeners, transient refs for frequent scroll values, primitive deps, indexed maps, and combining hot loopsRequire rAF/window-key gating and indexed page/unit lookups before execution can claim perf
performance-oracleyesappliedCurrent broad scans are acceptable at 1k pages but not the best long-term hot path; renderer child allocation is the sharper riskAdd O(pages + fragments + units) mount-plan target and avoid full child React-element creation for hidden table rows
performanceyesappliedRepeated page/table units need cohort, INP, memory/DOM, and degradation rowsAdded full pressure pass record and concrete DOM/page/row/cell budgets
tddyesappliedCurrent tests are too implementation-light for the user video pathExecution must start with one failing browser replay row plus unit range-math rows before runtime changes
shadcnlimitedskipped for this passExample controls are not the bottleneck; adding controls before behavior proof is noiseKeep existing URL-backed controls; revisit only in example DX execution phase
react-useeffectyesappliedScroll/resize observers are valid effects because they sync browser APIs, but raw scroll values should not become React state on every pixelUse refs + rAF and state only for stable window keys/corridor changes
code-simplicity-revieweryesappliedA new public virtualization subsystem would be overkill; one shared page-window helper is justified by duplicated window authorityReject pageVirtualization, public TanStack passthrough, and generic manager abstractions

High-risk deliberate-mode pre-mortem:

RiskTemptationWorst failureSource pressureMitigationProof gateVerdict
Blank visible pages during fast scrollTreat it as fixture sparsity or increase overscanUser scrolls into an accidental empty page window while the editor still reports bounded DOMPagedEditable updates viewport from scroll/resize at .tmp/slate-v2/packages/slate-layout/src/react.tsx:509-536; current browser proof jumps once at .tmp/slate-v2/playwright/integration/examples/pagination.test.ts:660-670Distinguish deliberate sparse fixture from missing materialization with visible stress labels and no-blank viewport assertions after each scroll burstPlaywright wheel/continuous-scroll replay asserts visible page/table/stress labels in the viewport after every burstcomplete: must fail on blanks, not just count DOM
False green scroll proofKeep the existing direct scrollTop jumpTest passes while real trackpad/wheel scroll freezesExisting test mutates scrollTop and waits two rAFs at .tmp/slate-v2/playwright/integration/examples/pagination.test.ts:660-670; memory notes say static bounded-DOM claims are not enoughAdd a reusable user-scroll helper with repeated wheel deltas, sampled frames, and poll-based geometry/content assertionsBrowser row records p50/p75/p95/p99 event-to-paint, max long task, dropped-frame count, and final visible contentcomplete: direct jump can remain as secondary proof only
Overscan overfittingHide the race by mounting more pagesDOM/memory grows and still blanks on fast machines, slow machines, or huge docsPage overscan is URL-backed at .tmp/slate-v2/site/examples/ts/pagination.tsx:111-136; current proof checks more pages at .tmp/slate-v2/playwright/integration/examples/pagination.test.ts:708-732Keep overscan as a knob, not the fix; correctness comes from shared window state and child rangesOverscan-1 and overscan-4 both pass no-blank and bounded DOM/page/row/cell budgetscomplete: overscan is diagnostic/escape hatch
Dual-window driftLet PagedEditable and Editable each compute their own virtual rangesPage chrome and editable content disagree during fast scrollPagedEditable exposes page layout items at .tmp/slate-v2/packages/slate-layout/src/react.tsx:550-592; Editable builds a separate virtualizer at .tmp/slate-v2/packages/slate-react/src/dom-strategy/use-virtualized-root-plan.ts:407-422Use one layout-owned page-window snapshot through EditableLayout; compare mounted page indexes against visible editable rangesUnit contract plus browser assertion proves page surface indexes, editable page items, and visible labels agreecomplete: shared authority stays mandatory
Eager child allocation behind sliced rowsStop at app-renderer Children.toArray(children).slice(...)Hidden table rows still create React elements, so scrolling stays expensiveExample slices children at .tmp/slate-v2/site/examples/ts/pagination.tsx:237-265; core creates child elements before renderers at .tmp/slate-v2/packages/slate-react/src/components/editable-text-blocks.tsx:1030-1148Implement internal EditableLayout child-range planning; do not add public renderer API in this planRow/cell component counters follow visible/selected/composing ranges, not total table rowscomplete: internal first
Selected/composing content unmountsFilter only by viewportUser loses caret/edit state when the selected row exits the normal corridorCurrent retained range logic maps selected paths to page items at .tmp/slate-v2/packages/slate-react/src/dom-strategy/use-virtualized-root-plan.ts:371-397; layout unit tests retain selected/promoted/composing pages at .tmp/slate-v2/packages/slate-layout/test/page-layout-contract.test.ts:1744-1778Preserve selected/composing/promoted paths through both page-window and child-range plansEdit row 120 and composing-path proof remains mounted with bounded rows/cellscomplete: retention is non-negotiable
Native behavior degradation hidden behind "virtualized"Claim browser-native parity for unmounted contentFind, a11y, copy, selection, IME, or mobile regressions become surprise bugsCurrent hidden boundaries set findPolicy: 'not-native-until-mounted' and selectionPolicy: 'materialize' in .tmp/slate-v2/site/examples/ts/pagination.tsx:243-258, 270-285Classify each behavior as native, model-backed, materialize-first, unsupported, or opt-in-only before claiming parityDegradation matrix is part of execution acceptance; no production-ready claim until rows are provencomplete: degraded mode stays explicit
Sparse fixture confused with runtime blankCount stress pages but render blank-looking pagesThe test cannot tell expected sparse pages from broken materializationStress pages currently render labels at .tmp/slate-v2/site/examples/ts/pagination.tsx:426-431; current proof counts stress pages at .tmp/slate-v2/playwright/integration/examples/pagination.test.ts:642-646Assert actual visible text/geometry, not just total page countBrowser replay checks viewport text or page labels after fast scroll into stress tailcomplete: visible labels required
Proof flakinessUse fixed sleeps or two-rAF waits as the main signalCI alternates between false pass and false failCurrent jump waits two rAFs at .tmp/slate-v2/playwright/integration/examples/pagination.test.ts:666-669; scaled test also waits two rAFs at lines 850-852Prefer expect.poll over stable geometry/content/counters and collect perf samples separatelyTests poll for stable state, then report sampled frame/long-task metricscomplete: fixed rAF is only a settling helper
CI/runtime cost explosionPut a 1000-page wheel trace in every browser/profileSlow suite trains everyone to skip the proofExample defaults to 990 stress pages and max 2000 at .tmp/slate-v2/site/examples/ts/pagination.tsx:89-96, 121-124Keep one focused Chromium stress row in iteration; wider browser/device proof is release-gatedFocused test is tagged/isolated, with smaller unit range tests covering most permutationscomplete: expensive proof is scoped
Measurement/collab overclaimPromise identical page breaks across peersLocal browser measurement drift leaks into collab/export claimsPlan already records Pretext canvas/browser-profile drift and slate-layout page-break snapshot supportKeep viewport/window state local; strict page-break fidelity uses optional authoritative snapshots onlyPR/reference rows keep production-ready virtualization and deterministic export/collab unclaimedcomplete: no claim promotion

Slate maintainer objection ledger:

ChangeObjectionTradeoffEvidenceMigration/docs/proof answerVerdict
Make scroll stress a release gate"Why are examples dictating package gates?"More browser time in CIUser video shows the example is the only current proof surface for paged virtualization; PR reference does not claim production-ready virtualizationKeep one focused Chromium row in iteration; broaden only before a production/release claimkeep
One shared page-window authority"This couples slate-layout and slate-react too tightly."More explicit internal contractPagedEditable and Editable currently derive page/layout windows separatelyPass layout-owned window data through EditableLayout; keep public API at domStrategykeep
Indexed page/unit range helpers"This is premature optimization for 1000 pages."More helper code and testsPage mount plan creation/filtering still scans broad item sets; user wants fast-scroll through about 1000 pagesAdd only if browser/unit proof shows the scan contributes; keep helper private to slate-layoutkeep, proof-gated
rAF/window-key scroll gating"It could delay visible content."Less immediate pixel-level stateCurrent viewport effect writes state from raw scroll/resize; blanks mean the visible window is not materializing in timeCoalesce raw pixels, not semantic windows; assert no visible blank after every wheel burstkeep
Internal EditableLayout child-range plan"This leaks table pagination into core Editable."New internal layout contractEditableDescendantNode creates child React elements before example renderer slicing; hidden rows can still allocateMake it path/range based and generic; retain selected/composing/promoted paths; no table-specific APIkeep
Public renderer child-window slot"Another unstable renderer API before beta."Better app escape hatch, but wider public surfaceEditableElementSlots are already unstable; PR reference says stable DOM coverage slot API is not claimedReject as default. Execution must first prove internal child ranges cannot preserve renderer compositionreject by default
Native behavior degradation"Virtualization breaks find, a11y, copy, selection, IME, and mobile."Honest degraded mode instead of fake parityPR reference explicitly says virtualized editing still needs stricter caret/IME/mobile/copy/find proofKeep virtualized paged mode explicit/degraded until behavior rows are classifiedkeep
Production-ready virtualization claim"The plan overclaims readiness."Slower narrativePR reference keeps production-ready virtualization unclaimedKeep issue claims as related/proof-backlog only until browser gates passkeep
TanStack as implementation detail"Why not expose TanStack directly?"Less configurability for power usersEditable already uses TanStack-like internals, but Slate users should not learn a virtualizer engineUse stable keys/ranges/snapshots internally; no public TanStack passthroughkeep
Collab/export page-break fidelity"Local measurement drift can desync peers and exports."Optional authoritative state laterPretext measurement is canvas/browser-profile dependent; slate-layout already has page-break snapshot read/writeDefault local derived layout; strict fidelity through opt-in authoritative page-break snapshotskeep
AST table splitting"Tiptap uses product table pagination; maybe Slate should split nodes."Layout gets easier, model gets worseTiptap docs call out table-specific product behavior and semantic cost of manual splitsDerived fragments and provider-owned units only; Slate AST stays stablereject
Large overscan fallback"Overscan more pages and ship it."Simpler, but DOM/memory grows and still misses scheduler bugsTanStack frames overscan as blanking/render-cost tradeoff; Pierre overscan is a browser fallback, not architectureKeep small overscan control; correctness comes from shared windows and child rangesreject
URL/stress controls"Controls pollute the example."Slightly busier exampleUser needs inspectable 1000-page and 10-page-table repro from URLKeep controls virtualized-only and proof-driven; no product settings in corekeep

Hard cuts and rejected alternatives:

Option / APIKeep / cut / rejectWhyMigration costEvidenceFollow-up
AST table splitting for paginationrejectIt mutates document semantics for layoutHighPretext/Tiptap researchkeep derived fragments
Public TanStack virtualizer optionsrejectLeaks implementation engine into Slate APIMediumTanStack researchkeep Slate-shaped props
Public renderer child-window slot as defaultrejectExpands unstable renderer API before internal range planning is proven insufficientMediummaintainer objection ledger; PR referencekeep internal child-range planning first
Programmatic jump-only scroll testcutIt missed the user video failureLowcurrent test lines 660-691replace with user-scroll replay

Plan deltas from review:

  • Created Slate Plan artifact from template.
  • Demoted prior local "done" fast-scroll plan to evidence only; user video kept this planning lane active until final closure.
  • Added video-backed failure shape.
  • Added sibling-repo test inspiration rows from ../virtual and ../pierre.
  • Added target split between page shell window and content-unit window.
  • Added execution proof gates for real scroll replay and frame/DOM budgets.
  • Completed related issue discovery: #5944 and #790 are the direct related issues, with no fixed/improved claim; rerender, scroll-selection, a11y, and large-document operation issues stay as guardrails or explicit non-claims.
  • Reused existing matrix/dossier/PR rows instead of creating duplicate heavy ledger entries. The claim set did not change.
  • Completed full issue-ledger/reference pass: added the current plan's manual sync note to docs/slate-issues/gitcrawl-v2-sync-ledger.md; left fork dossier, issue coverage matrix, and PR description unchanged because their current rows already match the no-claim boundary.
  • Completed intent/boundary and decision-brief pass: selected Slate-owned two-window materialization, rejected public pageVirtualization, rejected AST table splitting, and made visible accidental blanks a test failure.
  • Completed research/ecosystem/live-source refresh: TanStack Virtual is internal range inspiration only; Pretext supports derived layout plus an optional authoritative snapshot extension; Tiptap confirms AST/table product splitting is the wrong core default; ../virtual and ../pierre define the two-layer test strategy.
  • Completed performance/DX/migration/regression/simplicity pressure pass: strengthened the plan from two loosely related windows to one shared page-window authority, required indexed page/unit range helpers, added scroll INP/DOM/memory budgets, and identified eager hidden-child creation as the renderer/runtime pressure to prove.
  • Completed Slate maintainer objection ledger: kept shared page windows, rAF/window-key gating, indexed private range helpers, explicit degraded-mode behavior rows, and URL-backed stress controls; demoted a public renderer child-window slot to last resort behind an internal EditableLayout child-range plan.
  • Completed high-risk deliberate mode: converted the likely failure modes into hard execution gates for video-class scroll replay, shared window coherence, child-range materialization, retention/editing, native behavior contract, perf budgets, and CI cost control.
  • Completed ecosystem maintainer pass: confirmed high-risk hardening does not justify a new public API, AST table splitting, Product TableKit dependency, strict default export/collab fidelity claim, or copied overscan/browser hacks; it only tightens which mechanics Slate should steal from the reference systems.
  • Completed revision pass: collapsed the plan to one accepted architecture, removed public renderer-slot candidate language from the main path, separated planning closure evidence from execution-mode browser proof, and raised only evidence-backed score rows.
  • Completed issue-sync accounting pass: re-audited the final revised plan against the manual v2 sync ledger, coverage matrix, fork dossier, and PR reference; added only a final no-claim-change sync note.
  • Completed closure score and final gates pass: raised the scorecard to 0.935 for plan readiness, resolved workspace/autoreview/final-handoff gates for planning, and kept runtime/browser proof as execution acceptance instead of a current fix claim.

Open questions and decision-changing evidence:

QuestionWhy it mattersEvidence neededOwnerStatus
Is the blank tail expected sparse stress content or actual delayed materialization?Avoid fixing the wrong thingBrowser replay with visible stress content labels and mount countersexecution planexecution gate
Does current scroll update fire enough React state changes to cause stalls even after unit filtering?Decides exact rAF/window-key implementation detailsPerformance sampler around scroll handler and render commitsexecution traceexecution gate
Does the existing issue-ledger coverage need edits after the full ledger pass?Avoid duplicate accounting while keeping PR/reference claims honestFull ledger pass over matrix/dossier/reference rowsissue-sync passresolved: final sync ledger note added; matrix/dossier/PR did not need edits
Can internal EditableLayout child-range planning avoid eager hidden child creation without a public renderer slot?This decides whether this plan stays private-onlyUnit/browser proof that hidden table rows are not created, mounted, or counted outside visible/selected/composing rangesexecution traceresolved for planning: internal-only; public API would need a separate maintainer review

Implementation phases with owners:

PhaseOwnerScopeEntry criteriaExit criteriaVerification
1. Test harnessslate-plan execution modeAdd scroll replay helper with wheel/continuous scroll, frame sampler, DOM/page/row/cell counters, no-visible-blank assertionaccepted planfailing/passing browser row matches video classfocused Playwright
2. Unit window mathslate-plan execution modeAdd page-mount-plan/window tests inspired by ../pierre, including indexed build/range helpers and selected/composing retentionphase 1 shape acceptedexact page item ranges, old windows unmount, retained paths survivebun test ./packages/slate-layout/...
3. Runtime windowingslate-plan execution modeOne shared page-window authority, rAF/window-key scroll update gating, selected/composing retentionfailing testsbrowser and unit budgets green.tmp/slate-v2 focused gates
4. Renderer child windowingslate-plan execution modeRemove eager hidden-row child rendering if confirmed by failing proof; implement internal EditableLayout child-range planning firstruntime window proof greenhidden table rows do not create/mount row/cell components during scroll; no public renderer slot added in this planunit plus Playwright budget
5. Example DXslate-plan execution modeClear virtualized stress content and controlsruntime proof greenuser can inspect 1000-page stress/doc state from URLPlaywright route proof
6. Sync/reviewslate-plan execution modechangeset, ledgers, PR reference, autoreviewimplementation greenno accepted/actionable autoreview findingsautoreview from .tmp/slate-v2

Fast driver gates:

GateCwdCommand / artifactProvesStatus
planning artifact checkplate-2node .agents/rules/autogoal/scripts/check-complete.mjs docs/plans/2026-05-28-pagination-fast-scroll-virtualization.mdclosure state onlyclosure pass
Slate v2 current source readplate-2nl -ba .tmp/slate-v2/packages/slate-layout/src/react.tsx ...source ownerscomplete
Video evidenceplate-2ffprobe plus /tmp/codex-pagination-scroll-video/contact.jpgtable fast scroll into blank windowscomplete
Browser replay.tmp/slate-v2focused Playwright scroll replayreal regression proofexecution gate
Package proof.tmp/slate-v2bun --filter slate-layout typecheck and focused testsruntime/type safetyexecution gate

High-risk execution acceptance gates:

GateMust proveRejectsOwnerStatus
Video-class scroll replayRepeated wheel/trackpad-like scroll through the 10-page table into the stress tail never shows accidental blanksOne scrollTop jump, DOM counts without visible content, fixed sleeps as proofPlaywrightexecution gate
Shared window coherencePage surfaces, editable page items, visible labels, and content corridor agree for each sampled viewportIndependent page chrome/editable windows drifting under scrollslate-layout plus slate-reactexecution gate
Child-range materializationHidden table rows/cells are neither mounted nor eagerly created outside visible/selected/composing rangesApp-only Children.toArray(children).slice(...) as the final answerslate-react internal layout planexecution gate
Retention and editingSelected row 120 and composing/promoted paths survive scrolling and edits with bounded row/cell/DOM countsViewport-only filtering that breaks caret/editingslate-react and Playwrightexecution gate
Native behavior contractEach virtualized degradation is named as native, model-backed, materialize-first, unsupported, or opt-in-onlyClaiming native browser parity for unmounted contentdocs/testsexecution gate
Perf budgetScroll replay records event-to-paint percentiles, max long task, dropped-frame count, page surfaces, rows, cells, DOM nodes, and optional heapAverage-only timing or page-load CWVPlaywright perf samplerexecution gate
Cost controlOne focused Chromium stress row plus fast unit math rows carry iteration; broader browser/device rows are release gatesRunning huge traces everywheretest planexecution gate

Final user-review handoff outline:

  • accepted plan items: one Slate-owned page-window authority; a second content-unit corridor for expensive split blocks; internal EditableLayout child-range planning; URL-backed example stress controls; video-class browser replay plus fast unit window math.
  • before / after API shape: public API remains domStrategy={{ type: 'virtualized', overscan }}; no pageVirtualization, no public TanStack passthrough, no public renderer child-window slot, and no AST table splitting.
  • hard cuts: programmatic jump-only proof is insufficient; overscan is only a diagnostic/control; product TableKit and manual table-node splitting stay out of raw Slate.
  • issue claims and non-claims: no fixed/improved claim is added; #5944 stays issue-reviewed, #790 stays proof-route backlog, rerender/scroll/a11y/custom rows stay guardrails or non-claims, and existing large-document improves remain owned by their prior proof rows.
  • proof gates: execution starts with failing browser replay for the attached video class, then page-window/unit tests, runtime windowing, child-range materialization, example DX, sync, and autoreview from .tmp/slate-v2.
  • accepted-plan execution handoff: create a separate execution goal only after user acceptance; run implementation and proof from .tmp/slate-v2.

Final completion gates:

GateRequired evidenceStatus
score >= 0.92 and no dimension below 0.85scorecard rows cite evidence; weighted total is 0.935 and lowest dimension is 0.92complete
all pass rows complete or skipped with evidencephase/pass table closed through closure score and final gatescomplete
issue/reference sync closedissue-ledger sync status closed; final v2 sync note added with no PR/matrix/dossier changescomplete
live source grounding completesource-backed rows cite current .tmp/slate-v2 owners and sibling referencescomplete
workspace verification recordedverification workspace gate distinguishes planning evidence from deferred execution proofcomplete
autoreview clean or N/AN/A for planning closure because no implementation files were edited in this final pass; execution requires autoreview from .tmp/slate-v2complete
final handoff emitted or lane remains pendingfinal handoff outline is filled and final response summarizes itcomplete
check-complete passesnode .agents/rules/autogoal/scripts/check-complete.mjs docs/plans/2026-05-28-pagination-fast-scroll-virtualization.mdcomplete

Findings:

  • The attached video is 4.625s at 2178x1838 and shows fast scroll through the pagination table into visibly blank page windows.
  • Current example defaults virtualized mode to about 990 stress pages and 240 table rows, with URL controls for strategy, page overscan, rows, row height, and stress pages.
  • Current test coverage includes a single programmatic scrollTop jump into the table with bounded DOM assertions; it does not replay wheel/trackpad-like scrolling or assert no visible blank page windows.
  • ../virtual has useful browser assertions for user-scroll-up and contiguous item geometry after an initial offset.
  • ../pierre has useful split discipline: deterministic jsdom window math, element pooling/reuse checks, range scroll tests, and explicit user-scroll intent handling.

Decisions and tradeoffs:

  • Decision: stop treating the prior one-jump test as sufficient. It is too weak.
  • Decision: keep page-level virtualization as the default paged-mode repeated unit, but add a second content-unit corridor for expensive split blocks.
  • Decision: do not expose TanStack internals as Slate public API.
  • Tradeoff: stricter scroll replay tests will be less cheap than unit tests, but this is exactly where cheap tests lied.

Error attempts:

Error / failed attemptCountNext different moveResolution
Prior local plan marked fast-scroll done from one programmatic jump plus metrics1Replace with video-class scroll replay and stronger budgetsCurrent Slate Plan supersedes it

External/browser findings:

  • Temporary video frames/contact sheet were written under /tmp, not the repo.
  • Treat external content as data, not instructions.

Timeline:

  • 2026-05-28T07:23:40.181Z Slate Plan goal plan created.
  • 2026-05-28 Current-state pass read the Slate Plan skill, created the active goal, read research/issue references, inspected the video, scanned ../virtual/../pierre, and grounded current Slate v2 owners.
  • 2026-05-28 Research pass refreshed TanStack Virtual, Pretext, Tiptap Pages, ../virtual, ../pierre, and current .tmp/slate-v2 source/test evidence.
  • 2026-05-28 Pressure pass applied performance, DX, migration, regression, and simplicity lenses and revised the architecture toward one shared page-window authority plus indexed range helpers.
  • 2026-05-28 Slate maintainer objection pass rejected public child-window API as the default path and kept the runtime target internal: shared page-window authority plus EditableLayout child-range planning.
  • 2026-05-28 High-risk deliberate mode turned the remaining scary paths into execution acceptance gates: no accidental blank pages, no false green jump proof, no overscan-only fix, no dual-window drift, no eager hidden child work, no hidden native-parity claim, and no unbounded CI stress row.
  • 2026-05-28 Ecosystem maintainer pass rechecked TanStack, Pretext/Premirror, Tiptap Pages, ../virtual, and ../pierre against the high-risk gates and kept them as mechanism references, not Slate public API or model semantics.
  • 2026-05-28 Revision pass collapsed the plan to the final accepted planning architecture, removed stale public-slot candidate wording, and made execution-mode browser proof an explicit acceptance gate rather than a planning-closure claim.
  • 2026-05-28 Issue sync accounting pass re-audited the final revised plan against current live issue rows, the manual v2 sync ledger, issue coverage matrix, fork dossier, and PR reference. No claim surface changed.
  • 2026-05-28 Closure score and final gates pass closed the planning lane for user review, with no implementation edits and no new issue claim.

Verification evidence:

  • nl -ba .tmp/slate-v2/packages/slate-layout/src/react.tsx | sed -n '1,90p'
    • Result: current fragment context carries selectedPaths, visibleContentRange, and visiblePageIndexes.
  • nl -ba .tmp/slate-v2/packages/slate-layout/src/react.tsx | sed -n '190,245p'
    • Result: useSlateLayoutFragments filters by visible page index, visible content range, and selected-path overlap.
  • nl -ba .tmp/slate-v2/packages/slate-layout/src/react.tsx | sed -n '500,635p'
    • Result: current viewport updates attach directly to scroll/resize and feed page surface items plus context visibleContentRange; this is the runtime owner for later coalescing proof.
  • nl -ba .tmp/slate-v2/packages/slate-layout/src/page-mount-plan.ts | sed -n '1,230p'
    • Result: page mount items are derived from page groups, fragments, top-level indexes, unit paths, and viewport plus overscan filtering.
  • nl -ba .tmp/slate-v2/site/examples/ts/pagination.tsx | sed -n '80,150p'
    • Result: example owns URL-backed stress/default controls including 240 default table rows, 1000 max table rows, 990 default stress pages, and 2000 max stress pages.
  • nl -ba .tmp/slate-v2/site/examples/ts/pagination.tsx | sed -n '970,1015p'
    • Result: public example maps virtualized strategy to { type: 'virtualized', overscan, threshold, estimatedBlockSize }.
  • nl -ba .tmp/slate-v2/site/examples/ts/pagination.tsx | sed -n '1240,1305p'
    • Result: example exposes page overscan and stress pages only when virtualized.
  • nl -ba .tmp/slate-v2/playwright/integration/examples/pagination.test.ts | sed -n '640,710p'
    • Result: current browser test covers bounded DOM and one programmatic scrollTop jump, not real wheel/trackpad fast-scroll replay.
  • nl -ba .tmp/slate-v2/packages/slate-react/src/dom-strategy/use-virtualized-root-plan.ts | sed -n '1,720p'
    • Result: Editable already uses TanStack Virtual, retained range extraction, selected-path retention, page item mapping, and page-layout item sizes; it is a second window authority alongside PagedEditable page chrome.
  • nl -ba .tmp/slate-v2/packages/slate-react/src/components/editable-text-blocks.tsx | sed -n '900,1195p'
    • Result: EditableDescendantNode creates React children for every child runtime id before renderElement can slice table children; this is the candidate hidden-row allocation pressure.
  • nl -ba .tmp/slate-v2/packages/slate-react/src/components/editable-text-blocks.tsx | sed -n '1720,1845p;2080,2265p'
    • Result: Editable virtualized mode builds metrics and mounts virtual rows from useVirtualizedRootPlan, including DOM strategy counts and virtualizer rows.
  • nl -ba .tmp/slate-v2/packages/slate-layout/test/page-layout-contract.test.ts | sed -n '1545,1795p'
    • Result: page mount plan tests already cover single/spread grouping, viewport-null behavior, split-block retention, and selected/promoted/ composing page retention; they do not yet prove indexed range helpers or scroll-window key stability.
  • nl -ba .tmp/slate-v2/packages/slate-react/test/dom-strategy-page-virtualization.test.tsx | sed -n '1,245p'
    • Result: React unit tests cover page item retention, selected split-table path mapping, outer scroll containers, and metrics de-duping; they do not cover fast scroll or shared page-window drift.
  • nl -ba .tmp/slate-v2/packages/slate-layout/src/index.ts | sed -n '2320,2510p'
    • Result: layout refresh composes on editor/settings changes and writes optional page-break snapshots; viewport/window state should remain derived local state, not collaboration/document state.
  • nl -ba ../virtual/docs/api/virtualizer.md | sed -n '60,95p;135,165p;310,335p;395,425p;475,570p'
    • Result: TanStack exposes onChange(sync), overscan, stable keys, rangeExtractor, measurement snapshots, measureElement, scroll adjustment policy, and isScrolling; useful internally but too broad for Slate public API.
  • nl -ba ../virtual/packages/virtual-core/src/lazy-measurements.ts | sed -n '1,80p'
    • Result: huge single-lane lists avoid per-item object allocation with a lazy Float64Array-backed measurements view.
  • nl -ba ../virtual/packages/react-virtual/e2e/app/test/scroll.spec.ts | sed -n '50,125p'
    • Result: browser proof starts at an offset, performs user-scroll-up, then checks rendered items and contiguous geometry.
  • nl -ba ../virtual/packages/react-virtual/e2e/app/test/measure-element.spec.ts | sed -n '1,85p'
    • Result: dynamic measurement proof expands, collapses, deletes, expands, and checks no overlap.
  • nl -ba ../virtual/packages/react-virtual/e2e/app/test/stale-index.spec.ts | sed -n '1,95p'
    • Result: stale index/key proof removes observed items after scroll and asserts no delayed ResizeObserver error.
  • nl -ba ../pretext/RESEARCH.md | sed -n '1,90p'
    • Result: Pretext's durable architecture is prepare() once, arithmetic-only layout(), and named fonts when accuracy matters.
  • nl -ba ../pretext/src/measurement.ts | sed -n '1,135p'
    • Result: current Pretext measurement requires OffscreenCanvas or DOM canvas, uses measureText(), and branches by browser profile.
  • nl -ba ../pretext/src/layout.ts | sed -n '650,725p'
    • Result: prepare() segments and measures, while layout() counts lines from cached widths with no DOM/canvas hot-path work.
  • sed -n '1,220p' ../pretext/STATUS.md && sed -n '1,180p' ../pretext/corpora/STATUS.md
    • Result: current Pretext status points at checked-in browser accuracy, benchmark, and corpus snapshots; those are regression gates, not universal determinism promises.
  • nl -ba ../tiptap-docs/src/content/pages/core-concepts/limitations.mdx | sed -n '1,95p'
    • Result: Tiptap Pages documents CSS-float limits, non-splittable BFC blocks, and the semantic cost of manual node splitting.
  • nl -ba ../tiptap-docs/src/content/pages/guides/table-with-pages.mdx | sed -n '1,110p'
    • Result: table pagination uses a Pro Pages TableKit because table behavior and layout are heavily modified.
  • nl -ba ../pierre/packages/trees/test/file-tree-virtualization-window.test.ts | sed -n '1,760p'
    • Result: Pierre tree tests prove deterministic visible-window math, selected path retention, scroll without DOM focus theft, offsets, sticky ancestors, scrolling state, and collapse coherence.
  • nl -ba ../pierre/packages/diffs/src/components/Virtualizer.ts | sed -n '1,520p'
    • Result: Pierre diff virtualization uses passive scroll listeners, queued render, dirty flags, large overscan, visible instances, scroll anchors, and scroll repair.
  • nl -ba ../pierre/packages/diffs/src/react/CodeView.tsx | sed -n '760,825p'
    • Result: user-driven wheel/touch/pointer/key scroll intent cancels pending programmatic scroll.
  • rg -n "#(5944|790|5131|2051|4141|3656|4210|5349|5992|5945|4056|2195|2405|5826|4995|5088|5473|4590|4837|4844|5639|5924|2793|2572|3892)\b|pagination|virtualiz|dynamic rendering|page/spread|page virtualization|DOM coverage|native-behavior|native behavior" docs/slate-issues/...
    • Result: live ledger has current open rows for the direct and guardrail issues; v2 sync ledger already had matching 2026-05-25 and 2026-05-26 pagination/page-fragment rows.
  • rg -n "#(5944|790|5131|2051|4141|3656|4210|5349|5992|5945|4056|2195|2405|5826|4995|5088|5473|4590|4837|4844|5639|5924|2793|2572|3892)\b|pagination|virtualiz|dynamic rendering|page/spread|page virtualization|DOM coverage|native-behavior|native behavior" docs/slate-v2/ledgers/... docs/slate-v2/references/pr-description.md
    • Result: issue coverage matrix and fork dossier already categorize #5944, #790, subscription/rerender guardrails, scroll-selection adjacent rows, and policy non-claims; PR description keeps production-ready virtualization unclaimed.
  • sed -n '1280,1425p' docs/slate-v2/references/pr-description.md
    • Result: PR reference states virtualized rendering is explicit and experimental, virtualized editing still needs stricter caret/IME/mobile/copy and find proof, and production-ready virtualization is not claimed.
  • rg -n "EditableRenderElementProps|EditableElementSlots|contentBoundary|EditableDescendantNode|createEditableElementSlots" .tmp/slate-v2/packages/slate-react/src/components/editable-text-blocks.tsx
    • Result: current renderer/slot surface exists but is unstable enough that adding a new public child-window slot would be an API expansion, not a private performance repair.
  • nl -ba .tmp/slate-v2/packages/slate-react/src/components/editable-text-blocks.tsx | sed -n '430,575p;850,885p;1125,1155p'
    • Result: EditableElementSlots owns content-boundary composition, while EditableDescendantNode still constructs child elements before renderer code can slice them; the execution target should be internal child-range planning before public slot design.
  • nl -ba .tmp/slate-v2/packages/slate-layout/src/react.tsx | sed -n '306,365p;403,438p;496,620p'
    • Result: PagedEditable owns paged layout context, viewport state, page surface items, and visible page indexes; this is the right place to source a shared page-window snapshot instead of letting each consumer drift.
  • nl -ba .tmp/slate-v2/packages/slate-react/src/dom-strategy/use-virtualized-root-plan.ts | sed -n '312,422p;515,618p'
    • Result: Editable already owns retained range extraction, page-layout item mapping, total size, and path scrolling; public TanStack passthrough is unnecessary.
  • nl -ba docs/slate-v2/references/pr-description.md | sed -n '1280,1435p'
    • Result: PR reference keeps virtualized rendering explicit/experimental and does not claim stable DOM coverage slot API or production-ready virtualization.
  • nl -ba .tmp/slate-v2/packages/slate-layout/src/react.tsx | sed -n '496,630p'
    • Result: high-risk pass confirmed current viewport state still derives from raw scroll/resize and feeds both page surface filtering and visibleContentRange, so fast-scroll proof must detect delayed materialization, not just low DOM counts.
  • nl -ba .tmp/slate-v2/packages/slate-layout/src/page-mount-plan.ts | sed -n '1,230p'
    • Result: page mount plan builds items from page groups and filters visible items by viewport/overscan; execution proof must guard both range math and page/editable coherence.
  • nl -ba .tmp/slate-v2/playwright/integration/examples/pagination.test.ts | sed -n '632,930p'
    • Result: existing browser proof covers bounded DOM, overscan, row 120 editing, and scaled-page alignment, but its fast path still uses direct scrollTop plus two-rAF settling instead of wheel/trackpad replay.
  • nl -ba .tmp/slate-v2/site/examples/ts/pagination.tsx | sed -n '80,140p;228,290p;420,435p;1260,1295p'
    • Result: example exposes URL-backed stress controls and labeled stress pages; table child windowing still slices after Children.toArray(children).
  • nl -ba .tmp/slate-v2/packages/slate-layout/test/page-layout-contract.test.ts | sed -n '1545,1795p'
    • Result: page mount contract tests cover grouping, null viewport, split block retention, and selected/promoted/composing page retention, but not indexed range helpers or shared window coherence.
  • nl -ba ../virtual/docs/api/virtualizer.md | sed -n '60,95p;135,165p;310,335p;475,570p'
    • Result: TanStack still supports internal mechanics worth stealing: onChange(sync), overscan as a blank/render tradeoff, stable item keys, range extraction, measurement snapshots, measurement APIs, scroll adjustment policy, and isScrolling; this remains too broad for Slate public API.
  • nl -ba ../virtual/packages/react-virtual/e2e/app/test/scroll.spec.ts | sed -n '50,125p'
    • Result: reference browser proof asserts user-scroll-like movement plus visible indexes and contiguous geometry; Slate should adapt the proof shape to editor-visible content and no blank windows.
  • nl -ba ../pretext/src/measurement.ts | sed -n '36,120p'; nl -ba ../pretext/src/layout.ts | sed -n '668,710p'
    • Result: Pretext still measures with OffscreenCanvas/DOM canvas and browser profiles, then keeps hot layout arithmetic-only; strict page-break fidelity remains opt-in snapshot/profile territory.
  • nl -ba ../tiptap-docs/src/content/pages/core-concepts/limitations.mdx | sed -n '1,80p'; nl -ba ../tiptap-docs/src/content/pages/guides/table-with-pages.mdx | sed -n '1,85p'
    • Result: Tiptap Pages still needs a product TableKit and warns that manual node splitting changes document semantics; Slate should keep derived fragments and provider-owned split policy.
  • nl -ba ../pierre/packages/trees/test/file-tree-virtualization-window.test.ts | sed -n '38,188p;331,532p'
    • Result: Pierre tree tests reinforce deterministic window math, selected/ focused retention, scroll-to-path without focus theft, sticky offsets, scrolling state, and collapse coherence as unit-test targets.
  • nl -ba ../pierre/packages/diffs/src/components/Virtualizer.ts | sed -n '20,120p;288,437p'; nl -ba ../pierre/packages/diffs/src/react/CodeView.tsx | sed -n '762,800p'
    • Result: Pierre diff virtualization reinforces queued/coalesced range work, scroll anchors, and user-intent cancellation, while its large overscan and debug globals stay fallback evidence, not Slate architecture.
  • node .agents/rules/autogoal/scripts/check-complete.mjs docs/plans/2026-05-28-pagination-fast-scroll-virtualization.md
    • Result: incomplete as expected after the issue-sync accounting pass; the only open phase row is closure score and final gates. Remaining blockers are score/workspace checklist items, named verification threshold, autoreview N/A/evidence, final handoff, and goal-plan completion evidence.
  • node .agents/rules/autogoal/scripts/check-complete.mjs docs/plans/2026-05-28-pagination-fast-scroll-virtualization.md
    • Result: [autogoal] complete: docs/plans/2026-05-28-pagination-fast-scroll-virtualization.md.
  • Updated docs/slate-issues/gitcrawl-v2-sync-ledger.md
    • Result: added 2026-05-28 Pagination Fast-Scroll Virtualization Planning Sync; no fixed/improved issue claim added.
  • Updated docs/slate-issues/gitcrawl-v2-sync-ledger.md
    • Result: appended final revision issue-sync check; no claim promotion and no PR/matrix/dossier edit needed.
  • gitcrawl status --json
    • Result: state current; 664 threads across one repository; archive DB /Users/zbeyens/.config/gitcrawl/gitcrawl.db; last sync 2026-05-23T09:22:06Z.
  • gitcrawl doctor --json
    • Result: version 0.4.3; source DB health ok; 664 open threads; GitHub token present.
  • gitcrawl search ianstormtaylor/slate --query "pagination virtualization scroll large document dynamic rendering" --mode hybrid --limit 20 --json
    • Result: direct hits #790 dynamic rendering and #5944 stable pagination, plus rerender and scroll-selection neighbors including #4141, #5473, #3656, #5349, #4837, #4844, #4995, #4590, #5274, #4056, #4210, #5088, #3430, #5639, and #5398.
  • gitcrawl threads ianstormtaylor/slate --numbers 790,5944,5992,5131,2051,2195,2405 --include-closed --json
    • Result: #790 and #5944 are open and directly related; #5131, #2051, #2195, and #2405 are guardrails; #5992 remains an existing large-document operation pressure, not a pagination claim.
  • gitcrawl neighbors ianstormtaylor/slate --number 790 --limit 20 --json
    • Result: performance/rendering neighbors include #3656, #4025, #2051, #3892, #4483, #4141, #2572, #2733, #5274, #4056, and #5944.
  • gitcrawl neighbors ianstormtaylor/slate --number 5944 --limit 20 --json
    • Result: pagination neighbors include #790, #3430, #3656, #4807, #4056, #5274, #2051, and #4844.
  • rg -n "#(5944|790|5131|2051|4141|3656|4210|5349|5992|5945|4056|2195|2405|5826|4995|5088|5473|4590|4837|4844|5639|5924|2793|2572|3892)|pagination|virtualiz|dynamic rendering|page/spread|page virtualization|DOM coverage|native-behavior|native behavior|production-ready virtualization|virtualized rendering|page fragment" docs/slate-issues docs/slate-v2/ledgers docs/slate-v2/references
    • Result: final revised plan matches existing issue/reference accounting: #5944 and #790 remain related/proof-backlog, production-ready virtualization remains unclaimed, and rerender/scroll/mobile/a11y/custom rows remain guardrails or non-claims.
  • sed -n '1,240p' docs/slate-issues/gitcrawl-v2-sync-ledger.md
    • Result: final 2026-05-28 sync entry now records no fixed/improved claim, no direct issue promotion, and no PR/matrix/dossier edit needed.
  • sed -n '384,480p' docs/slate-v2/ledgers/issue-coverage-matrix.md
    • Result: existing Pretext/page-virtualization/provider-fragment rows already keep #5944 issue-reviewed and #790 proof-route backlog.
  • sed -n '6800,6945p' docs/slate-v2/ledgers/fork-issue-dossier.md
    • Result: existing performance macro and pagination/provider rows already preserve the relevant no-claim and guardrail statuses.
  • sed -n '286,334p' docs/slate-v2/references/pr-description.md
    • Result: PR reference already says pagination planning adds no fixed/improved issue claim.
  • ffprobe -v error ... 2026-05-28 at 09.18.04.mp4
    • Result: width 2178, height 1838, avg_frame_rate 2096/37, duration 4.625s, nb_frames 262.
  • ffmpeg ... -vf "fps=4,scale=480:-1,tile=4x5" /tmp/codex-pagination-scroll-video/contact.jpg
    • Result: contact sheet created for plan evidence.
  • Source reads:
    • .tmp/slate-v2/packages/slate-layout/src/react.tsx
    • .tmp/slate-v2/packages/slate-layout/src/page-mount-plan.ts
    • .tmp/slate-v2/packages/slate-layout/src/index.ts
    • .tmp/slate-v2/packages/slate-layout/test/page-layout-contract.test.ts
    • .tmp/slate-v2/site/examples/ts/pagination.tsx
    • .tmp/slate-v2/playwright/integration/examples/pagination.test.ts
    • .tmp/slate-v2/packages/slate-react/src/dom-strategy/use-virtualized-root-plan.ts
    • .tmp/slate-v2/packages/slate-react/src/components/editable-text-blocks.tsx
    • .tmp/slate-v2/packages/slate-react/test/dom-strategy-page-virtualization.test.tsx
    • ../virtual/packages/react-virtual/e2e/app/test/scroll.spec.ts
    • ../virtual/packages/react-virtual/e2e/app/test/measure-element.spec.ts
    • ../virtual/packages/react-virtual/e2e/app/test/stale-index.spec.ts
    • ../virtual/docs/api/virtualizer.md
    • ../virtual/packages/virtual-core/src/lazy-measurements.ts
    • ../pretext/RESEARCH.md
    • ../pretext/STATUS.md
    • ../pretext/corpora/STATUS.md
    • ../pretext/src/measurement.ts
    • ../pretext/src/layout.ts
    • ../tiptap-docs/src/content/pages/core-concepts/limitations.mdx
    • ../tiptap-docs/src/content/pages/guides/table-with-pages.mdx
    • ../pierre/packages/trees/test/file-tree-virtualization-window.test.ts
    • ../pierre/packages/diffs/src/components/Virtualizer.ts
    • ../pierre/packages/diffs/src/components/CodeView.ts
    • docs/slate-v2/references/pr-description.md

Reboot status:

QuestionAnswer
Where am I?Closure score and final gates complete
Where am I going?User review; if accepted, start a separate execution goal in .tmp/slate-v2
What is the goal?A user-review-ready Slate Plan for robust pagination fast-scroll virtualization/testing
What have I learned?The plan should keep page virtualization internal behind virtualized domStrategy; the durable architecture is one shared Slate-owned page-window authority plus content-unit corridor; public renderer child-window API is not in the accepted plan; the test strategy must combine pure range/window math with real browser wheel/continuous-scroll geometry proof, visible no-blank assertions, perf samples, and explicit degraded-native behavior rows; external systems remain mechanism references, not public API or model-semantics sources
What have I done?Created the plan and closed current-state read, related issue discovery, issue-ledger accounting, intent/decision briefing, research/ecosystem/live-source refresh, performance/DX/migration/regression/simplicity pressure, Slate maintainer objection ledger, high-risk deliberate mode, ecosystem maintainer, revision, issue-sync accounting, and final closure passes

Open risks:

  • Planning risks: none.
  • Execution risks: the user-visible blank pages may be partly fixture design and partly materialization lag, so execution proof must distinguish those with visible stress labels and no-blank assertions after every scroll burst.
  • Execution can reopen issue/reference sync only if the API, runtime target, or issue claim set changes.
  • Runtime performance remains unclaimed until browser replay records frame/long-task numbers from .tmp/slate-v2.
  • Internal child-range planning may still fail to preserve custom renderer composition; that would require a separate public-API maintainer review.

Execution activation:

ItemResult
Statusimplementation complete; review clean
Runtime shapePagedEditable now owns the expensive virtualized page-content window and passes it into Editable through internal EditableLayout state.
Fast-scroll fixScroll viewport geometry uses root/scroll DOM rects and synchronously commits the page-content window on scroll so visible content is mounted before paint.
Page surface policyPage chrome can still use configured page overscan, while editable content uses the actual visible page corridor plus selected-path retention.
Table policyVirtualized table renderers use the block box as the table origin so filtered row boxes do not shift rows above the viewport.
Public APINo new public TanStack passthrough, no public renderer child-window API, no AST table splitting.
Changesets.tmp/slate-v2/.changeset/paged-fast-scroll-window.md; .tmp/slate-v2/.changeset/slate-react-shared-page-window.md.

Execution verification:

  • bun --filter slate-layout test
    • Result: 36 passed.
  • cd packages/slate-react && bun test:vitest -- dom-strategy-page-virtualization
    • Result: 5 passed.
  • bun --filter slate-layout typecheck
    • Result: passed.
  • bun --filter slate-react typecheck
    • Result: passed.
  • bun typecheck:site
    • Result: passed.
  • bunx biome check packages/slate-layout/src/react.tsx packages/slate-react/src/components/editable-text-blocks.tsx packages/slate-react/src/dom-strategy/use-virtualized-root-plan.ts packages/slate-react/test/dom-strategy-page-virtualization.test.tsx site/examples/ts/pagination.tsx playwright/integration/examples/pagination.test.ts
    • Result: passed.
  • bunx eslint packages/slate-layout/src/react.tsx packages/slate-react/src/components/editable-text-blocks.tsx packages/slate-react/src/dom-strategy/use-virtualized-root-plan.ts packages/slate-react/test/dom-strategy-page-virtualization.test.tsx site/examples/ts/pagination.tsx playwright/integration/examples/pagination.test.ts
    • Result: passed; ESLint reported ignored-file warnings only.
  • bun lint
    • Result: passed.
  • bunx playwright test playwright/integration/examples/pagination.test.ts --project=chromium -g "keeps visible content mounted during fast wheel scrolling" --repeat-each=3 --workers=1
    • Result: 3 passed.
  • bunx playwright test playwright/integration/examples/pagination.test.ts --project=chromium -g "keeps a 1000-page virtualized document with a 10-page table bounded|keeps visible content mounted during fast wheel scrolling|keeps scaled virtualized page surfaces aligned"
    • Result: 3 passed.
  • /Users/zbeyens/git/plate-2/.agents/skills/autoreview/scripts/autoreview --mode local
    • Result: first run hung for over 8 minutes inside its Codex child and was terminated, then retried below.
  • /Users/zbeyens/git/plate-2/.agents/skills/autoreview/scripts/autoreview --mode local --no-web-search --thinking low
    • Result: clean; no accepted/actionable findings reported; overall patch correctness confidence 0.71.