SIGN IN SIGN UP

fix(tool-server): key Fabric component-tree measurement by nativeTag so tap coords don't collapse to 0.5,0.5 (#286)

## Problem

`debugger-component-tree` returned **every** element with the same tap
coordinate — `(tap: 0.50,0.50)` — on the New Architecture
(Fabric/bridgeless), making the tool's primary output (per-element tap
coordinates) unusable. Reproduced live on an RN 0.81.5 Fabric app; the
tree walk, skip stats, and screen size were all correct, only the
coordinates collapsed.

## Root cause

The injected script (`utils/debugger/scripts/component-tree.ts`)
batch-measures host views and caches each rect under a per-host key
built as `(hi.f ? 'f' : 'p') + hi.n`. On **Fabric**, `getHostInfo`
returns the shadow **node object** as `hi.n` (`{ f: true, n:
fiber.stateNode.node }`), so the key stringifies to `"f[object Object]"`
for *every* host. All hosts collapsed to a single cache entry → only the
first (root) view was measured → every component inherited the root's
full-screen rect → `(x + w/2)/screenW, (y + h/2)/screenH = 0.5, 0.5` for
all. Paper/old-arch was unaffected because there `hi.n` is already a
numeric `nativeTag`.

## Fix

Key the cache by a primitive `nativeTag` on Fabric
(`fiber.stateNode.canonical.nativeTag`, the same field the Paper branch
already uses), with a `WeakMap`-by-identity fallback so distinct shadow
nodes can never share a key even if `nativeTag` were ever absent.
`getHostInfo` now carries an explicit `key`, and the three cache-key
derivations use it. Paper/old-arch behavior is unchanged.

## Testing

**Live (first-hand), on a running RN 0.81 Fabric app** — walking the
live fiber tree:

| | Fabric host nodes | distinct measure keys |
|---|---|---|
| old key (`'f' + node`) | 2140 | **1** (`"f[object Object]"`) |
| new key (`'f' + nativeTag`) | 2140 | **2140** |

**Unit** — adds `test/debugger/component-tree-script.test.ts`, which
evaluates the injected script against a mocked Fabric tree (the existing
`component-tree.test.ts` only covered the pure post-processor with
pre-filled rects, which is why this shipped). The collapse is exercised
on both Fabric measure paths with **tagless** hosts (no numeric
`nativeTag`), so the cache key is forced onto the new
`WeakMap`-by-identity fallback — with a numeric tag the buggy `'f' +
node` key was already distinct and never collapsed. `vitest run` → 6/6
pass with the fix; reverting `component-tree.ts` to `main` fails both
collapse tests with `{ y: 100 }` where `{ y: 300 }` is expected (the
second component inheriting the first's rect — exactly the collapse).
I
Ignacy Łątka committed
b5fc1f2a505dd99a998ea2930cfa201596ffe7b1
Parent: 9fc1810
Committed by GitHub <noreply@github.com> on 6/18/2026, 9:49:53 AM