Files
5chan/.codex/skills/profile-browsing/SKILL.md
T
Tommaso CasaburiandGitHub da03239bb2 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
2026-07-30 00:29:12 +07:00

168 lines
8.2 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
name: profile-browsing
description: Profile app performance while browsing, collecting Web Vitals and React rerender data via react-scan. Orchestrates parallel profiler subagents via playwright-cli to capture navigation timing, long tasks, layout shifts, LCP, React commit counts, render bursts, and per-component render data. Use when profiling browsing performance, finding bottlenecks, diagnosing excessive rerenders, or auditing page performance.
---
# Profile Browsing Performance
Two-layer profiling: browser-level symptoms (Web Vitals, long tasks, scroll jank) and React-level diagnosis (commit counts, render bursts, per-component render data from react-scan). Each profiler subagent runs in its own browser session and context window.
## Prerequisites
- Dev server running at https://5chan.localhost (`yarn start` via Portless)
- `playwright-cli` installed (`npm install -g @playwright/cli@latest`)
**IMPORTANT:** The orchestrator (you) is responsible for ensuring exactly ONE dev server is running. Profiler subagents must NEVER start a dev server themselves.
### react-scan (already configured)
`src/lib/react-scan.ts` runs react-scan in dev mode. It:
- Highlights rerendering components visually (toolbar + overlay)
- 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.
No additional setup needed — react-scan is already a devDependency and imported in the entry file.
## Step 0: Ensure Dev Server is Running
Before spawning any profiler subagents, verify exactly one dev server is available:
```bash
# Check if the dev server is reachable
curl -sf https://5chan.localhost -o /dev/null && echo "OK" || echo "NOT RUNNING"
```
- If **OK**: proceed to Step 1.
- If **NOT RUNNING**: start one instance with `yarn start` (backgrounded), then poll until it responds. Do NOT start more than one.
- If a dev server is already running on a different port (check `ps aux | grep vite`), reuse it — do not start another.
## Step 1: Define Route Batches
Split routes into batches of 24 for parallel profiling.
**Default batches** (adjust boards as needed):
| Batch | Session | Routes | Focus |
|-------|---------|--------|-------|
| 1 | `prof-1` | `/all`, `/all/catalog` | Multi-board feed + catalog |
| 2 | `prof-2` | `/biz`, `/biz/catalog` | Single board feed + catalog |
| 3 | `prof-3` | `/pol`, `/pol/catalog`, `/g`, `/g/catalog` | Board switching (feed reloads) |
Keep batches balanced. Add thread views (`/:boardIdentifier/thread/:cid`) as needed.
## Step 2: Spawn Profiler Subagents
Read the profiler subagent definition at `.codex/agents/profiler.toml`. Then spawn one `profiler` subagent per batch **in parallel** using Codex's current delegation tool:
```
For each batch, create a subagent request:
agent_type: "profiler"
prompt: |
Session name: "prof-N"
Routes to profile: /route1, /route2, ...
Any non-default app URL or extra profiling constraints
```
Spawn up to 4 subagents simultaneously. Each opens its own browser session, navigates routes, scrolls, collects both Web Vitals and react-scan data per route, and returns a structured issues list.
**Trade-off:** Parallel is faster but may skew timing results under heavy machine load. For precise measurements, spawn sequentially.
## Step 3: Merge Results
Collect structured output from each subagent and merge:
1. Concatenate all Critical / Warning / React Rerenders / Scroll Jank / Info items
2. Combine per-view summary tables into one
3. Merge react-scan component data across routes (same component appearing in multiple routes = sum counts)
4. Deduplicate shared issues (e.g., same slow resource across routes)
5. Sort by severity (Critical first)
## Step 4: Final Report
```markdown
## Performance Profile Results
### Critical
- [metric]: [value] at [route] — [what likely needs fixing]
### Warning
- [metric]: [value] at [route] — [what likely needs fixing]
### React Rerenders
- [route]: [N] commits during load, [M] during scroll — [likely cause]
- Render bursts detected at [routes] — suggests cascading state updates
- Top rerendering components (react-scan):
- [ComponentName]: [total count] renders across [routes], [time]ms total
- [ComponentName]: [total count] renders across [routes], [time]ms total
### Scroll Jank
- [route]: [N] long tasks during scroll (max [X]ms), [M] React commits — [likely cause]
### Info
- [observations]
### Per-View Summary
| View | Nav (ms) | Long Tasks | CLS | LCP (ms) | Commits | Scroll Commits | Bursts | Top Component |
|------|----------|-----------|-----|-----------|---------|----------------|--------|---------------|
| /all | ... | ... | ... | ... | ... | ... | ... | ... |
```
## Interpreting React Metrics
| Signal | Likely cause | Fix direction |
|--------|-------------|---------------|
| High commits, no long tasks | Frequent cheap rerenders | `React.memo`, stabilize props |
| High commits + long tasks | Expensive rerenders | Profile render cost, split components |
| High scroll commits | Scroll/intersection observer triggering renders | Throttle handlers, memoize list items |
| Render bursts (>5 in 100ms) | Cascading state updates | Batch updates, review Zustand selectors |
| react-scan: component with >30 renders | Missing memoization or unstable references | `useMemo`/`useCallback`, check parent renders |
| react-scan: component with >50ms time | Expensive render function | Split component, move work out of render |
## Element-source follow-up
When `react-scan` identifies a rerender hotspot but you still need the exact file behind a concrete DOM node, hand off to `$inspect-elements`.
```bash
playwright-cli -s=prof-followup eval "async el => JSON.stringify(await window.__ELEMENT_SOURCE__.resolve(el))" e7
```
Use `source.filePath` as the direct edit target and `stack` to understand which parent components own the node.
## Step 5: Cleanup
After profiling is complete and the report is delivered, verify no orphaned processes were left behind:
```bash
# Check for any Vite dev servers started during profiling
ps aux | grep 'vite.*--port' | grep -v grep
```
- If the dev server was already running before Step 0, leave it alone.
- If the orchestrator started the dev server in Step 0, kill it now.
- If there are multiple Vite processes (should never happen), kill the extras and warn the user.
Also close any leftover playwright-cli sessions:
```bash
# Close any profiling sessions that weren't properly closed
playwright-cli -s=prof-1 close 2>/dev/null
playwright-cli -s=prof-2 close 2>/dev/null
playwright-cli -s=prof-3 close 2>/dev/null
```
## Notes
- **Session isolation**: Each subagent uses a named playwright-cli session (`-s=prof-N`).
- **Context isolation**: Each subagent runs in its own context window.
- **Per-route collection**: Data resets on each `goto` — the profiler collects before navigating away.
- **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.
- **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).