Skip to content

fix(gui): contain a failed lazy chunk instead of taking down the app - #224

Merged
huhamhire merged 1 commit into
devfrom
fix/lazy-chunk-boundary
Sep 8, 2026
Merged

huhamhire merged 1 commit into
devfrom
fix/lazy-chunk-boundary

Conversation

@huhamhire

Copy link
Copy Markdown
Owner

Caught by the root crash screen added in #221, which reported the failing module by name:

Failed to fetch dynamically imported module:
  file:///…/apps/desktop/out/renderer/assets/InlineCodeContext-lCMdDq2I.js

That is the diagnosis the containment work was meant to make possible — previously this would have been an unrecoverable black window with nothing in the log.

The structural defect

Suspense covers only the pending half of a dynamic import. When the chunk fails to load the promise rejects, and the error propagates to the nearest error boundary — and neither lazy() site had one:

DiffView does carry an internal boundary around DiffPane, but that cannot see its own chunk failing to arrive. So a Monaco snippet that could not be fetched cost the user the entire application, comment thread and all.

Each lazy subtree now owns a LazyBoundary (Suspense plus a boundary) and the failure stays inside the pane that could not load.

Stale chunks get their own treatment

For this class of failure the page is holding a hashed module URL the app no longer has — it was rebuilt or updated while the window stayed open. Re-rendering re-requests the same dead URL and fails identically, so "retry" is a button that cannot work. isChunkLoadError detects the case, and both the pane fallback and the root crash screen lead with reload and explain why; other errors keep retry as before.

On the origin of this particular crash

Worth stating plainly: this specific occurrence was almost certainly self-inflicted. The path is a local build output, and I ran npm run build repeatedly while the app was open, replacing hashed chunks under a live window.

That makes it unlikely to be the cause of the originally reported black screen — a packaged app reads from a read-only asar that does not change under a running window. That trigger is still unknown. The defect fixed here is real regardless of what surfaced it: one lazily-loaded pane failing should never cost the whole app, and in a packaged build the same failure is still reachable when the app updates while a window stays open.

Docs

03-gui/01-ui-interaction.md: the failure-containment section goes from three layers to four, with the lazy-subtree layer and the stale-chunk distinction written down. CHANGELOG in both languages.

lint / typecheck / test / build pass. Not exercised visually in the running app — the fallback paths were verified by construction, not by forcing a chunk failure under the UI.

🤖 Generated with Claude Code

Suspense covers only the pending half of a dynamic import. When the chunk fails
to load the promise rejects and the error propagates to the nearest boundary --
and neither lazy() site had one: the diff editor in PrPanel and a comment''s
inline code context in CommentItem. DiffView carries an internal boundary around
DiffPane, but that cannot see its own chunk failing to arrive. So a Monaco
snippet that could not be fetched cost the user the entire application.

Each lazy subtree now owns a LazyBoundary (Suspense plus a boundary) and the
failure stays inside the pane that could not load.

A stale chunk is singled out. The window holds a hashed module URL the app no
longer has -- it was rebuilt or updated while the window stayed open -- so
re-rendering re-requests the same dead URL and fails identically; only a reload
recovers. Offering "retry" there is offering a button that cannot work, so
isChunkLoadError detects the case and both the pane fallback and the root crash
screen lead with reload and say why. Other errors keep retry.

Observed via the root crash screen added earlier, which reported the failing
module by name -- the diagnosis this containment work was meant to make possible.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@huhamhire huhamhire added the bug Something isn't working label Sep 8, 2026
@huhamhire
huhamhire merged commit 195aa96 into dev Sep 8, 2026
3 checks passed
@huhamhire huhamhire mentioned this pull request Sep 9, 2026
3 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant