Repository navigation
fix(app-shell): a Studio pillar's cancelled draft load no longer leaves the canvas on "Loading…" (objectui#11331) - #11338
Conversation
…es the canvas on "Loading…" A pillar's draft-load effect raised `loading` as a load started and lowered it only from the load's own `finally`, behind `if (!cancelled)`. A load the cleanup cancelled never lowered it, so a next run that started no load of its own left the canvas spinning for good: an Interfaces leaf with no designer opened while the first leaf loaded, and a Data object whose load a new client cancelled (the re-run bailed on the load-once claim the cancelled load had taken but never honoured). One rule for the three draft loads (Interfaces, Data, Automations): the cleanup of a run whose load has not settled takes back what its start claimed, `loading` everywhere and the Data pillar's load-once claim too. Pinned by StudioDesignSurface.cancelledLoad-11331.test.tsx: gated race rows red on 5262f7d and green here, with controls. Claude-Session: https://claude.ai/code/session_0122Knsowci76D2rBWReCzzZ Co-authored-by: Claude <noreply@anthropic.com>
|
changeset-claim-re-read
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
|
Contract reviewServed-tier: Read: card objectui#11331 (body, claim ① Derived judgmentsPublic surface and accept sets: none move. The diff adds no export, prop, type, registry entry or spec key. (a) The rule as implemented. The three draft-load effects (
(b) The Data site's extra release. Under the same (c) Automations. The source reading is true at this head. (d) No second spinner. True. The cleanup's lower and the replacement run's raise both run in the passive-effects phase of one commit and batch into one render. Independently of that, the arms hold the spinner for a new item until its own load installs: Interfaces (e) Pins ( Canvas branch order and objectui#11272's stamping: unchanged. The diff is additions only; it touches no (g) Check-runs on the head: 43, all completed, none in progress: 39 success, 3 skipped ( ② Semver level
③ Boundary flags
Implemented-by: VERDICT: PASS |
…inner Takes objectui#11330's Spec Main Shape Gate repair. No conflicts; this branch's diff is unchanged. Claude-Session: https://claude.ai/code/session_0122Knsowci76D2rBWReCzzZ Co-authored-by: Claude <noreply@anthropic.com>
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Fixes #11331
Clause-②: no
A Studio pillar whose draft load is cancelled, with no load to replace it, no longer leaves its canvas on "Loading…". The Interfaces race removed here is the one that keeps
Test (shard 2/8)red on PR #11323 (StudioDesignSurface.canvasFrame.test.tsx, "a canvas state with no preview shell keeps the wrapper frame"). That file is unchanged here. It goes green because the race is gone, not because it waits longer.The rule, one shape for the three draft loads
Each draft-load effect (
InterfacesPillar,DataPillar,AutomationsPillarinStudioDesignSurface.tsx) raisedloadingas its load started. Only the load's ownfinallylowered it, behindif (!cancelled). The rule now: the cleanup of a run whose load has not settled takes back what that run's start claimed. Each run keeps a localsettledflag, which itsfinallysets. Its cleanup runsif (!settled)and does the following:setLoading(false).loadedNameRef.current = null). This is the same rule applied to a second claim, the only other claim a run's start makes. The Data run's start claims two things:loadingandloadedNameRef. The measurement below shows that loweringloadingalone leaves the Data case stuck.Why it cannot raise a second spinner or flash stale content: when a replacement load follows, the old run's cleanup
setLoading(false)and the new run'ssetLoading(true)happen in the same passive-effect flush, so React batches them into one render. Even without that batching, the canvas'sloading || !draftLoadedarm (or!objLoadedin Data) keeps the new item on the spinner until its own load is installed, because the objectui#11272 buffer stamp names the item it was loaded for. The stamping (draftFor,ifDirty,hasDraft, the!draftLoadedarm and itserror && !loading ? nullrule) is unchanged, and the canvas branch order is unchanged.What was measured (Zone 2)
origin/main5262f7dd3f. The gated probe is now a committed pin. It mounts the realInterfacesPillarwithcanvasFrame.test.tsx's mocks, holds the dashboard leaf'slayeredread on a gate, and clicks the report leaf while that read is in flight. On5262f7dd3fthe race row fails with the DOM captionSales reportover a canvas body that readsLoading…. A scratch probe asserted only the issue's "after the gate opens" column, and on the ablated tree it fails the same way after the gate opened. The control row, a click after the dashboard grid rendered, shows "cannot be previewed or designed here" on both trees.5262f7dd3fthe object stays onLoading…after the gate opens. The cause is!objLoaded, notloading. The cancelled load installed nothing, and the re-run bailed onloadedNameRef.current === loadKey. Loweringloadingalone does not fix this site (ablation leg D1 below: the race row stays red). Releasing the unsettled claim is what fixes it. A claim whose load settled is kept, and a pin row says so: a new client after the object loaded issues no secondlayeredread.!current, and the pillar never setscurrentback to null once a flow is open. Its threesetCurrentwrites are the list effect'sc ?? deepLinked ?? items[0] ?? null, a create and a rail click, all of them non-null once a flow is open. So a cancelled flow load always has a replacement. On!currentthe canvas's!currentbranch comes before the loading arm, so no spinner is visible with no leaf. That is a reading of the source; nothing in the tree re-derives it. The cleanup takes the same shape here as at the other two sites.settled-gated cleanup above, chosen over clearing in every early-return branch. A branch-by-branch clear covers only the early returns that exist today, and it does not cover the Data bail at all. The cleanup is the one place every cancelled run passes through.Pins:
StudioDesignSurface.cancelledLoad-11331.test.tsx5262f7dd3f(no fix)Loading…under theSales reportcaption)Loading…, noAdd field)Ablation (fix committed first, each leg mutated through
ablation-replace.mjs, restore proven by blob hash and an emptygit diff HEAD)setLoading(false), so the claim is not releasedlayeredread twice)Local verification (head
7b70a05236)pnpm --workspace-concurrency=2 --filter '@object-ui/app-shell^...' build: exit 0. Thenpnpm --filter @object-ui/app-shell type-check(tsc --noEmit && tsc -p tsconfig.test.json): exit 0.--listFilesshows the test project compiles the new pin.pnpm exec vitest run packages/app-shell/src/views/studio-design/:Test Files 85 passed (85),Tests 496 passed (496).StudioDesignSurface.canvasFrame.test.tsxpassed 10 of 10.check:new-line-citations(0 new),check-changeset-presence,changeset:check,check:control-bytes,check:vi-mock-specifiers,check:vi-mock-inherit,check:vi-mock-override-shape,check:test-path-roots,check:changeset-claims(report-only; the pending changeset it names is about chunk co-tenancy, which this diff does not move),check:pending-changeset-literals.--no-inline-config): the new test has 0 findings.StudioDesignSurface.tsxhas the same rule-by-rule counts as5262f7dd3fread through--stdin, which is 1 error (react-hooks/static-components) and 18 warnings, so nothing was added. The config enables no type-aware linting, so this diff cannot move a verdict in an untouched file. The repo-wide lint is CI's.heavyDomTestsentry was added.Acceptance notes
.changeset/11331-app-shell-cancelled-load-spinner.md,@object-ui/app-shellpatch.Spec Main Shape Gateis expected red from objectui#11330, which this diff does not touch.useMetadataClientre-mint, which the hook'suseMemocan hand out (AGENTS.md [WIP] Enhance every detail of the designer #10). No console flow that re-mints the client mid-load was exercised.Session:
https://claude.ai/code/session_0122Knsowci76D2rBWReCzzZGenerated by Claude Code