Skip to content

perf: keep large markdown pastes fast and responsive#66

Open
DavertMik wants to merge 1 commit into
masterfrom
perf/large-markdown-paste
Open

perf: keep large markdown pastes fast and responsive#66
DavertMik wants to merge 1 commit into
masterfrom
perf/large-markdown-paste

Conversation

@DavertMik
Copy link
Copy Markdown
Contributor

Problem

Pasting a large test document (the become_a_tasker.test.md suite — ~58 KB, 1002 blocks: ~140 test steps, 162 headings, the rest paragraphs) froze the editor for ~1.6 s.

Profiling (Chrome, real paste) showed the cost is rendering, not parsing. markdownToBlocks takes only ~3–13 ms. The freeze came from mounting ~800 OverType editor instances (every step field is an OverType editor) synchronously in a single main-thread task.

Changes

  • Lazily mount step editors (useDeferredMount) — off-screen steps render a cheap static preview and upgrade to the interactive OverType editor when scrolled into view or clicked. A click both upgrades and focuses the field, so editing starts on the first click; scroll/passive upgrades never steal focus. Freshly-inserted empty steps still mount eagerly and autofocus.
  • Stream large pastes (createMarkdownPasteHandler) — insert the first screenful synchronously, then append the rest in idle-time batches so the main thread never blocks while a thousand-block document is built.
  • Skip needless re-renders while typing — compute each step's number and bail out of the state update when it's unchanged, so ordinary edits don't re-render the whole step list.
  • Debounce the demo preview (App.tsx) — serialize Markdown/JSON once the document settles instead of on every streamed batch.

Results (~1000-block paste, measured in Chrome)

Metric Before After
Worst main-thread freeze 1651 ms ~160 ms
Total Blocking Time 1806 ms ~140 ms
Time to first visible content ~640 ms ~80 ms
OverType editors mounted on paste 801 6 (rest on scroll)

Tests

All 167 unit tests pass; tsc clean. New regression guards (behavioural, not wall-clock, so CI-safe):

  • createMarkdownPasteHandler.test.ts — a large paste is not built synchronously (only a bounded first chunk), and every block eventually streams in exactly once, in order.
  • stepNumber.test.ts — step-numbering correctness (resets after non-step blocks, continues across blank lines).
  • renderingPerf.test.ts — coarse parse-cost guard + a sub-quadratic scaling check.

Notes

  • For very large pastes (>150 blocks) the streamed insert creates a few undo steps rather than one. Can be wrapped in a single history transaction if single-undo-for-paste is desired.
  • Click-to-edit places the caret at the end of the field (the preview DOM differs from the textarea, so exact click-point mapping isn't trivial). Can be added with caretRangeFromPoint if wanted.
  • A per-editor step-number cache was prototyped and deliberately dropped — it only helped typing in 100+ step docs while adding notable complexity, so the simpler bail-out is used instead.

🤖 Generated with Claude Code

Pasting a large test document (~1000 blocks) froze the editor for ~1.6s.
The cost was rendering, not parsing (~3ms): ~800 OverType editor instances
mounted synchronously in a single main-thread task.

- Lazily mount step editors (useDeferredMount): off-screen steps render a
  cheap static preview and upgrade to the interactive OverType editor when
  scrolled into view or clicked. A click both upgrades and focuses the field
  so editing starts on the first click; scroll/passive upgrades never steal
  focus. Freshly inserted empty steps still mount eagerly and autofocus.
- Stream large pastes: insert the first screenful synchronously, then append
  the remaining blocks in idle-time batches so the main thread never blocks
  while a thousand-block document is built.
- Avoid re-rendering every step on each keystroke: compute the step number
  and bail out of the state update when it is unchanged.
- Debounce the demo's Markdown/JSON preview serialization so it runs once the
  document settles instead of on every streamed batch.

Result on a ~1000-block paste (Chrome, measured): worst main-thread freeze
1651ms -> ~160ms, Total Blocking Time 1806ms -> ~140ms, time-to-first-content
~640ms -> ~80ms; OverType editors mounted on paste 801 -> 6 (rest mount on
scroll).

Adds regression tests for the behaviour that makes rendering fast: chunked
paste (no synchronous full-document build), step-number correctness, and a
sub-quadratic markdown parse guard.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@cloudflare-workers-and-pages
Copy link
Copy Markdown

Deploying blocks with  Cloudflare Pages  Cloudflare Pages

Latest commit: c1203a7
Status: ✅  Deploy successful!
Preview URL: https://7e2aa228.blocks-cno.pages.dev
Branch Preview URL: https://perf-large-markdown-paste.blocks-cno.pages.dev

View logs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant