--- 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) The app has `react-scan` set up in `src/lib/react-scan.ts` with `report: true`. In dev mode it: - Highlights rerendering components visually (toolbar + overlay) - Tracks per-component render counts and times internally - Exposes `window.__getReactScanReport()` for programmatic collection 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 2–4 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 `.cursor/agents/profiler.md`. Then spawn one `profiler` Task per batch **in parallel** (single message, multiple Task calls): ``` For each batch, create a Task: subagent_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. - **Without react-scan**: If `__getReactScanReport` returns null, the profiler falls back to commit counts + render bursts (still useful, just no component names).