Skip to content

fix(tool-server): fall back to raw locations when inspect-element symbolication fails#284

Draft
latekvo wants to merge 1 commit into
mainfrom
fix/inspect-element-source-fallback
Draft

fix(tool-server): fall back to raw locations when inspect-element symbolication fails#284
latekvo wants to merge 1 commit into
mainfrom
fix/inspect-element-source-fallback

Conversation

@latekvo

@latekvo latekvo commented Jun 1, 2026

Copy link
Copy Markdown
Member

Problem

debugger-inspect-element, called with the default resolveSourceMaps: true, returned source: null and code: null for every item whenever Metro symbolication did not yield a usable original location. This made the tool effectively useless in its default mode — the caller got a list of component names with no file:line and no code fragment to edit.

Root cause

Two independent issues compounded:

  1. No raw fallback in the tool. In debugger-inspect-element.ts, the resolveSourceMaps branch only assigned source/code when symbolicate() returned a result. When it returned null (symbolication failed, or was filtered out — see below), the item was left with source: null / code: null, discarding the raw bundled frame the tool already had in item.frame.

  2. Over-broad node_modules rejection in the resolver. In source-resolver.ts, symbolicateFrame did if (frame.file.includes("node_modules")) return null;. That was meant to drop frames that symbolication couldn't map (where Metro echoes the bundle URL back). But it also threw away genuinely-mapped sources whose real path legitimately lives in node_modules — e.g. expo-router / react-navigation route components — turning a perfectly good resolved location into null, which then hit issue (1) and nulled the whole item.

Fix

Two minimal, localized changes:

  • debugger-inspect-element.ts — add an else to the resolveSourceMaps branch that falls back to the raw bundled frame { file: item.frame.file, line: item.frame.line, column: item.frame.col }, mirroring the exact shape produced by the resolveSourceMaps: false branch. When symbolication can't map a frame, the caller now still gets the bundled location instead of null.

  • source-resolver.ts (line ~93) — replace the includes("node_modules") guard with if (/^https?:\/\//.test(frame.file)) return null;. A successful symbolication yields a real file path (kept, including legitimate node_modules route components); a failed one yields the original http(s) bundle URL echoed back (rejected). This keeps the diff to a single line + comment at line ~93 to stay easy to reconcile.

Testing

cd packages/tool-server && vitest run test/metro/source-resolver.test.ts test/debugger/inspect-element-filter.test.ts

Added fail-before / pass-after coverage at two layers:

  • Resolver level (test/metro/source-resolver.test.ts): mocks fetch for /symbolicate and asserts that

    • a frame mapping to a node_modules/.../*.js path is kept (returned that mapped, project-relative location),
    • an echoed-back http(s) bundle URL is rejected (null),
    • a real app-source path is mapped relative to the project root,
    • a thrown request returns null.
  • Tool level (test/debugger/inspect-element-filter.test.ts): drives debuggerInspectElementTool.execute() with a fake JsRuntimeDebuggerApi (no live CDP) and asserts that when symbolicate() returns null, the item retains the raw { file, line, column } (and reads no source fragment); when it returns a mapped location, that location and its code fragment are used.

Verified the fail-before / pass-after cycle by checking out origin/main for the two source files only (keeping the new tests): the 3 new assertions fail on origin/main (source is null for the raw-fallback case; the node_modules-mapped source is null; the echoed bundle URL is wrongly accepted), then pass once the fixes are restored. All 34 tests pass with the fix; the 31 pre-existing tests are unaffected.

tsc --noEmit -p tsconfig.test.json reports no errors in any changed file (the only errors are pre-existing module-resolution artifacts from the symlinked node_modules: pngjs, @argent/native-devtools-android, @argent/update-core).

Known limitation / follow-up

User-defined and route component frames that lack _debugStack under React 19 (where _debugSource was removed) are still null even in raw mode, because there is no bundled frame to fall back to in the first place. That is a separate, riskier change (it requires a different source for the frame entirely) and is intentionally out of scope here.

Note for reconcilers

There are other open branches touching source-resolver.ts (fix/source-resolver-traversal, fix/source-map-ssrf). This change is deliberately confined to the single guard at line ~93 (plus an explanatory comment) to keep it trivially mergeable alongside them.

…bolication fails

debugger-inspect-element with the default resolveSourceMaps:true returned
source:null and code:null for every item whenever Metro /symbolicate either
failed or mapped a frame into node_modules. The resolveSourceMaps branch only
assigned source/code when symbolicate succeeded, so a null result discarded the
raw frame location the tool already had.

Two minimal fixes:

- debugger-inspect-element: when symbolicate returns null, fall back to the raw
  bundled frame (file, line, column) — mirroring the resolveSourceMaps:false
  shape — instead of leaving source/code null.

- source-resolver: the symbolicate guard rejected any path containing
  "node_modules", which threw away genuinely-mapped sources for route
  components that legitimately live in node_modules (expo-router /
  react-navigation). Replace it with a check that only rejects unmapped bundle
  URLs Metro echoes back (http(s) URLs); real file paths, including
  node_modules ones, are kept.

Adds resolver-level coverage for the node_modules-kept and echoed-URL-rejected
cases, plus a tool-level test asserting the raw-frame fallback when
symbolication fails.
@latekvo latekvo force-pushed the fix/inspect-element-source-fallback branch from 3117801 to 1d447e5 Compare June 3, 2026 15:09
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