Skip to content

feat: crash recovery overlay for unresponsive instances#7

Open
Teamingzooper wants to merge 1 commit into
fix/notification-sidebar-syncfrom
feat/crash-recovery
Open

feat: crash recovery overlay for unresponsive instances#7
Teamingzooper wants to merge 1 commit into
fix/notification-sidebar-syncfrom
feat/crash-recovery

Conversation

@Teamingzooper

Copy link
Copy Markdown
Owner

Summary

When an embedded webview's renderer process dies (OOM kill, V8 segfault, malicious page calling `process.crash()`, a runaway memory leak), Nexus previously just showed a frozen view with no signal to the user. This PR adds an HTML overlay over the affected instance with Reload and Dismiss buttons.

Branch base: `fix/notification-sidebar-sync` — this stacks on the open PR #6 so the teardowns + bus-forwarding patterns added there are reused here. Merge #6 first.

Mechanism

Same main → bus → IPC → renderer pattern as the notification/sidebar sync fix:

  1. `ViewService` attaches `webContents.on('render-process-gone', …)` to every view at creation. Filters out clean exits. Emits a new `view:crashed` bus event with `{instanceId, reason}`.
  2. `IpcService` subscribes to the bus event (alongside `instance:activated`) and forwards to the main renderer window via the new `nexus:view:crashed` IPC channel.
  3. `preload.ts` exposes `window.nexus.onViewCrashed(cb)`.
  4. Renderer store tracks `crashedInstances: Record<id, {reason}>` and gets two new actions: `reloadCrashedInstance(id)` and `dismissCrash(id)`.
  5. New `CrashOverlay` component mounted globally in `App.tsx`. Shows only when the active instance is in the crashed map. Uses the existing `useOverlay()` hook so the underlying WebContentsView is moved offscreen while the overlay is visible — without that, the native Chromium layer would obscure our HTML.

Design choices

  • No auto-reload. Crash loops are worse than a stuck panel; user controls when to retry.
  • Background instances that crash are silently tracked and the overlay only shows when the user activates them. Avoids interrupting whatever else they're doing.
  • Dismiss removes the entry entirely — re-activating the instance after dismissing won't re-show the overlay until it crashes again.
  • Optimistic flag clearing on Reload. If the reload itself fails, the action re-flags with the new error string so the overlay reappears with a useful message.

Test plan

  • `npm run typecheck` — clean
  • `npm run test` — 178/178 passing
  • `npm run build` — renderer + main both clean
  • Manual smoke: open an instance, open its devtools, type `process.crash()`. Overlay should appear within ~200ms. Click Reload — fresh renderer spawns, overlay disappears. Try Dismiss instead — overlay hides, you can switch to another instance.
  • Manual smoke: trigger a crash on a background instance (open it, switch away, then crash). No overlay shows until you activate that instance, at which point it appears.

No automated test — the same IPC integration gap as the prior PR. The change is small enough that manual smoke is the right level.

🤖 Generated with Claude Code

When a WebContentsView's renderer process dies (OOM kill, V8 segfault,
page calling process.crash(), etc.), Nexus previously just showed a
blank/frozen view. Now an HTML overlay appears over the affected
instance with "Reload" and "Dismiss" buttons.

Implementation follows the same main→bus→IPC→renderer pattern as the
notification/sidebar sync fix:

- ViewService attaches `webContents.on('render-process-gone', …)` to
  every view at creation time. Filters out clean exits. Emits a new
  `view:crashed` bus event with {instanceId, reason}.
- IpcService subscribes to the bus event (alongside instance:activated)
  and forwards to the main renderer window via `nexus:view:crashed`.
- preload.ts exposes `window.nexus.onViewCrashed(cb)`.
- Renderer store tracks `crashedInstances: Record<id, {reason}>` and
  gets a `reloadCrashedInstance(id)` action that clears the flag,
  activates the instance, and calls reloadActiveInstance. Failure to
  reload re-flags with the new error.
- New CrashOverlay component mounted globally in App. Shows only when
  the *active* instance is in the crashed map. Calls useOverlay() so
  the underlying WebContentsView is suspended (moved offscreen) while
  the overlay is visible.
- No auto-reload by design — crash loops are worse than a stuck panel.

178/178 unit tests still pass. Renderer + main builds clean. Manual
smoke: in a webview's devtools, type `process.crash()` — the overlay
should appear within ~200ms; Reload spawns a fresh renderer; Dismiss
hides the overlay and the user can switch to another instance.
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