Files
hermes-agent/apps/desktop/src/debug/dev-only.ts
Brooklyn Nicholson 44211f3608 feat(desktop): dev-only render + store churn counters
Adds two dev-only counters that attribute re-renders and store
notifications during an interaction, so a perf claim can be answered with
a number instead of a hunch:

  window.__RENDER_COUNTS__  what re-rendered, and why (props/state/parent)
  window.__ATOM_CHURN__     which store published it, and whether it mattered

The `wasted` column in each is the fix list — components that re-rendered
with no changed input, and stores that published a value equal to the
last one. Both are inert until start(), so idle cost is one branch per
commit and per notify.

React 19.2 removed injectProfilingHooks from react-dom, so the mark*
profiling family is unavailable and onCommitFiberRoot is the only channel
left. <Profiler> can't answer the question either: React invokes onRender
for every Profiler in a committed tree including subtrees that bailed
out, and a bailed-out subtree still reports nonzero actualDuration. This
uses bippy's didFiberRender instead. bippy over react-scan because
react-scan/lite is a thin wrapper over it while the package pulls ~217
transitive deps and floats two on latest.

main.tsx imports the entry statically above react-dom because react-dom
captures the devtools hook at module init — a late install reports
renderers=0 and observes zero commits. Production exclusion is handled by
a build-time alias to a no-op module rather than tree-shaking, since a
static side-effect import can't be eliminated.
2026-07-26 06:10:54 -05:00

20 lines
928 B
TypeScript

// Dev-only diagnostics entry. Statically imported from `main.tsx` ABOVE the
// `react-dom` import — that ordering is load-bearing and non-negotiable.
//
// react-dom captures the devtools hook at MODULE INIT, not at `createRoot`.
// Verified: installing bippy after react-dom has evaluated yields
// `renderers=0` and `commits=0` — the hook object exists but react-dom never
// registered with it. A dynamic `import()` here would resolve a microtask
// after main.tsx's static graph (react-dom included) had already evaluated,
// so it must be a plain static import chain, whose evaluation order ESM
// guarantees.
//
// Production builds don't tree-shake this away (a static side-effect import
// can't be eliminated) — instead `vite.config.ts` aliases this module to
// `dev-only.noop.ts` whenever the build isn't serving dev, so neither bippy
// nor the counters reach a shipped renderer.
import './index'
export {}