Skip to content

perf(bundle): code-split the Tiptap editor stack out of the eager bundle - #57

Merged
bhackett1024 merged 1 commit into
mainfrom
task/fix-sh-bug-muojitzp-iu36-788kb-eagerly-l-7yqOo2bt
Oct 1, 2026
Merged

bhackett1024 merged 1 commit into
mainfrom
task/fix-sh-bug-muojitzp-iu36-788kb-eagerly-l-7yqOo2bt

Conversation

@bhackett1024

Copy link
Copy Markdown
Contributor

Why

Self Healing bug-muojitzp-iu36 (network-performance): production served one eagerly-loaded ~791KB index-*.js containing the entire app — including the Tiptap rich-text editor and its toolbar stack — with zero code-splitting (verified live: https://obvious-issue-tracker-github.netlify.app loads a single 809,810-byte module script on first paint). Every surface pays for the editor even though only three render one.

What

  • LazyRichTextEditor (src/components/ui/LazyRichTextEditor.tsx): a React.lazy wrapper with a layout-stable Suspense fallback; the three consumers (IssueDetailView, ActivityFeed, CreateIssueDialog) swap to it. Vite now emits Tiptap + ProseMirror as a separate on-demand chunk: eager JS drops 809,810 → 388,117 bytes (−52%; 246 → 117 KB gzip), and a fresh shell load fetches zero editor bytes until an editor surface mounts.
  • RichTextEditor lifecycle guards: under the lazy/Suspense mount, React can discard the render that created the editor, and the discarded instance crashed the commit with Cannot read properties of null (reading 'cached') (Tiptap's open useEditor + lazy/Suspense issue: the hook can hand back an editor destroyed before its first effect runs). Fixed with the vendor-recommended pattern: immediatelyRender: false plus editor.isDestroyed guards in the sync effects. Behavior under the original static import is unchanged apart from editor creation moving one effect-tick later.

Rejected alternative: route-level splitting of all views — wider surface, and sibling reports this wave already own the command palette and main-bundle re-fetch behaviors; this fix stays scoped to this report's surface.

How to Review

  • Core change: LazyRichTextEditor.tsx (new) + the import/JSX swaps in the three consumers.
  • The immediatelyRender: false + isDestroyed guards in RichTextEditor.tsx are the non-obvious part — they are what makes mounting the editor inside Suspense safe. Without them the issue detail view hard-crashes to a blank screen.

Test Evidence

Local build + headless browser (vite preview): fresh shell load requests 0 editor chunks; opening an issue detail fetches the chunk once and mounts both editors (description + comment composer); the create-issue dialog editor also mounts; typing round-trips into HTML in all three; zero uncaught console errors. npm run lint and npm run build green.

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

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

@bhackett1024
bhackett1024 marked this pull request as ready for review September 30, 2026 23:04
Rebased on top of #56, which landed the same split on main: the editor
now lazy-loads through RichTextEditor.tsx -> RichTextEditorImpl and the
command palette lazy-loads in CommandPaletteProvider, so those stand as
the chunking mechanism and the duplicate lazy wrapper from this commit
is dropped (importers keep importing RichTextEditor unchanged).

What remains from this commit is the destroyed-editor guard on the
editable-sync effect (skip setEditable once editor.isDestroyed), the
last piece of the iu36 hardening not already on main -- the content-sync
guard and immediatelyRender: false landed via #56.

Addresses the surface reported by Self Healing bug-muojitzp-iu36.

Co-authored-by: Brian Hackett <bhackett@replay.io>
@bhackett1024
bhackett1024 force-pushed the task/fix-sh-bug-muojitzp-iu36-788kb-eagerly-l-7yqOo2bt branch from 21f8acf to 37c6c2b Compare October 1, 2026 16:55
@bhackett1024
bhackett1024 merged commit dfd178c into main Oct 1, 2026
2 checks passed
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