Skip to content

feat(browser): @maple-dev/browser/react with error boundary and router adapters - #1153

Merged
Makisuo merged 3 commits into
feat/browser-sdk-http-errors-cspfrom
feat/browser-sdk-react
Sep 29, 2026
Merged

Makisuo merged 3 commits into
feat/browser-sdk-http-errors-cspfrom
feat/browser-sdk-react

Conversation

@Makisuo

@Makisuo Makisuo commented Sep 29, 2026 •

Copy link
Copy Markdown
Collaborator

Part 7 of the browser SDK stack. Based on the HTTP errors / CSP PR.

Apps had to hand-wire startNavigation/endNavigation into their router and write their own error boundary. The new @maple-dev/browser/react subpath does both.

What changes

  • MapleErrorBoundary reports a render error once, as a react.render_error span with the component stack in maple.react.component_stack (trimmed to 2 kB), and renders a fallback node or ({ error, reset }) => node.
  • mapleReactErrorHandler() for React 19's createRoot onCaughtError / onUncaughtError / onRecoverableError. It shares the SDK's dedupe, so an error a boundary already reported is not reported twice.
  • instrumentReactRouter(router) for data routers (createBrowserRouter): a navigation starts when the router starts loading and ends when its loaders settle, named by the template built from the matched routes (navigate /projects/:id). Routes without loaders, which never enter loading, are still spanned.
  • instrumentTanStackRouter(router): onBeforeNavigate → onResolved, named by the leaf route's fullPath (navigate /projects/$projectId). Search-only changes are not navigations, and the router's own initial-load event does not restart the page load as a navigate.

Packaging

  • Routers are typed structurally, so there is no router dependency. A real @tanstack/react-router router was type-checked against TanStackRouterLike (scratch file, not committed).
  • react is an optional peer dependency.
  • The entry shares one chunk with the main entry (dist/react.mjs imports the same src-*.mjs as index.mjs), so navigation and dedupe state stay single-instance.
  • apps/web uses the Effect SDK, not @maple-dev/browser, so there is nothing to dogfood there.

Testing

Browser tests with real React 19 rendering: the boundary reports once with the component stack, renders the fallback and resets; the root handler reports once. React Router and TanStack Router adapters are tested against fakes that mirror each router's state and event sequence (page load, loading navigations, loader-less routes, search-only changes, initial-load re-announce).


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

Summary by CodeRabbit

  • New Features

    • Added React integrations for reporting component errors, displaying and resetting an error fallback, and tracking navigation with React Router or TanStack Router.
    • Added an optional React package entry point, compatible with React 18 and later.
  • Documentation

    • Added setup guidance and examples for using the React integrations, including React 19 root error handling.

…r adapters

Apps had to hand-wire startNavigation/endNavigation into their router and
write their own error boundary. The new subpath does both.

- MapleErrorBoundary reports a render error once, as a react.render_error
  span with the component stack in maple.react.component_stack, and
  renders a fallback node or ({ error, reset }) function.
- mapleReactErrorHandler() for React 19's createRoot onCaughtError /
  onUncaughtError / onRecoverableError; dedupes with the boundary.
- instrumentReactRouter(router) for data routers: a navigation starts when
  the router starts loading and ends when loaders settle, named by the
  route template built from the matched routes (navigate /projects/:id).
  Routes without loaders, which never enter loading, are still spanned.
- instrumentTanStackRouter(router): onBeforeNavigate to onResolved, named
  by the leaf route's fullPath; search-only changes are not navigations,
  and the router's own initial-load event does not restart the page load.

Routers are typed structurally, so there is no router dependency; a real
TanStack router was checked against the interface. react is an optional
peer dependency. The entry shares one chunk with the main one, so
navigation and dedupe state stay single-instance.
@coderabbitai

coderabbitai Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Review in Change Stack →

Navigate logical layers of code changes, visualize relationships, and explore their blast radius.

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 4fa3e9a0-116c-49aa-bc76-f32986fc6d33

📥 Commits

Reviewing files that changed from the base of the PR and between cad4838 and 9ec3c09.

