fix(core): legacy scenes with raw nodes load without crashing - #979
Merged
Merged
Conversation
The load migrations added by #976 read stored nodes as if schema-parsed. The oldest stored scenes omit container fields the schema defaults (walls without `children`, openings placed by a legacy `offset` with no `position`), so `plateLevelContext` and `isFloorAnchoredOpening` threw and both the hosted authority and `setScene` failed to open the project. Every exported load migration now runs through `loadMigration`: it reads a view of the stored nodes with container defaults (arrays and empty objects) filled, never scalars whose absence migrations read, and strips the fills it carried through untouched so no stored node is rewritten by them. A migration that still throws is reported and skipped, so the scene loads as main loaded it instead of failing. Also: deleting a room re-poses items kept from a curved wall with the curve tangent at the item, not the wall chord (Bugbot on #976). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013LKpG6PpZe6Jb4dBBAKtBG
|
I hit an error while handling your request (Model unavailable on AI Gateway free tier: Free tier users do not have access to this model. Upgrade to paid credits at https://vercel.com/d?to=%2F%5Bteam%5D%2F%7E%2Fai%3Fmodal%3Dtop-up for unrestricted…). Please try again, rephrase, or reach out if it keeps failing. Error id: ccf2d685-266c-49d4-ab7a-c91ad3225923 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Cause
The load migrations added by #976 read stored nodes as if they had been schema-parsed. The oldest stored scenes don't have every field the schema defaults. The private CI fixture
legacy-parent-links.test.ts > documentationZoneshows it: its walls have nochildren, and its doors and windows are placed by a legacyoffsetwith noposition. Two crashes followed:plateLevelContextreadwall.children.flatMap(lib/plate-surface.ts), reached frommigrateExteriorThresholdsinsidereconcileStructureOnLoad.isFloorAnchoredOpeningreadopening.position[1](lib/floor-opening-footprints.ts), reached frommigrateFloorPlates.Both the hosted authority (
normalize-authority-scene) and the clientsetScenethrew, so any such project failed to open. Prod isn't affected yet because private hasn't bumped to #976.Fix
One mechanism, applied to every exported load migration:
loadMigration/loadMapMigrationincore/src/utils/load-migration.ts.children,holes,holeMetadata,position,rotation,boundaryWallIds, and empty objects such asmetadata. Scalars stay absent, because migrations read their absence as meaningful: slabthickness/elevation, levelheight,floorThresholdVersion. A missing container means exactly its default, since the reload aftermaterializeNodeDefaultssees it filled and must migrate the same way.[scene load] <name> failed…) and skipped. The scene then loads as main loaded it, instead of failing.migrateFloorPlateskeeps its per-level guard inside this.Wrapped: legacy material slots, vertical, room zones, ceiling room links, legacy auto openings, floor plates (including piece adoption), slab slots, stair/elevator openings, wall face keys, wall face bands, floor openings, owned floor openings, and structure reconcile on load (including exterior thresholds, legacy wall datums, and footprint following). The legacy map passed to
reconcileStructureOnLoadgoes through the same view.materializeNodeDefaults,normalizeLegacyStructure,healSceneNodesandremoveRetiredDrawingSheetNodesalready tolerate raw nodes.Legacy door
offset: main never converted it. The client parsed the door with its schema defaultposition: [0, 0, 0]and leftoffsetin place, and the authority stored neither. That behaviour is unchanged.Also: Bugbot finding on #976
deleteZone'slevelPoseplaced a wall-hosted item with the curve frame, but took the yaw from the wall chord. Items kept from a curved wall therefore got the wrong rotation when their room was deleted. It now uses the curve tangent at the item's station, the same wayopening-floor-datum.tsdoes.Tests
utils/raw-legacy-load.test.tschildren, door and window withoffsetonly, averticesslab, a documentation-only zone, a level withoutheight). It loads through the authority's migration sequence and throughsetScenewithout throwing and without any migration falling back. Every stored node is retained, the source isn't mutated, and a second load is idempotent. The door stays as main left it: clientposition: [0, 0, 0]withoffset: 1, and the authority writes noposition.utils/load-migration.test.ts: view fills, restore semantics (carried-through fill vs written value vs value grown in place), and the fallback.commands/structure/delete-zone.test.ts: an item kept from a curved wall faces along the tangent. It fails before the fix.Verification
tsgo --noEmitclean for core (plus contracts), nodes, viewer, editor and mcp. Manual tsgo dist build of core, nodes, viewer and mcp.--set golden126 scenes, 0 blocking.--set broad496 scenes, 0 crashes. Both were also run once with the fallback turned into a rethrow, and neither changed, so no migration falls back on any of the 622 real scenes.legacy-parent-links.test.tsagainst these dists: 2/2 pass (documentationZone failed before). The whole privatecollaboration/__tests__directory passes: 286.🤖 Generated with Claude Code
https://claude.ai/code/session_013LKpG6PpZe6Jb4dBBAKtBG
Note
Medium Risk
Touches the full scene load migration pipeline and persistence shape restoration; failures are swallowed per migration but incorrect restore logic could change stored graphs or skip migrations silently.
Overview
Legacy scene load safety is the main theme: new
loadMigration/loadNodeViewinload-migration.tsruns each structure load migration on a view with container schema defaults (children,holes,position, etc.) filled in, while scalars stay absent so migrations can still read “missing” fields. Untouched view-only fills are stripped on output so stored JSON is not rewritten for no-op paths; thrown migrations log[scene load] … failedand the pipeline continues with the original nodes.Wrapped migrations include floor plates, room zones, vertical model, material slots, wall faces, floor/opening reconcile, and related helpers—same public exports, safer behavior on raw stored nodes.
deleteZone/levelPosenow subtracts yaw from the wall curve tangent at the item’s station (not the straight chord), so items kept when a room is deleted stay correctly oriented on curved walls.Tests add
load-migration.test.ts, end-to-endraw-legacy-load.test.ts(authority + client, optional-field fuzz), and a curved-wall keep-item case indelete-zone.test.ts.Reviewed by Cursor Bugbot for commit f8287f8. Bugbot is set up for automated code reviews on this repo. Configure here.