perf: reduce browsing rerenders and startup cost (#1189)

* chore(profiling): collect serializable react-scan reports

* perf(routing): rerender only when directory winner changes

* perf(zustand): remove shallow warning hot path

* perf(state): narrow directory lifecycle updates

* perf(home): avoid redundant stats render work

* perf(posts): isolate live loading subscriptions

* perf(chrome): skip unchanged shell rerenders

* fix(profiling): drop unsupported render metric

* perf(feeds): skip unchanged loading state renders

* perf(home): stop resolved stats collector renders

* chore(agent run): record rerender verification

* perf(home): defer stats requests until metadata resolves

* perf(startup): load protocol modules on demand

* chore(agent run): record acquisition and startup profiles
This commit is contained in:
Tommaso Casaburi
2026-07-30 00:29:12 +07:00
committed by GitHub
parent 27cc4767c6
commit da03239bb2
33 changed files with 1274 additions and 380 deletions
+8 -4
View File
@@ -16,10 +16,14 @@ Two-layer profiling: browser-level symptoms (Web Vitals, long tasks, scroll jank
### react-scan (already configured)
The app has `react-scan` set up in `src/lib/react-scan.ts` with `report: true`. In dev mode it:
`src/lib/react-scan.ts` runs react-scan in dev mode. It:
- Highlights rerendering components visually (toolbar + overlay)
- Tracks per-component render counts and times internally
- Exposes `window.__getReactScanReport()` for programmatic collection
- Accumulates per-component render counts and times via react-scan's `onRender` option
- Exposes `window.__getReactScanReport()` and `window.__resetReactScanReport()` for programmatic collection
`__getReactScanReport()` returns a plain object: `{ ComponentName: { count, time } }`.
**Do not use react-scan's own `getReport()`.** It reads `Store.legacyReportData`, which react-scan 0.5.3 never writes to, so it always returns an empty `Map`. The live `Store.reportData` is no better: it is only populated while the toolbar is visible *and* a component is manually focused in the inspector, neither of which holds under automation. The app's `onRender` collector exists precisely because of this. Also note a `Map` cannot be serialized — `JSON.stringify(new Map())` is `"{}"` regardless of contents — which is why the collector returns a plain object.
The profiler's `addInitScript` sets `window.__PROFILING__ = true` before the app loads, which tells react-scan to disable its toolbar and sounds during automated runs.
@@ -160,4 +164,4 @@ playwright-cli -s=prof-3 close 2>/dev/null
- **addInitScript persistence**: Instrumentation re-injects automatically in each new document.
- **Tracing**: Each subagent produces a `trace.zip` viewable in [Trace Viewer](https://trace.playwright.dev).
- **Board codes**: `biz`, `pol`, `g`, `a`, `v`, etc. map to community addresses via the directory.
- **Without react-scan**: If `__getReactScanReport` returns null, the profiler falls back to commit counts + render bursts (still useful, just no component names).
- **Empty react-scan report**: react-scan is a dynamic import, so `__getReactScanReport()` returns `{}` for the first moment after a `goto`. If it is empty, wait ~1s and re-read before falling back to commit counts + render bursts (still useful, just no component names).