Skip to content

Sprite Lab in Lab2: fix level switch#73854

Open
breville wants to merge 1 commit into
stagingfrom
spritelab-lab2-fix-level-switch
Open

Sprite Lab in Lab2: fix level switch#73854
breville wants to merge 1 commit into
stagingfrom
spritelab-lab2-fix-level-switch

Conversation

@breville

@breville breville commented Jul 15, 2026

Copy link
Copy Markdown
Member

Navigating between two SpriteLab2 levels in a lesson crashed the page: Error: Can't change value of key "channelId", then the error boundary.

Root cause

SpriteLab2 seeds the legacy pageConstants slice (isBlockly, channelId, …) because it bypasses StudioApp.init, and the legacy reducer forbids changing a key once set — it assumes one page load per level. Lab2 switches levels in place: the new level's channel loads, the seeding effect re-fires with the new channelId, and the reducer throws mid-dispatch. Single-level sessions never hit it because the first undefined → value transition is legal; it takes a second, different channel.

Why not reset on LifecycleEvent.LevelChangeRequested

Tried first — it's the formal seam, and the event does fire. But SourcesContainer is keyed on levelProperties.id, so the view remounts on switch and its seed effect re-runs immediately, while state.lab.channel still holds the previous channel. The stale value gets re-seeded right over the reset, and the throw comes back when the new channel lands. No event-ordered reset wins that race.

The fix

The lab registers the reducer wrapped (reseedablePageConstants): delegate to the legacy reducer, and when its change-guard throws, re-apply the action from the initial state — last write wins. Correct under any effect ordering, no legacy edits, and no coupling to the legacy module's internal action type (it catches the throw rather than matching type strings). Violations the legacy reducer rejects outright, like disallowed keys, throw again on the retry and so still throw. Classic labs are untouched; the lab's own seeding effect is the slice's only writer on Lab2 pages.

Testing

  • Unit tests: the level-switch sequence (seed A → seed B) succeeds with last-write-wins; disallowed keys still throw; unrelated actions pass through untouched.
  • In-browser, against a temporarily seeded second SpriteLab2 level in the Lab2 Showcase lesson (not part of this PR — the lesson keeps one level per lab): reproduced the exact crash on unfixed code (bubble-navigate → error page), then with the fix: level 9 → 10 → back to 9 with no errors, and pageConstants.channelId matching state.lab.channel.id at every step — each level isolated on its own channel.

Follow-up

When SpriteLab2 adopts the unified useSources hook (#73822), the channel stops flowing through state.lab and this seam should be revisited — potentially reading channelId live instead of seeding it at all.

The legacy pageConstants reducer forbids changing a key once set - it
assumes one page load per level. Lab2 switches levels in place, so
navigating between two SpriteLab2 levels threw "Can't change value of
key 'channelId'" from the seeding effect and took down the page.

The lab now registers the reducer wrapped: a change the legacy guard
rejects is re-applied from the initial state (last write wins).
Wrapping beats resetting on LifecycleEvent.LevelChangeRequested: the
view remounts on level switch and re-seeds the stale channel before
the new one loads, so no event-ordered reset can win that race.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@breville breville added the AI generated This PR has been substantially generated using AI. label Jul 15, 2026
@breville breville force-pushed the spritelab-lab2-fix-level-switch branch from 76cfb0e to 36e40fc Compare July 15, 2026 00:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI generated This PR has been substantially generated using AI.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant