Skip to content

perf(editor): mount TipTap editors after the navigation paint - #58

Open
bhackett1024 wants to merge 1 commit into
mainfrom
fix/sh-bug-mupr5tsb-7yuz
Open

bhackett1024 wants to merge 1 commit into
mainfrom
fix/sh-bug-mupr5tsb-7yuz

Conversation

@bhackett1024

Copy link
Copy Markdown
Contributor

Why

Opening an issue stuttered: navigating from the Issues list to the detail page delivered one synchronous 52.6ms commit (~3 dropped frames), 83.8% of it from mounting two TipTap/ProseMirror editors — the description editor and the comment composer — whose useEditor initialization costs ~20ms of synchronous main-thread work per instance. PR #56 moved the editor download off the critical path, but with the chunk cached every issue open still paid the mount cost inside the navigation frame. Report: bug-mupr5tsb-7yuz.

What

Editors now wait for the navigation frame to actually paint (double requestAnimationFrame) before mounting, inside RichTextEditorImpl. While deferred, the shell renders a static preview of the same chrome:

  • a toolbar skeleton reserving the real toolbar's exact height — no layout shift when the toolbar appears;
  • the description HTML via dangerouslySetInnerHTML — the same trust model as the editor itself and ActivityFeed's comment rendering — or, for an empty document, TipTap's own placeholder paragraph markup (p.is-editor-empty[data-placeholder]) so the placeholder text is present during the swap.

Trade-off: the interactive editor arrives 1–2 frames after the page paints, instead of the page waiting ~3 frames to paint at all. Alternatives rejected: wrapping the navigation state in a transition (does not remove the synchronous editor-effect cost from the commit) and click-to-edit descriptions (changes product behavior).

How to Review

  • The change is confined to src/components/ui/RichTextEditorImpl.tsx — deliberately zero overlap with PR perf(bundle): code-split the Tiptap editor stack out of the eager bundle #57's in-flight chunk-splitting refactor (wrapper + consumer files).
  • immediatelyRender: false and its destroy-timer rationale (bug-mun3jox6-810l) are preserved; the deferral gates mounting the component, not the editor creation race.
  • All three editor consumers (detail description, comment composer, new-issue dialog) get the deferral; none pass className, and the dialog does not autofocus the editor.

Verification

  • npm run lint and npm run build (tsc -b + vite) green locally; the editor impl still builds as its own lazy chunk.
  • Self Healing QA verification against the exact-head Netlify preview is queued through the operate loop.

Human author: Brian Hackett (bhackett@replay.io)

🔗 Obvious Project · 🧵 Obvious Thread · 🔀 Obvious PR Artifact

Opening an issue mounted two TipTap editors (description and comment
composer) inside the navigation commit. Editor creation costs ~20ms of
synchronous main-thread work per instance, so the detail page could not
paint for ~53ms — about three dropped frames — on every issue open
(bug-mupr5tsb-7yuz).

Editors now wait for the navigation frame to actually paint (double
rAF) before mounting. While deferred, the shell renders a static
preview of the same chrome: a toolbar skeleton reserving its exact
height, and the description HTML (or TipTap's empty-placeholder
paragraph markup) so the swap is pixel-identical. The change is
confined to the editor impl chunk so it stays independent of the
in-flight chunk-splitting work (#57).

Co-authored-by: Brian Hackett <bhackett@replay.io>
@bhackett1024
bhackett1024 marked this pull request as ready for review October 1, 2026 17:03
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.

2 participants