--- name: react-prod-state-mismatch-bisection description: Diagnose React SPA prod-build state mismatches (state-derived props update but sibling output stays stale) by driving a real browser and bisecting per-render state via throwaway source instrumentation. --- # Prod React state-mismatch bisection Use when a built SPA shows an internally inconsistent DOM — e.g. `aria-current` flips while a sibling conditional slot stays stale, a click handler runs with zero network activity, jsdom tests pass but the prod journey fails. Reasoning from source fails here; only per-render state truth resolves it. ## Steps 1. **Drive the real browser, not tests.** Throwaway `@playwright/test` chromium script beside the package that installs it (e.g. `tests/`), seeded via direct API calls + cookie injection: ```js import { chromium } from "@playwright/test"; // resolve from tests/ node_modules const ctx = await browser.newContext(); await ctx.addCookies([{ name, value, domain: "127.0.0.1", path: "/" }]); page.on("console", ...); page.on("pageerror", ...); page.on("request", ...); ``` Run it from that directory; a script at repo root or /tmp hits `ERR_MODULE_NOT_FOUND` for workspace deps. 2. **Fiber prop probe** — call the handler outside React events to split "delegation dead" from "handler wrong", and read the props the fiber holds: ```js await page.evaluate(() => { const el = document.querySelector('[data-testid^="thing-"]'); const key = Object.keys(el).find((k) => k.startsWith("__reactProps$")); el[key].onClick(); // or .onChange({target:{value:"x"}}) }); ``` 3. **If props updated but output is inconsistent, suspect falsy-equality, not React.** `undefined === undefined` yields `"true"` for `el.id === selectedId` highlights — a fake "state updated" signal. Dump the slot's raw `innerHTML`, don't trust the derived attribute. 4. **Instruments beats deduction:** insert temporary `console.log`s in the component source (one in each state-mutating callback, one at render logging the state), rebuild, restart the server, re-run the script. Compare *per-render* values — the render where the value is `undefined` instead of the id names the root cause (usually a server payload keyed differently than the client type, e.g. DB row key `orgId` crossing HTTP where the contract says `id`). 5. **Also grep every suite teardown for broad `DELETE ... LIKE`/`TRUNCATE` on the shared DB when a test flakes only under uncached concurrent runs (`turbo run test --force`) and passes isolated:** parallel vitest files on one DB — the suite that finishes first FK-cascades away the other's live sessions; the symptom is a spurious 401 (or missing row) in the still-running file. Fix by namespacing emails/ids per suite and scoping teardowns to rows the suite created. ## Cleanup Remove instrumentation, move the throwaway script to `.AGENTTRASH/`, never commit the debug script. Verified on franksign: `GET /api/orgs` leaked `orgId` where the client contract was `id` (fake aria-current true, silent auto-select failure).