ui/phase-21: harden applyOrderOverlay against HMR-stale localScience
Fixes a black-canvas regression on /map after creating a science in DEV: when Vite hot-reloads the decoder bump that adds the `localScience` field, the live in-memory `gameState.report` keeps its older shape with no such field, so the overlay's `[...report.localScience]` throws inside the reactive getter and silently aborts the map view's `$effect`. The fix wraps the spread and the final return in `?? []` defaults — and matches the ship-class branches for symmetry — so the overlay stays well-defined for any partial report shape upstream consumers may carry across an HMR boundary. Also adds order-overlay regression tests covering the createScience / removeScience branches plus the explicit HMR-stale shape, and a Playwright e2e (sciences-map-regress.spec.ts) replaying the user-reported flow: /map → /designer/science → save → /map, asserting no map-mount-error overlay and no console errors. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -897,7 +897,7 @@ export function applyOrderOverlay(
|
||||
}
|
||||
if (cmd.kind === "createShipClass") {
|
||||
if (mutatedShipClass === null) {
|
||||
mutatedShipClass = [...report.localShipClass];
|
||||
mutatedShipClass = [...(report.localShipClass ?? [])];
|
||||
}
|
||||
// Skip duplicates: the engine refuses them server-side and
|
||||
// the designer's local validator prevents them client-side,
|
||||
@@ -917,7 +917,7 @@ export function applyOrderOverlay(
|
||||
}
|
||||
if (cmd.kind === "removeShipClass") {
|
||||
if (mutatedShipClass === null) {
|
||||
mutatedShipClass = [...report.localShipClass];
|
||||
mutatedShipClass = [...(report.localShipClass ?? [])];
|
||||
}
|
||||
const idx = mutatedShipClass.findIndex((cls) => cls.name === cmd.name);
|
||||
if (idx < 0) continue;
|
||||
@@ -926,7 +926,18 @@ export function applyOrderOverlay(
|
||||
}
|
||||
if (cmd.kind === "createScience") {
|
||||
if (mutatedScience === null) {
|
||||
mutatedScience = [...report.localScience];
|
||||
// `?? []` guards a real failure mode: in DEV with hot
|
||||
// module replacement the running `gameState.report`
|
||||
// object can predate the decoder bump that introduced
|
||||
// `localScience` — its field is then `undefined` on
|
||||
// the live JS object even though the type declares it
|
||||
// as a required array. A naked spread on `undefined`
|
||||
// throws inside the reactive overlay getter and aborts
|
||||
// the map's `$effect` silently, leaving the canvas
|
||||
// blank until a full reload. The default keeps the
|
||||
// overlay well-defined for any upstream that supplies
|
||||
// a partial report shape.
|
||||
mutatedScience = [...(report.localScience ?? [])];
|
||||
}
|
||||
// Skip duplicates by name: the engine refuses duplicates
|
||||
// server-side and the designer's local validator pre-checks
|
||||
@@ -946,7 +957,7 @@ export function applyOrderOverlay(
|
||||
}
|
||||
if (cmd.kind === "removeScience") {
|
||||
if (mutatedScience === null) {
|
||||
mutatedScience = [...report.localScience];
|
||||
mutatedScience = [...(report.localScience ?? [])];
|
||||
}
|
||||
const idx = mutatedScience.findIndex((sci) => sci.name === cmd.name);
|
||||
if (idx < 0) continue;
|
||||
@@ -966,8 +977,13 @@ export function applyOrderOverlay(
|
||||
...report,
|
||||
planets: mutatedPlanets ?? report.planets,
|
||||
routes: mutatedRoutes ?? report.routes,
|
||||
localShipClass: mutatedShipClass ?? report.localShipClass,
|
||||
localScience: mutatedScience ?? report.localScience,
|
||||
// `?? []` mirrors the per-branch HMR guard above: an old
|
||||
// in-memory `report` whose shape predates a field bump must
|
||||
// still produce a well-defined array on the way out, otherwise
|
||||
// downstream `$derived` blocks (`localShipClass.map`,
|
||||
// `localScience.find`, …) fault and the active view blanks.
|
||||
localShipClass: mutatedShipClass ?? report.localShipClass ?? [],
|
||||
localScience: mutatedScience ?? report.localScience ?? [],
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user