⛔ Files ignored due to path filters (1)
  • bun.lock is excluded by !**/*.lock
📒 Files selected for processing (6)
  • docs/browser-sdk.md
  • packages/browser/README.md
  • packages/browser/package.json
  • packages/browser/src/react.browser.test.ts
  • packages/browser/src/react.ts
  • packages/browser/tsdown.config.ts

Included review availability: This review used your included allowance. Your plan provides up to 4 included reviews per hour; 1 remain after this review.


📝 Walkthrough

Walkthrough

The browser package adds a React entry point with an error boundary, a React 19 root error handler, and React Router and TanStack Router navigation adapters. It adds browser tests, package exports and build configuration, and integration documentation.

Changes

React integration

Layer / File(s) Summary
React error reporting
packages/browser/src/react.ts, packages/browser/src/react.browser.test.ts
The error boundary reports render errors, displays a fallback, and supports reset. The root handler reports errors with a source and optional component stack. Tests cover error reporting, fallback reset, and duplicate-report suppression.
Router navigation instrumentation
packages/browser/src/react.ts, packages/browser/src/react.browser.test.ts
The router adapters track initial loads and navigation spans, derive span names from matched routes, and return unsubscribe functions. Tests cover route changes and search-only changes.
Package exposure and integration documentation
packages/browser/package.json, packages/browser/tsdown.config.ts, packages/browser/README.md, docs/browser-sdk.md
The build and package exports include the React entry point, and React is an optional peer dependency. The documentation describes the error handlers and router adapters.

Priority: ➖ Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Feature

Sequence Diagram(s)

sequenceDiagram
  participant React
  participant MapleErrorBoundary
  participant MapleBrowser
  React->>MapleErrorBoundary: Deliver render error
  MapleErrorBoundary->>MapleBrowser: Report error and component stack
  MapleErrorBoundary-->>React: Render fallback
Loading
sequenceDiagram
  participant ReactRouter
  participant instrumentReactRouter
  participant MapleBrowser
  ReactRouter->>instrumentReactRouter: Publish router state
  instrumentReactRouter->>MapleBrowser: Track navigation span
Loading

Suggested reviewers: jeremyfunk

Merge Risk: ⚪ Minimal · up to 9ec3c

This adds an opt-in React subpath with error reporting and router adapters. No concrete merge-blocking problem was identified in the supplied evidence.

Security Architecture Review

Security architecture risk: 🔵 Low · up to 9ec3c

The integrations reuse existing reporting, consent and URL-sanitization controls. Navigation cleanup leaves a bounded ownership gap, while packaged runtime sharing and concurrent-router behavior remain unverified. No introduced security vulnerability was established.

Retained concerns

  • Low · reliability · observed: Unsubscribing either adapter during an open navigation removes listeners without releasing the shared navigation span. Subsequent traced work can inherit that stale parent until another navigation, page exit, explicit completion or SDK reset. The new automated lifecycle therefore leaves a bounded telemetry-ownership and failure-containment gap.
Security review details

Security Blast Radius

  • inferred — The new exposure is opt-in client-side telemetry from applications adopting the React entrypoint. It uses the existing configured tracing provider and ingest destination; the inspected integration does not add service credentials, deployment authority or a separate export channel.

Security Findings and Attack Paths

  • inferred — Browser-controlled locations and render failures gain new automatic producers, but the inspected paths still pass through existing sanitization, error filtering and consent controls. An introduced authorization bypass or verified sensitive-data disclosure was not established; component-stack content and packaged runtime identity remain limitations.

Trust Boundaries and Controls

  • observed — captureException deduplicates error objects and applies shouldCapture before creating spans. ConsentSpanExporter drops exports without consent and excludes spans begun before the current consent grant. These controls remain downstream of the React reporting path.

Resilience and Maintainability Implications

  • observed — Stale navigation ownership can affect trace attribution, but later navigation and reset provide recovery. Navigation creation requires a live consent-authorized tracer, and export remains consent-gated; the cleanup gap does not itself demonstrate additional privileges or a consent bypass.

Hardening Proposals

  • proposed — Define ownership-aware adapter disposal and completion correlation so cleanup cannot strand its navigation or close another owner's span. Validate interruption, unsubscribe and superseded-event behavior against supported routers.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 7 functions across 3 files. (3 skipped: 3… Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely identifies the new @maple-dev/browser/react entry point, error boundary, and router adapters, which are the main changes in the pull request.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Full details: Docstring Coverage

Explanation

Docstring coverage is 71.43% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 7 functions across 3 files. (3 skipped: 3 unsupported.)

  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@maple-review-bot

maple-review-bot Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Note

A newer push replaced 5091408 before its review finished. The latest commit is reviewed in a new comment.

@maple-review-bot

maple-review-bot Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Maple review

Confidence 4/5 · likely safe to merge
Additive, annotation-only SDK surface with browser tests for every new behavior; no app wires it yet.
quality 100/100 · no findings · tests covered · risk medium · 2/2 new units observable

Adds a @maple-dev/browser/react entry with a reporting error boundary, a React 19 root error handler, and React Router / TanStack Router adapters that drive startNavigation/endNavigation. Self-contained and additive; safe to merge.

  • MapleErrorBoundary reports one react.render_error span and renders a node or ({error, reset}) fallback
  • mapleReactErrorHandler() wires React 19 root error options into the shared dedupe
  • instrumentReactRouter spans loading navigations, named by matched route template
  • instrumentTanStackRouter spans onBeforeNavigate → onResolved, ignoring search-only changes
What was checked
  • Dedupe really is shared: boundary reports through captureException, whose reported WeakSet is the same one the root handler checks (packages/browser/src/errors.ts:49,116)
  • Component-stack attribute is trimmed before it reaches the span (packages/browser/src/react.ts:17); maple.react.component_stack matches the spelling apps/web/src/components/app-error-boundary.tsx al…
  • Name templates are redacted by the existing scrubUrl in endNavigation, so no concrete URL leaks into the span name (packages/browser/src/navigation.ts:108)
Observability coverage: 2 of 2 changes observable
Change Kind Observable Evidence
Render errors from MapleErrorBoundary and the React 19 root handler client error span yes Routes through captureException → one-off span react.render_error, status Error, maple.exception.source set (packages/browser/src/errors.ts:96-108)
Navigations driven by instrumentReactRouter / instrumentTanStackRouter client span yes Delegates to MapleBrowser.startNavigation/endNavigation, so pageload/navigate spans come from packages/browser/src/navigation.ts:80-111

0174a9d · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple to ask about one.

@Makisuo
Makisuo added this pull request to stack #1161 September 29, 2026 21:22
@maple-review-bot

maple-review-bot Bot commented Sep 29, 2026 •

Copy link
Copy Markdown

Maple review

Confidence 5/5 · safe to merge
quality 100/100 · no findings · tests covered · risk low · 2/2 new units observable

Only packages/browser/src/error-filters.ts and its test changed since the last review: matches now resets lastIndex before test, so a global or sticky ignore/allowUrls/denyUrls regex drops every matching error instead of every other one. The fix and its regression test are both correct; safe to merge.

  • matches resets pattern.lastIndex before test, fixing stateful g/y filter regexes
  • New test asserts a /chunk/gi ignore drops three consecutive errors
What was checked
  • matches (error-filters.ts:55) is the only regex call in the file; BENIGN_MESSAGES and EXTENSION_URL (lines 37-38) carry no g/y, so they need no reset
  • The regression test at error-filters.test.ts:83 fails against the pre-fix code: the old pattern.test kept the 2nd of three matches
  • shouldCapture still treats a missing top frame as keep, and allowUrls/denyUrls are still top-frame only (error-filters.ts:82-83)
Observability coverage: 2 of 2 changes observable
Change Kind Observable Evidence
React error boundary + root error handler reporting error span yes reportReactError -> MapleBrowser.captureException with name react.render_error (react.ts:11-21)
Router navigation spans (React Router, TanStack Router) navigation span yes MapleBrowser.startNavigation/endNavigation (react.ts:112-190)

9ec3c09 · Updated on every push. Reply "won't fix" to dismiss a finding, or mention @maple to ask about one.

@Makisuo
Makisuo merged commit 9e0267f into main Sep 29, 2026
42 of 43 checks passed
@Makisuo
Makisuo deleted the feat/browser-sdk-react branch September 29, 2026 21:44
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.

1 participant