fix(profiler): accept the component names the analyze report actually prints - #663
Open
filip131311 wants to merge 2 commits into
Open
fix(profiler): accept the component names the analyze report actually prints#663filip131311 wants to merge 2 commits into
filip131311 wants to merge 2 commits into
Conversation
react-profiler-analyze strips Forget()/Memo()/ForwardRef() wrappers for readability, then the drill-down tools matched on the raw DevTools name with string equality — so the report named a component and the next call refused it. React Compiler is on by default in Expo SDK 54, so this hit essentially every new Expo app. Resolution now matches a query against each recorded name's DISPLAY form rather than stripping both sides. Stripping both sides looks equivalent but is not: it would quietly answer a query for Forget(Foo) with Memo(Foo) when only the latter exists, and it is not even reliable, because the display stripper stops after four wrappers so a deeply wrapped name still displays with one. Comparing against the display form compares against exactly the string that was printed. Exact matches always win and are never widened. Where a bare name and a wrapped one coexist — StaticContainer and Memo(StaticContainer) both appear in a single ordinary session — the exact name resolves and the output names the sibling rather than silently showing one of two. When a stripped name maps to several distinct fibers the tools refuse and list the exact strings to retry with; those fibers are not merged, because a combined total would not describe any real component. The same mismatch ran in the opposite direction and made the workflow a deadlock: the AST index is keyed on bare source identifiers, so react-profiler-component-source accepted only the name the query tools rejected. There was no single name an agent could carry from analyze through to reading source. Both AST lookups now fall back through the stripped name, plus the expo-router file suffix (the report shows TabTwoScreen(./(tabs)/explore.tsx); the source declares TabTwoScreen). Loosening is safe there in a way it is not for commit data — a wrong guess against the index simply misses. That also fixes react-profiler-analyze silently dropping sourceLocation for every wrapped component, which is why the File column read '—' for project files that were sitting right there.
This was referenced Aug 1, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #632.
The problem
react-profiler-analyzestripsForget()/Memo()/ForwardRef()wrappers for readability, then the drill-down tools matched the raw DevTools name with===. The report names a component and the very next call refuses it. React Compiler is on by default in Expo SDK 54, so this hits essentially every new Expo app.Reproduced live
Real session on an Expo SDK 54 app (
experiments.reactCompiler: true, RN 0.81.5) — 821 fiber renders, 16 commits. The report printed`ParallaxScrollView` [React Compiler]:The two messages differ, and that is the proof: "not found in commit data" means the name never matched; the other is emitted only after a match.
Two things beyond the report:
profiler-commit-query mode=by_componenthas the same bug — same refusal, full table under the raw name.Memo()andForwardRef()are stripped too; the same report printed`InnerScreen` [forwardRef].The workflow was a deadlock
The AST index is keyed on bare source identifiers, so
react-profiler-component-sourceaccepts only the name the query tools reject:IconSymbolForget(IconSymbol)There was no single name an agent could carry from analyze → drill-down → read source. Fixing only the query half would have left the issue's stated workflow broken.
Approach
Match a query against each recorded name's display form, rather than stripping both sides.
Stripping both sides looks equivalent and is not:
Forget(Foo)withMemo(Foo)when only the latter exists — the agent asked about the compiler-wrapped fiber and silently got the memo one;Comparing against the display form compares against exactly the string that was printed.
Exact matches always win and are never widened.
StaticContainerandMemo(StaticContainer)both appear in one ordinary 18-second session — I checked all 70 distinct names in the dump, and that is the one real collision. Asking for the bare name returns the bare fiber, and the output now names the sibling instead of silently showing one of two.Ambiguity is refused, not merged. Where a display name maps to several distinct fibers the tools list the exact strings to retry with.
Memo(X)andXare often one component's two fibers whose durations nest, so a merged total describes no real component; and picking the largest silently picks the memo wrapper's inclusive time, which is the wrong fiber for self-cost analysis.The AST lookups get the reverse fallback: raw → stripped → expo-router file suffix removed (the report shows
TabTwoScreen(./(tabs)/explore.tsx); the source declaresTabTwoScreen). Loosening is safe there in a way it is not for commit data — a wrong guess against the index simply misses and returnsfound: false.Verified against the live session
component_cpu ParallaxScrollViewForget(ParallaxScrollView)— was refusedby_component ParallaxScrollView> Resolved … (React Compiler). Either name works here.by_component Forget(ParallaxScrollView)by_component ParallaxScroll(typo)Forget(ParallaxScrollView)as a re-queryable suggestioncomponent-source Forget(IconSymbol)found: true,icon-symbol.tsx:28— wasfound: falsecomponent-source Forget(TabTwoScreen(./(tabs)/explore.tsx))found: true,app/(tabs)/explore.tsx:12Visible report change (intentional)
Fixing the analyze-side lookup means wrapped components stop losing their source location. In the live session exactly one row changes — every other row is a node_modules component with no user source:
Deliberately out of scope
profiler-combined-report.ts:448correlates leaks to components by substring-matching raw names into native symbols, so wrapped components are structurally excluded. Same bug class — affected, deferred, not unaffected.react-profiler-stopjoins fiber metadata by name across two different namespaces, so wrapped fibers loseparentName,hookTypesandisCompilerOptimizedentirely. Measured on this session: 41 wrapped fibers, 0 with any of the three, versus 181/272 bare fibers carryingparentName. That is why the report's header saysReact Compiler: ✗while the same document tags six components[React Compiler].Checks
annotateComponentNamemove is a pure refactor — verified output-identical before anything else changed03-tag.ts'sANIMATED_PATTERNdeliberately left alone (it matches unanchored and coversMotion/Transition, which the stripper must not)description:fields unchanged, so the SpiderShield average is untouched; skills gate 10.0; prettier, eslint, both typechecks clean