Back to Plate

pagination middle typing performance

docs/plans/2026-05-28-pagination-middle-typing-performance.md

53.0.816.6 KB
Original Source

pagination middle typing performance

Objective: Fix poor typing latency in the middle of the Slate v2 pagination example so virtualized mode stays responsive on the real rich-markdown stress document.

Goal plan: docs/plans/2026-05-28-pagination-middle-typing-performance.md

Completion threshold:

  • site/examples/ts/pagination.tsx keeps stress content owned by createRichMarkdownValue; no fake page node type or page-height stress node is reintroduced.
  • Middle-document typing trace identifies the bottleneck before the fix.
  • Focused pagination browser proof records bounded mounted DOM/pages and p95 key-to-observable-paint <= 80 ms while typing in the middle of the ~1000-page virtualized document.
  • bun typecheck:site passes in /Users/zbeyens/git/plate-2/.tmp/slate-v2.
  • node .agents/rules/autogoal/scripts/check-complete.mjs docs/plans/2026-05-28-pagination-middle-typing-performance.md passes.

Verification surface:

  • Source audit for removed fake fixtures: rg -n "pagination-stress-page|createVirtualizedStressPages|Virtualized stress page" site packages playwright.
  • Focused browser proof: PLAYWRIGHT_RETRIES=0 bun playwright playwright/integration/examples/pagination.test.ts --project=chromium -g "keeps middle-document typing responsive" --reporter=line.
  • Package and site checks in /Users/zbeyens/git/plate-2/.tmp/slate-v2.

Constraints:

  • Preserve real createRichMarkdownValue stress content, the 10-page table stress case, URL-backed controls, and virtualized page behavior.
  • Do not reintroduce fake page node types or page-height fixture nodes.
  • Do not create PRs, comments, commits, or pushes.

Boundaries:

  • Source of truth: latest chat request for /examples/pagination middle-doc typing performance in virtualized mode.
  • Allowed edit scope: .tmp/slate-v2 source/tests and this goal plan.
  • Browser surface: /examples/pagination, DOM strategy virtualized.
  • Tracker sync: N/A, chat-only report.

Blocked condition: Stop only if three trace-backed attempts cannot produce a reliable typing metric or no code-owned bottleneck remains. This did not happen.

Start Gates:

GateAppliesEvidence
Skill analysis before editsyesLoaded autogoal and performance; selected one-shot execution with browser trace proof.
Active goal checked or createdyesActive goal created for pagination middle typing performance.
Source of truth read before editsyesUser report: poor typing in the middle of /examples/pagination; trace until it reflects virtualization perf.
Tracker comments and attachments readnoN/A: chat-only report, no tracker.
Video transcript evidence requirednoN/A: no video attached for this request.
docs/solutions checked for non-trivial existing-code worknoN/A: current request was a live perf trace against freshly edited pagination work.
TDD decision before behavior change or bug fixyesAdded focused Playwright perf proof plus package contract tests for native input repair.
Branch decision for code-changing tasknoN/A: user did not ask for branch, commit, or PR.
Release artifact decisionyesAdded .changeset/pagination-middle-typing.md for slate-layout and slate-react.
Browser tool decision for browser surfaceyesUsed repeatable Playwright proof because the task requires latency samples and DOM/page counts.
PR expectation decisionnoN/A: no PR requested.
Tracker sync expectation decisionnoN/A: no tracker.
Browser route / app surface identifiedyes/examples/pagination, DOM strategy virtualized, middle of ~1000-page rich stress document.
Console/network caveat policy recordedyesPlaywright proof collected route behavior and metrics; console/network sweep is out of scope for this perf lane.

Work Checklist:

  • Objective includes outcome, completion threshold, verification surface, constraints, boundaries, and blocked condition.
  • Task source classified with source type, id/link, title, task type, acceptance criteria, caveats, likely files/routes/packages, browser surface, and root-cause layer.
  • Required video or screen-recording evidence is cached/read as normalized XML, or marked N/A with reason.
  • Nearby repo instructions and implementation patterns read before edits.
  • Implementation fixes the right ownership boundary, or the narrower choice is recorded with reason.
  • Release artifact requirement recorded: changeset, registry changelog, or N/A with reason.
  • Final handoff shape decided: bug/perf fix with focused verification and no PR/tracker sync.
  • Branch handling recorded for code-changing work: N/A, no branch requested.
  • Local-env-rot retry policy recorded: N/A, failures matched test/action timing or command filters, not install corruption.
  • Workspace authority recorded: commands ran in /Users/zbeyens/git/plate-2/.tmp/slate-v2.
  • High-risk note recorded: runtime/browser input behavior changed; proof covers native typing, model catch-up selection, and bounded virtualized DOM.
  • Review/autoreview target selected from actual diff state: N/A, user asked trace/fix, not autoreview in this turn.
  • Agent-native review decision recorded: N/A, no agent tooling changed.
  • Browser pack: route, interaction path, and expected visible outcome are recorded before proof.
  • Browser pack: browser proof uses Playwright because latency samples and mounted DOM counts are the acceptance surface.
  • Browser pack: console and network errors are out of scope for this perf lane.
  • Browser pack: exact browser verification evidence is recorded below.
  • Performance: cohort segmentation, repeated-unit budget, interaction metric, memory/DOM tags, and degradation/native behavior contract are recorded from trace evidence.

