Files
command-center/agent/managed-skills/react-prod-state-mismatch-bisection/SKILL.md
T
Frank 8af40c402b chore(agent): commit the machine tier and the doctrine text the checkout was already running
Landing blocker for #61 and #63: the deployment checkout is 12 commits behind and
`merge --ff-only` refuses while tracked paths are dirty. Discard is not available here
(workspace policy: no `checkout`/`stash` on shared state), so the dirt is committed.

- `agent/APPEND_SYSTEM.md`: the point-of-use / one-work-log / PR-link doctrine, already
  byte-identical to `origin/main` (`cmp` against the trunk blob: no difference). No new
  prose — the commit exists so the trunk merge can proceed.
- `agent/config.yml` and `agent/managed-skills/**`: the machine-live tier the heartbeat
  owns (AGENTS.md § Live-sync tiers).

`ALLOW_SECRETS=1` was used, deliberately and after auditing the staged diff by hand: the
only credential-shaped strings in it are two copies of `AKIA0000000000000EXAMPLE` — the
inert all-zero AWS example id — and they appear inside the two skill pages that teach
that exact placeholder shape. This checkout is still running the pre-#61 hook; the merged
gate (value-scoped placeholder filter, `71601f7`) exempts this value, so the refusal
dies with this landing. No `--no-verify` was used: the gate ran and its verdict is
recorded here.

Refs #61
2026-10-21 01:44:51 -07:00

61 lines
3.1 KiB
Markdown

---
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).