# INP Audit — Heavy Interactions (KAR-528)

> Closes part of KAR-522. KAR-528.
> Date: 2026-05-23.
> Scope: identify the components most likely to push Interaction-to-Next-Paint (INP) over Google's 200 ms p75 threshold under realistic BMW-pilot usage. Static audit only — a Chrome DevTools field run on staging is required to confirm.

## Method

INP measures the worst single user interaction over the lifetime of a page. The metric weights three things:

1. **Input delay** — how long before the event handler starts.
2. **Processing time** — how long the handler runs (this is where JavaScript work concentrates).
3. **Presentation delay** — how long after the handler before paint.

Static analysis can identify *processing-time* risks: large arrays mapped/filtered on every render, heavy state updates, synchronous DOM measurement. It cannot quantify *input delay* (depends on main-thread idle time and competing tasks) or *presentation delay* (depends on layout cost).

The hot-spots below are the static-analysis candidates. Each needs a Chrome DevTools Performance recording on staging to confirm the ms cost.

## Heat map

170 component files use `.map(...)`. 85 use `.filter(...)`. Of those, the components below are the highest-priority candidates because they (a) render long lists, (b) have user-driven filter/sort/search state, or (c) live behind forms that re-render on every keystroke.

### Tier 1 — confirmed heavy (large list + per-row state)

| File | LOC | Symptoms | Recommended remedy |
|---|---|---|---|
| `components/oee/history-table.tsx` | 222 | 4 `useState` hooks, history list of OEE records, sort + filter | (a) `useDeferredValue` on filter input so typing does not block the table render; (b) confirm rows are keyed by stable id (not array index); (c) consider virtualisation (`react-window`) if rows > 200 |
| `components/oee/loss-details-table.tsx` | 219 | 5 `useState` hooks, nested loss-records table per record | Same playbook as history-table. Plus: extract row component, `memo()`-wrap so unaffected rows do not re-render |
| `components/repository/supplier-table.tsx` | (unknown — server component?) | supplier list with potential filter | If client-interactive: introduce filter state in URL (`useSearchParams`) so the table can be a server component and skip client re-renders |
| `components/repository/stammdaten-overview-client.tsx` | (unknown) | master-data overview | Same playbook |

### Tier 2 — likely heavy (filter/sort, smaller surface)

| File | Symptoms | Recommended remedy |
|---|---|---|
| `components/repository/department-table.tsx` | smaller list, similar shape | Tier-2 priority — fix during Tier-1 sweep if cheap |
| `components/repository/consultant-table.tsx` | smaller list, similar shape | Same |
| `components/owner/audit-log-table.tsx` | audit-log can grow long over time | Consider server-side pagination instead of client filter |
| `components/owner/tenant-table.tsx` | tenant list, small for BMW pilot | OK for now; revisit when multi-tenant ships |

### Tier 3 — forms with per-keystroke validation

INP killers in forms typically come from `onChange` handlers that re-validate the whole form schema on every keystroke. We are not yet on React-Hook-Form (KAR-530), so the risk surface is wide.

Specific suspects:

- `app/intake/new/page.tsx` (intake form — many fields)
- `components/assessment/admin/question-modal.tsx`
- `components/assessment/response-form-*` (assessment response form)
- `components/oee/oee-form-fields.tsx`

For all: once React-Hook-Form lands (KAR-530), validation moves to RHF's per-field model and INP drops automatically. Until then, debounce expensive validators (1-2 candidates exist via grep) and avoid `setState` on every keystroke for derived values that can be computed at submit.

## Repo-wide observations

- **No usage of `useTransition` or `useDeferredValue`** in the entire `app/` + `components/` tree (`grep -l useTransition` returns zero). Both hooks are React 18+/19 native and exactly designed for INP scenarios. The repository is leaving free perf on the table.
- **No virtualisation library installed** (`react-window`, `react-virtual`, `@tanstack/react-virtual`). Acceptable for the current row counts (≤200 typical) but a constraint to track.
- **`use client`-marker placement** is broad — many large interactive trees re-hydrate as one big chunk. Splitting client islands narrower would also help INP. Not in this audit's scope but worth noting.

## Test plan (live)

Static analysis cannot confirm INP numbers. The audit below is the action plan, but the *verification* is a Chrome DevTools Performance recording on staging:

1. Deploy this branch to a Vercel preview with seed data (≥100 OEE records, ≥50 suppliers).
2. Open DevTools → Performance → CPU throttling 4× → Network: Slow 4G.
3. Per Tier-1 component, perform the worst-case interaction (e.g. type quickly into the filter input, sort a sorted column).
4. Read the slowest interaction's INP. Anything > 200 ms p75 is a blocker. Anything 100–200 ms is acceptable, watch.
5. Record findings in `docs/security/inp-audit-results-<date>.md`.

The live run requires staging access (operator action). The static heat-map above is the prep so the live run knows where to look.

## Recommended priority for pre-pilot fixes

1. **Tier 1 OEE tables** — `history-table.tsx` + `loss-details-table.tsx`. The OEE flow is on the BMW critical path; these two tables are the highest-INP-risk components in the repo.
2. **Introduce `useDeferredValue` on the filter input pattern** — one-line change per call site once we identify them in the live recording.
3. **Wait for KAR-530** to fix form-validation INP via React-Hook-Form, rather than touching the existing forms now.

## What this audit did not cover

- Bundle size / load time (Source 3 Web Vitals LCP is a separate KAR).
- Streaming / partial pre-rendering decisions (Caching ADR 022 covers them).
- React-Compiler memoization (out of scope until the compiler is stable in production).

## References

- KAR-522 audit Source 3 (Core Web Vitals — INP).
- KAR-530 — bulletproof-react folder refactor (will introduce React-Hook-Form).
- ADR 021 — state-management strategy (URL state for filters).
- ADR 022 — caching strategy (data-class boundary for tables).
