## What
Adds type-aware linting to the workspace. Until now the only static gate
on JS/TS was Prettier (formatting) and `tsc` — there was no linter, so
the whole class of bugs `tsc` doesn't catch (floating promises, misused
promises, throwing non-Errors, unsafe stringification, dead code) went
unguarded across ~450 TS files.
- **`eslint.config.mjs`** — flat config. `recommendedTypeChecked` runs
on the TS sources via an explicit `project` list (each package keeps
tests in a separate `tsconfig.test.json` that `projectService` can't
auto-discover, so it's listed explicitly). Type-checked rules are
disabled for the loosely-typed plain-JS build/dev scripts. The
`argent-private` submodule and build artifacts are ignored (mirrors
`.prettierignore`).
- **`lint` / `lint:fix`** npm scripts.
- **`.github/workflows/lint.yml`** — builds the workspace (type-aware
rules need the referenced projects' declarations) then runs `eslint .`.
Node 24 (ESLint 10 requires `^20.19 || ^22.13 || >=24`).
## Rule calibration
This is an adoption PR, so it's deliberately calibrated rather than
maximal:
- **Kept on (bug-catchers):** `no-floating-promises`,
`no-misused-promises` (with `checksVoidReturn: false` to drop the noisy
callback variant), `only-throw-error`, `no-base-to-string`,
`prefer-promise-reject-errors`, `no-unused-vars` (`_`-prefix escape
hatch), plus the ESLint recommended set.
- **Parked off as documented debt:** the high-volume style/`any`-family
rules — `no-unsafe-*`, `require-await`, `no-unnecessary-type-assertion`,
`restrict-template-expressions`, `unbound-method`, `no-explicit-any`,
etc. These flag broad classes of pre-existing code and are out of scope
here; each is commented as a ratchet target for follow-up passes.
All findings from the kept-on rules are fixed in this PR (dead
code/imports, error-cause chaining, **3 genuine floating promises**,
non-Error throws, unsafe stringification). Stale unused `eslint-disable`
directives left over in the tree were removed by `--fix`.
## Verification
Locally, all green: `eslint .` (0 errors), `tsc --build`,
`typecheck:tests`/`typecheck:scripts`, the full `vitest` suite (no
behavior change from the fixes), and `prettier --check`.
## Not included (intentionally)
Other static-analysis gates discussed (CodeQL, dependency-audit,
ShellCheck, actionlint) are out of scope for this PR.
## Problem
`debugger-inspect-element` returned `{"error":"no host fiber"}` at every
coordinate on a React Native 0.81.5 app (Hermes, old architecture /
legacy bridge, React Compiler). Verified live on a real device.
## Root causes (three layers, each verified live)
1. **Host-fiber resolution** - `findHostFiber` only recognised old-arch
host fibers via `stateNode.canonical`, but RN 0.81 bridge host fibers
expose `stateNode._nativeTag` with no `canonical`, so it found nothing
and threw "no host fiber". Fixed by also matching `_nativeTag` (mirrors
the working `component-tree.ts` getHostInfo). On the real tree: 87/87
host fibers had `_nativeTag` and 0 had `canonical`.
2. **Wrong anchor** - even with the host fiber resolved, the first host
fiber is a sibling subtree that does not contain the touch point, so the
renderer's native `findSubviewIn` resolved nothing for *every*
coordinate (touchedViewTag undefined). Fixed by anchoring at the
**FiberRoot container** (its `containerTag`), the universal ancestor.
Fabric and older Paper (no containerTag) keep the host-fiber anchor.
3. **Changed data shape** - RN 0.81 no longer returns
`data.closestInstance`/`hierarchy`; it returns `data.componentStack` (a
React stack string). The old extraction produced empty results. Now
parsed into items; bundle source locations are symbolicated by the tool
layer exactly like `_debugStack` frames.
## Result
Live on a real RN 0.81 app, inspecting a point now returns the real
component hierarchy (170 frames: `SplashScreen`, `ErrorBoundary`,
`LoggedOut`, `NativeStackNavigator`, `HomeTabNavigator`, `SceneView`,
`NavigationProvider`, ...) with source locations.
## Safety / scope
- Fabric (new-arch) and iOS paths unchanged; canonical-publicInstance
handling is byte-for-byte preserved (incl. the lazily-null Fabric case).
- Distinct from #284 (symbolication fallback - a different, later
failure mode).
- Unit tests added for: `_nativeTag` host-fiber resolution, container
anchoring, componentStack parsing, and the Fabric null-publicInstance
no-regression case. The test harness evaluates the generated script, so
it exercises the real emitted code.
- Validated end-to-end against a live emulator, not only unit tests.
## Summary
Companion to #316. `debugger-inspect-element` has the same
hardcoded-renderer assumption as the component-tree script, so it
inspects the wrong React renderer on multi-renderer apps.
## Root cause
`makeInspectScript`
(`packages/tool-server/src/utils/debugger/scripts/inspect-at-point.ts`)
picks:
```js
var renderer = Array.from(hook.renderers.values())[0];
var roots = hook.getFiberRoots(1);
var root = Array.from(roots)[0];
```
On React Native **New Architecture** apps that also use a secondary
React reconciler (`react-native-skia`, `react-native-svg`,
`react-three-fiber`, …), that library registers its own renderer and
frequently takes id `1`. `renderer` then points at the Skia/SVG
reconciler and `getFiberRoots(1)` returns only its roots, so
`renderer.rendererConfig.getInspectorDataForViewAtPoint(...)` runs
against the wrong tree and returns no app component at the touch point.
## Fix
Enumerate `hook.renderers`, walk each renderer's roots, and select the
**(renderer, root)** pair with the largest fiber subtree (the real app
UI) before inspecting — then call `getInspectorDataForViewAtPoint` on
that renderer. Falls back to the previous first-renderer behavior if
enumeration yields nothing.
Unlike the component-tree script, the Fabric layout path here was
already correct (it uses
`hostFiber.stateNode.canonical.publicInstance`), so no measurement
change is needed — this PR is purely the renderer/root selection.
## Testing
- Full `npm run build` (`tsc --build`) passes; the modified file
typechecks under `strict`.
- Same environment as #316: a React 19.2 / Fabric (bridgeless) app whose
DevTools hook exposes a `react-native-skia` renderer at id 1 alongside
the real `react-native` UI renderer. With the fix, inspection resolves
against the app renderer instead of the Skia one.
No change to the output schema or the Paper path.
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>