Skip to content

fix(issues): stop cold-load render loop on the Issues route (MUL-4985)#5643

Merged
NevilleQingNY merged 1 commit into
mainfrom
walt/mul-4985-issues-cold-load-loop
Jul 20, 2026
Merged

fix(issues): stop cold-load render loop on the Issues route (MUL-4985)#5643
NevilleQingNY merged 1 commit into
mainfrom
walt/mul-4985-issues-cold-load-loop

Conversation

@NevilleQingNY

Copy link
Copy Markdown
Collaborator

What & why

The Issues route crashed with "Maximum update depth exceeded" on first paint (desktop_route_error on /…/issues). Root cause is a cold-load reference-instability loop, amplified by the real <Virtuoso> in the Board/Swimlane columns:

  • While the member/agent/squad directory queries are still loading, useActorName's const { data: members = [] } defaults allocated a fresh array every render, so its getActorName memo changed identity each render.
  • BoardView's groups memo and SwimLaneView's laneGroups list getActorName as a dependency, so they churned a new value every render.
  • The board/list column-resync useEffect(setColumns, [groups, …]) then re-fired without end. react-virtuoso republishes its store synchronously on every render, which starves the query-resolution microtask and escalates the loop into the reported crash.

This is not a MUL-4797 regression — it reproduces at the parent commit and was exposed by the earlier board/swimlane virtualization reland (ea03912ba) that mounted a real <Virtuoso>; the latent getActorName instability predates that.

Changes

  1. Root causepackages/core/workspace/hooks.ts: share stable module-level empty arrays for the loading directory snapshot, so getActorName is referentially stable across cold-load renders (matches the existing EMPTY_* convention in the surface data hook).
  2. Defense-in-depthpackages/views/issues/components/use-drag-settle.ts: equality-guard the shared column setter so a content-equal rebuild returns the previous reference and cannot spin the resync effect from any future unstable input. It only skips no-op writes; drag/settle semantics are unchanged.

workspaceProperties = [] was considered but left untouched: during cold load groupingProperty collapses to a stable null, so it does not churn the groups memo. groups is fully stabilized by fix (1).

Verification

  • New regression tests (all red without the fix, green with it):
    • packages/core/workspace/hooks.test.tsxgetActorName is referentially stable across renders during cold load.
    • packages/views/issues/components/use-drag-settle.test.tsx — the column setter does not loop when a resync effect rebuilds a content-equal map every render, and still applies real content changes.
    • packages/views/issues/components/issues-cold-load-loop.test.tsx — Board (40 cards → real <Virtuoso>) and Swimlane (assignee grouping) render under pending directory queries with the real react-virtuoso. Without the fix the Board test fails with the exact issue error ("Maximum update depth exceeded"); with the fix both paint.
  • Existing suites: packages/views issues tests 417 passed (incl. the 40+ swimlane drag tests exercising the guarded setter); packages/core workspace tests 19 passed.
  • tsc --noEmit clean and eslint clean for packages/core and packages/views.

Closes MUL-4985

Board and Swimlane crashed with "Maximum update depth exceeded" on first
paint of the Issues route. During cold load the member/agent/squad
directory queries are unresolved, so useActorName's `= []` defaults
allocated a fresh array every render and its `getActorName` memo changed
identity each render. BoardView's `groups` (and SwimLaneView's
`laneGroups`) list `getActorName` as a dep, so they churned every render
and re-fired the column-resync effect without end; react-virtuoso
escalated the loop into the reported crash. This predates MUL-4797 — it
was exposed when board/swimlane columns were virtualized with a real
<Virtuoso>.

- Share stable empty references for the loading directory snapshot so
  getActorName is referentially stable across cold-load renders (root
  cause).
- Equality-guard the shared column setter in useDragSettle so a
  content-equal rebuild returns the previous reference and cannot spin
  the resync effect (defense-in-depth).

Regression coverage: useActorName stability during cold load; the
column-setter equality guard; and Board (40 cards) + Swimlane rendered
with the REAL react-virtuoso under pending directories, which reproduce
"Maximum update depth exceeded" without the fix and pass with it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: multica-agent <github@multica.ai>
@vercel

vercel Bot commented Jul 19, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
multica-docs Ready Ready Preview, Comment Jul 19, 2026 9:45am

Request Review

@NevilleQingNY
NevilleQingNY merged commit fcb4798 into main Jul 20, 2026
7 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant