Symptom: Browser tab freezes within seconds after committing any cell value (Enter or click outside the cell). Chromium offers "wait or kill"; DevTools become inaccessible. Reproduces on main (v0.25.0) and on the live deployment.
Root cause: Infinite render loop in src/app.jsx triggered by the per-category Commons-existence check added in 6bb990f (T425950 follow-up).
The cat-check effect (src/app.jsx:574-666) depends on [items]. Inside its Promise.all(...).then() it calls setItems((prev) => prev.map((row) => { ... })). The per-row bailout returns the original row reference, but prev.map() itself always returns a new array reference — React's Object.is(newArr, prevArr) is always false → re-render → items memo recomputes → effect re-fires. With at least one row carrying a category that's already in categoryCheckRef.current, the cached path runs Promise.all([]).then(...) which resolves on the next microtask → setItems again → loop. The microtask queue self-fills; the event loop never yields to render or input → tab hangs and DevTools can't attach.
Manifests "on commit" because that's the most common setItems trigger; once a category sits in the cache, any setItems call (any cell commit, dup-check resolve, background refresh) freezes the tab.
Fix: Track a changed flag inside the prev.map; return prev (same reference) when no row mutated. Lets Object.is bail. Targeted ~5-line change in src/app.jsx.
Repro:
- Add a category to a stash row, commit (Enter)
- Wait ~1s for the category-existence fetch to resolve
- Edit any cell on any row and commit → freeze
Severity: Critical — every active editor hits this once they have categories on rows. Lost work, forced tab kill.