Skip to content

feat(notebook-app): route ErrorBoundary errors through host logger#2101

Merged
rgbkrk merged 1 commit intomainfrom
investigate/e2e-app-not-ready
Apr 23, 2026
Merged

feat(notebook-app): route ErrorBoundary errors through host logger#2101
rgbkrk merged 1 commit intomainfrom
investigate/e2e-app-not-ready

Conversation

@rgbkrk
Copy link
Copy Markdown
Member

@rgbkrk rgbkrk commented Apr 23, 2026

Summary

  • ErrorBoundary.componentDidCatch gains an opt-in module-scope sink; main.tsx wires it to logger.error(...) so caught render errors and their component stacks land in notebook.log.
  • Until now, the boundary only did console.error. In packaged / CI builds that goes nowhere — which is why every E2E spec on 2026-04-23 failed with "App not ready — toolbar not found within 15s" but app.log showed nothing interesting.

Motivation

While investigating why E2E is broken on main today, I pulled the failure screenshots from run 24839322000. Every one shows the ErrorBoundary fallback — "Something went wrong. The notebook encountered an unexpected error." — rendered before the tests ever find the toolbar. The logs around it look healthy:

[INFO] webview: [automerge-notebook] Interactive materialization done
[INFO] webview: [window-focus] Handler stopped
[INFO] webview: [automerge-notebook] Cleanup: flushing and stopping engine
[INFO] webview: [sync-engine] Stopping

That "Cleanup" sequence is the boundary unmounting the thrown subtree. The actual error is invisible. This PR fixes that observability gap.

Approach

error-boundary.tsx lives in /src/ (shared code, no NotebookHost dependency), so I added a module-scope setErrorBoundarySink() setter rather than coupling the component to @nteract/notebook-host. main.tsx installs the sink right after setLoggerHost(host) — same pattern as the other boot-time setters. The sink is wrapped in try { } catch {} so a broken logger can never hide the original error.

Test plan

  • cargo xtask lint --fix passes
  • Next failing CI run on any branch surfaces a [ErrorBoundary] render error: line in e2e-logs/app.log

Does not fix the underlying render error — that's a follow-up PR once we know what's actually throwing. This is purely observability.

componentDidCatch used to log via console.error only. In dev builds the
dev-mode attachConsole() pipes that to the terminal, but packaged / CI
builds swallow it entirely — which is why every E2E spec on 2026-04-23
shows "App not ready — toolbar not found" without any corresponding
error in app.log. The app is rendering the ErrorBoundary fallback
("Something went wrong") and we can't tell what threw.

Adds an opt-in module-scope sink in error-boundary.tsx. main.tsx wires
it to logger.error(...) so the React error and component stack land in
notebook.log alongside the rest of the app's output. Captured by CI as
e2e-logs/app.log, so the next failing run names the offender.

The sink is decoupled from NotebookHost because error-boundary.tsx
lives in /src/ (shared) and can't depend on app-level modules.
@github-actions github-actions Bot added the frontend Webview, React, TypeScript UI label Apr 23, 2026
@rgbkrk rgbkrk merged commit df19f29 into main Apr 23, 2026
17 of 22 checks passed
@rgbkrk rgbkrk deleted the investigate/e2e-app-not-ready branch April 23, 2026 14:32
rgbkrk added a commit that referenced this pull request Apr 23, 2026
* docs(specs): streaming Arrow IPC for DataFrame repr (#1816)

Design for dx emitting a Parquet head + a pull handle for
incremental Arrow IPC continuation, so huge DataFrames render
a first screenful immediately and grow in place.

Key shape:
- Head: 100-ish rows serialized as Parquet through the existing
  dx path. Sift's existing load hits immediately.
- Continuation: new `nteract.dx.stream.<id>` comm. Runtime agent
  pulls Arrow IPC chunks outside the execution-message hot path
  and appends them as blob refs in a new manifest field on the
  same output id.
- Transport is shared with #1815 (query backend).
- No mutable blobs, no ContentRef shape change — chunks are a
  JSON list of existing blob refs inside the manifest.
- Late joiners replay from the CRDT because chunks go through
  normal sync, not a side channel.

* docs: update streaming Arrow IPC spec for runtime-doc crate changes

- RuntimeStateDoc moved from notebook-doc to runtime-doc (#2056)
- CRDT writes go through RuntimeStateHandle (#2059)
- Pull task uses fork()/merge() for async blob work
- Dead broadcasts removed (#2065) - manifest updates propagate via CRDT
- Updated review pointers to current file paths

* docs: fix reserved-comm-namespace pointers in streaming Arrow IPC spec

The namespace rule moved out of CLAUDE.md and now lives in
.claude/rules/architecture.md § "Reserved Comm Namespace:
`nteract.dx.*`". Update the two spec references to point there.

No change to the design itself.

* feat(runtimed-wasm): install console_error_panic_hook on module init

Rust panics inside WASM currently surface to the frontend as an opaque
`__wbg___wbindgen_throw_6b64449b9b9ed33c` stack with wasm-function
indices and no file/line. The error reaches the App ErrorBoundary and
the "Something went wrong" fallback renders, but the cause is
invisible in packaged / CI builds.

This is exactly what's happening on UV Pyproject + UV Prewarmed E2E
today (post-#2103): something in the runtime-doc read path panics
when the daemon syncs a RuntimeState that walks through the full
lifecycle starting → running, and we have no way to name it.

Install `console_error_panic_hook::set_once()` from a
`#[wasm_bindgen(start)]` function so it runs exactly once before
any `NotebookHandle` is constructed. Panics now log with file, line,
message, and a Rust backtrace.

Combined with #2101 (ErrorBoundary → host logger), the next failing
E2E run will emit both the React component stack and the Rust panic
payload into `e2e-logs/app.log`.

Rebuilds the WASM bundle to pick up the hook wiring.

Verification:
- `cargo xtask wasm runtimed` — succeeds
- `deno test --allow-read crates/runtimed-wasm/tests/` — shape test
  still passes (51 filtered + 1 ok, the expected set)

* feat(notebook-app): forward console.error to host logger

The wasm panic hook from the previous commit calls `console.error`.
In dev builds `attachConsole()` from tauri-plugin-log is DEV-only
(see packages/notebook-host/src/tauri/index.ts:280), and the plugin
only bridges Rust log output INTO the browser console — it doesn't
forward browser console OUT to Rust. In packaged / CI builds the
panic message goes to `console.error` and stops there.

Install a small forwarder in main.tsx: wrap `console.error` to also
call `logger.error` (host-log). WASM panics now land in notebook.log
alongside everything else, visible in CI's `e2e-logs/app.log`.

Preserves the original console.error behavior so devtools stays
unchanged. The forwarding call is in a try/catch so a logger failure
can't swallow the original error.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

frontend Webview, React, TypeScript UI

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant