v0.82.1 — Clicking a token to impersonate it froze the map
- Fixes an unbounded render loop in
useAutoArtworkthat locked up the map tab. A performance trace of 39 seconds shows the shape precisely: ~49,000 renders, ~96,000console.warncalls, 11 seconds of garbage collection — and three network requests in total. Nothing was waiting on anything; the loop fed itself. - The cause is one missing pair of braces.
useImageGenAuthFetch()returns a{ authFetch }wrapper; only the function inside it is memoised, so the wrapper is a new object on every render. Five of the six call sites destructure it.useAutoArtworkheld the wrapper, which made it two bugs at once. It went tolistModels()andgenerate()where theImageGeneratorExtensioncontract requires a function, so every call rejected with aTypeErrorbefore reaching the network — which is why auto-generated monster art never worked and why the trace has no requests in it. And it was listed as a dependency of the effect that callslistModels, so the effect re-ran on every render. The.catchthen calledsetModels([]), a fresh array is neverObject.isthe old one, that re-rendered, the re-render minted a new wrapper, and the effect ran again. - The compiler had already found it, and nothing was listening. Both lines were
TS2322errors onmain— an object where a function was required, stated exactly. They survived becauseapps/webis deliberately not typechecked in CI: it carries a standing population of pre-existingtscerrors, documented inscripts/verify.mjs, and a new one lands in that crowd without a signal. This fix takes the count from 192 to 190; it does not make the 190th harder to add. Worth naming as the gap that let a one-line type error reach production rather than treating it as fixed here. setModels([])is now identity-preserving when the list is already empty. The loop needed both halves — an unstable dependency and a state write that always counted as a change — and this is the half that turns a future unstable dependency into one wasted effect run instead of a frozen tab.- Regression test:
useAutoArtwork.renderLoop.test.tsruns the real hook against a miniature React that re-renders until state stops changing and fails at a cap of 25 passes. Against the unfixed hook it reportsnever settled, which is the production symptom rather than a proxy for it. - The trigger being token impersonation is incidental, and worth writing down so the next
trace is read faster:
generatoris captured byuseMemo(…, []), so an instance only arms the loop if thegenaimodule had already registered when it mounted. Any remount of the map subtree after module load would have done it.
Also in this release
- CI stops depending on an apt repository it never reads. The e2e job installs chromium
with
playwright install --with-deps, and--with-depsshells out toapt-get update— which refreshes every source baked into the runner image, Google's Chrome repository included.dl.google.comserved aPackages.gzwhose SHA256 disagreed with theReleasefile advertising it; apt treats a bad index as fatal (exit 100),playwright installexited 1, and a build that reads no Google package failed on a Google CDN blip. The two third-party lists — Chrome andmicrosoft-prod— are removed before the install, soapt-get updatetalks only to the Ubuntu archives the dependencies actually come from. - And it retries. Those archives can flake too and cannot be removed, so the install runs
up to three times with a widening pause rather than failing the whole build on one
apthiccup.ciPlaywrightInstall.test.tsholds both halves in place, next to the guard on CI's registry auth for the same reason:packages/server-coreis a suite CI actually runs.