Skip to content

fix(items): don't clobber a same-commit settled flag (cached-model race)#485

Open
wass08 wants to merge 1 commit into
mainfrom
fix/item-settled-race
Open

fix(items): don't clobber a same-commit settled flag (cached-model race)#485
wass08 wants to merge 1 commit into
mainfrom
fix/item-settled-race

Conversation

@wass08

@wass08 wass08 commented Jul 10, 2026

Copy link
Copy Markdown
Collaborator

What does this PR do?

Fixes the effect-ordering race behind tonight's slow office bake: a cached item model mounts in the same commit as ModelWithRetry, child effects run first (settled=true), and the wrapper's mount effect then stomped it back to false permanently. The item renders and exports fine — the artifact was complete — but its dirty mark never clears, so scene-ready waits out the full sceneReadyMaxWaitMs cap and the bake page falsely reports the loaded items as skipped.

Only the bake worker hits this (shared warm HTTP context → synchronous cached mounts); every fresh-browser local test loads via suspense and settles after the wrapper's effect, which is why it validated clean locally.

The settled flag is now URL-stamped: unsettleForUrl is a no-op when the flag is already settled for the same URL (the cached same-commit case), and still resets on a genuine model swap (the URL-keyed remount keeps the retry-budget reset).

How to test

  1. bun dev, /bake/demo_office?disable=postFx,draw — settles organically (~2.5s), full artifact, skippedItems absent.
  2. Real proof post-merge: a warm office-scene bake on the worker should settle in seconds instead of waiting the full 240s cap, with no false skips.

Screenshots / screen recording

N/A.

Checklist

  • I've tested this locally with bun dev
  • My code follows the existing code style (run bun check to verify)
  • I've updated relevant documentation (if applicable)
  • This PR targets the main branch

🤖 Generated with Claude Code


Note

Medium Risk
Touches scene-ready / bake gating via itemModelSettled in ItemSystem; behavior change is narrow but timing-sensitive for headless exports.

Overview
Fixes a React effect-ordering bug where cached GLBs could render correctly but never clear the item “settled” dirty mark, so headless bakes waited until sceneReadyMaxWaitMs and falsely reported skips.

ItemRenderer replaces the boolean setSettled callback with a SettleControl that writes itemModelSettled together with itemModelSettledUrl. ModelWithRetry calls unsettleForUrl(url) on mount instead of always forcing unsettled — it is a no-op when the flag is already settled for that same URL (child ModelRenderer effect runs first on synchronous cache hits). Genuine asset swaps still reset because the component is keyed by URL.

settle(url) is used when the model resolves, on terminal load failure, and for room-clear preview.

Reviewed by Cursor Bugbot for commit ab3db67. Bugbot is set up for automated code reviews on this repo. Configure here.

A CACHED item model mounts in the same React commit as its retry
wrapper. Child effects run first, so ModelRenderer wrote settled=true —
and the wrapper's mount effect then blindly reset it to false, forever:
the item rendered and exported fine but its dirty mark never cleared.
Fresh-browser loads (every local test) go through suspense and settle
later, so this only bit the bake worker, whose shared warm HTTP cache
mounts most models synchronously — every warm bake waited out the full
readiness cap and falsely reported loaded items as skipped.

The settled flag is now URL-stamped: the wrapper's mount effect only
un-settles when the URL actually differs from the one already settled,
so the child's same-commit write wins on cached mounts and a real model
swap still resets state (the URL key remount covers the retry budget).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit ab3db67. Configure here.

const group = ref.current as (Group & { userData: Record<string, unknown> }) | null
if (!group) return
if (group.userData.itemModelSettled && group.userData.itemModelSettledUrl === url) return
group.userData.itemModelSettled = false

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remount preserves stale settled flag

High Severity

When ModelWithRetry remounts (e.g. node.asset.src key change) but resolveCdnUrl yields the same URL, unsettleForUrl returns early if itemModelSettled is already true for that URL. Stale userData on the parent group can survive from a prior terminal skip or load, so ItemSystem may clear the dirty mark while the GLB is still loading and scene-ready can fire too early for bakes.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit ab3db67. Configure here.

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