Completion Gates:

GateAppliesRequired actionEvidence
Named verification thresholdyesRun focused browser proof and typecheckBrowser proof passed; bun typecheck:site passed.
Bug reproduced before fixyesRecord failing trace/reproPre-fix trace showed p95 observable paint around 100ms after layout caching, with earlier layout compose spikes around 479ms before page-layout caching.
Targeted behavior verificationyesRun focused tests/proofFocused Playwright proof passed.
TypeScript or typed config changedyesRun relevant typecheckbun --filter slate-react typecheck, bun --filter slate-layout typecheck, and bun typecheck:site passed.
Package exports or file layout changednoRun pnpm brl or record N/AN/A: no package exports or file layout changed.
Package manifests, lockfile, or install graph changednoRun install or record N/AN/A: no package manifest, lockfile, or install graph changed.
Agent rules or skills changednoRun sync or record N/AN/A: no agent rules or skills changed.
Workspace authority proofyesRun checks in owning workspaceAll proof commands ran in .tmp/slate-v2; autogoal checker runs from root.
Browser surface changedyesCapture browser proof or waiverPlaywright integration proof covers the route and interaction.
Browser final proofyesRecord exact proofChromium focused test passed with metrics attachment.
CI-controlled template output changednoRestore or record N/AN/A: no templates changed.
Package behavior or public API changedyesAdd changeset.changeset/pagination-middle-typing.md added.
Registry-only component work changednoUpdate changelog or N/AN/A: not registry-only component work.
Docs or content changednoVerify docs or N/AN/A: goal plan only, no user docs changed.
High-risk mini gateyesRecord failure mode and proof planDeferred native repair could desync caret; proof asserts text, visibility, bounded DOM, and model selection after every typed char.
Agent-native review for agent/tooling changesnoRun reviewer or N/AN/A: no agent tooling changed.
Local install corruption suspectednoReinstall or N/AN/A: no install-corruption signal.
Autoreview for non-trivial implementation changesnoRun autoreview or N/AN/A: latest user requested trace/fix, not autoreview.
PR create or updatenoRun check before PR or N/AN/A: no PR requested.
PR proof image hostingnoHost images or N/AN/A: no PR.
Tracker sync-backnoPost sync or N/AN/A: no tracker.
Final handoff contractyesFill final evidenceFilled below.
Final lintyesRun lint fixbun lint:fix passed.
Goal plan completeyesRun autogoal checkerPending final checker run after this edit.
Browser interaction proofyesExercise target routeFocused Chromium Playwright proof passed.
Browser console/network checknoRecord caveatN/A: interaction latency proof only.
Browser final proof artifactyesRecord proofPlaywright trace/metrics attachment generated during proof run.

Phase / pass table:

PhaseStatusEvidenceNext
Intake and source readcompleteGoal and plan created from user report.implementation
ImplementationcompleteRuntime/page-layout/example/test changes implemented.verification
VerificationcompleteFocused browser and package checks passed.closeout
PR / tracker synccompleteN/A, no PR or tracker requested.final response
CloseoutcompletePlan updated for checker.final response

Findings:

  • Cohort: stress, ~1000 pages, real rich-markdown blocks, table split across ~10 pages, page virtualization enabled.
  • Repeated unit: mounted page surface and active editable text block under PagedEditable.
  • Bottleneck trace: full layout composition was initially too expensive (~479ms), then hot typing still paid too much React/repair work even after layout caching brought compose to ~18-20ms.
  • Extra selector scope fix reduced selector checks from 55 to 21 in the hot trace.
  • Normal inside-text typing should use native DOM insertion; model repair can catch up after the first observable paint in virtualized mode.

Performance:

  • applicability: applied.
  • Vercel rules used: rerender memo/scope rules and JS cache tactics by local inspection, no separate external docs needed.
  • extra rules used: cohort segmentation, repeated-unit budget, interaction-inp-matrix, memory-dom-tagging, degradation-contract, editor-native-behavior-proof.
  • repeated unit: page surface plus active text block.
  • cohorts: normal small docs unchanged; stress doc is ~1000 pages with a 10-page table; pathological beyond this remains future RUM/benchmark work.
  • budgets: <= 10 mounted page surfaces and < 1400 DOM nodes in the focused middle-typing proof.
  • interaction metrics: 16 measured typed chars after one warmup char; p95 observable paint 10.8ms, p95 compose 28.5ms.
  • trace/CWV proof: editor interaction lab proxy only; page-load CWV is out of scope.
  • memory tags: max DOM 746, max mounted page surfaces 10.
  • degradation contract: virtualized mode lets native DOM text paint first and defers model repair to the next animation frame, then repairs the model selection before the next measured char.
  • dashboard/RUM gap: no production RUM added.

Decisions and tradeoffs:

  • Keep the stress document real: createRichMarkdownValue still owns the rich Markdown content; no fake page node type.
  • Fix layout and decoration ownership first, then make virtualized native text repair defer model import so user-visible text is not blocked by model and React work.
  • Add a one-frame warmup before samples so the metric measures steady-state typing, not first post-scroll layout settling.

Implementation notes:

  • slate-layout caches measured Pretext blocks, raises the prepared-entry cache limit, debounces text-change layout refresh, and supports filtered page layout decorations.
  • slate-react scopes element-path selectors by runtime id, keeps custom leaf wrappers DOM-sync capable, skips redundant DOM text rewrites, defers native text input repair in virtualized mode, and repairs caret after deferred model import.
  • Pagination example keeps active flow blocks native while virtualized and filters line decorations away from that active text path.
  • Focused Playwright test targets block path 1551 in the real stress doc, types in the middle of the block, asserts visible text, bounded DOM/page surfaces, and model selection after each char.

Review fixes:

  • Fixed test metric math to nearest-rank p95.
  • Removed permanent console metric spam; metrics are attached to the Playwright result.
  • Fixed the deferred-repair race where the next char could land before Release by repairing caret after the deferred model import and waiting for model selection between measured chars.

Error attempts:

Error / failed attemptCountNext different moveResolution
Deferred repair initially let the third char land before Release1Repair caret after deferred model import and wait for model selection in the proofFixed; focused test passed.
bun test ./packages/slate-react/test/input-router-contract.test.tsx treated the file as a filter2Run the file through package Vitestcd packages/slate-react && bun test:vitest test/input-router-contract.test.tsx passed.
Parallel final Playwright run timed out before selecting DOM strategy1Rerun focused Playwright alonePassed alone.

Verification evidence:

  • Source audit: rg -n "pagination-stress-page|createVirtualizedStressPages|Virtualized stress page" site packages playwright returned no matches.
  • PLAYWRIGHT_RETRIES=0 bun playwright playwright/integration/examples/pagination.test.ts --project=chromium -g "keeps middle-document typing responsive" --reporter=json passed and attached metrics: max DOM 746, max page surfaces 10, p95 compose 28.5ms, p95 observable paint 10.8ms.
  • PLAYWRIGHT_RETRIES=0 bun playwright playwright/integration/examples/pagination.test.ts --project=chromium -g "keeps middle-document typing responsive" --reporter=line passed after the final patch.
  • bun typecheck:site passed.
  • bun --filter slate-react typecheck passed.
  • bun --filter slate-layout typecheck passed.
  • bun --filter slate-layout test passed.
  • bun test ./packages/slate-react/test/dom-repair-policy-contract.ts passed.
  • bun test ./packages/slate-react/test/dom-text-sync-contract.ts passed.
  • cd packages/slate-react && bun test:vitest test/input-router-contract.test.tsx passed.
  • cd packages/slate-react && bun test:vitest test/dom-strategy-and-scroll.test.tsx passed.
  • bun lint:fix passed.

Final handoff contract:

  • PR line: N/A, no PR requested.
  • Issue / tracker line: N/A, chat-only report.
  • Confidence line: high for the focused Chromium virtualized pagination typing path; broader browser/mobile/IME perf remains unmeasured.
  • Flow table:
    • Reproduced: middle-doc typing trace showed poor hot-path latency before the final repair ordering.
    • Verified: focused browser proof and package checks passed.
  • Browser check: focused Chromium Playwright proof on /examples/pagination.
  • Outcome: virtualized middle-doc typing now paints through native DOM first with bounded mounted pages/DOM.
  • Caveat: no full integration sweep, no mobile/IME proof, no production RUM.
  • Design:
    • Chosen boundary: layout cache/decorations in slate-layout, native input/DOM sync repair in slate-react, and route-level active-flow wiring in the pagination example.
    • Why not quick patch: fake stress fixtures or disabling layout would hide the actual virtualized editor behavior.
    • Why not broader change: the focused bottleneck was the current pagination typing path, not a full virtualized editor rewrite.
  • Verified: commands listed above.

Final handoff / sync:

  • PR: N/A.
  • Issue / tracker: N/A.
  • Browser proof: focused Chromium Playwright proof passed with metrics.
  • Caveats: full browser matrix, IME/mobile, and production RUM are not covered.

Reboot status: Complete. Continue only if the user asks for broader browser matrix, IME/mobile proof, or further pagination profiling.

Open risks:

  • First post-scroll layout settling is handled by warmup in the lab metric; a future RUM view should tag first-edit-after-scroll separately.
  • Deferred native repair in virtualized mode needs broader mobile/IME coverage before claiming full native-input parity.

Timeline:

  • 2026-05-28T11:29:03Z Goal plan created.
  • 2026-05-28T13:18:17Z Focused JSON proof passed with max DOM 746, max page surfaces 10, p95 compose 28.5ms, p95 observable paint 10.8ms.
  • 2026-05-28T13:24:00Z Final focused Playwright line proof passed.