diff --git a/CHANGELOG.md b/CHANGELOG.md index d601c37..297353c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,9 @@ # Changelog +## v0.9.7 — 2026-09-29 09:59 +- LOCAL SYSTEM no longer shows the previous system after a jump (#30): WorldMapPanel owns the world state and applied the `/api/jump` response locally, so MissionControl's lifted `/api/system` state (which drives the LOCAL SYSTEM tab, CARGO HOLD and the SECTOR row) went stale until a page refresh. The panel now takes an `onJumped` callback that MissionControl wires to a `/api/system` refetch after every successful jump — same failure policy as the mount fetch (keep old state on error). +- e2e: new smoke spec — click-to-jump on WORLD MAP, wait for the chart's turn counter to update, switch to LOCAL SYSTEM and assert its header shows the NEW current system from `/api/world` (no `page.reload()` workaround; one jump per run/retry, same pattern as the existing jump test). + ## v0.9.6 — 2026-09-29 09:41 - The active operations tab now survives a page refresh (#27): the selected tab ID is persisted to `localStorage` (`sw-active-tab`) on every change and restored on mount when it matches one of the seven valid tab IDs (unknown/stale values fall back to WORLD MAP). The Radix Tabs are controlled for this; SSR and first client render still agree on the default, so there is no hydration mismatch. - e2e: new smoke spec — click CREW, reload, assert `aria-selected=true` again (fresh context per run/retry, no API state touched, idempotent). diff --git a/components/mission-control.tsx b/components/mission-control.tsx index 21ffe38..b74face 100644 --- a/components/mission-control.tsx +++ b/components/mission-control.tsx @@ -1,6 +1,6 @@ 'use client' -import { useEffect, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' import { Badge } from '@/components/ui/badge' import { Panel, PanelContent, PanelHeader, PanelTitle } from '@/components/ui/panel' import { Progress } from '@/components/ui/progress' @@ -112,6 +112,19 @@ export function MissionControl() { if (stored && TAB_IDS.includes(stored)) setActiveTab(stored) }, []) + // #30 — re-fetch /api/system after a jump: WorldMapPanel owns the world state, so without this + // the lifted system state stays on the previous system until a page refresh. Same failure + // policy as the mount fetch — keep the old state on error. + const refreshSystem = useCallback(() => { + fetch('/api/system') + .then(async (res) => { + if (!res.ok) throw new Error(`HTTP ${res.status}`) + return (await res.json()) as SystemState + }) + .then((s) => setSystem(s)) + .catch(() => {}) + }, []) + const timeStr = new Date().toLocaleTimeString('en-GB', { hour12: false }) // Slugs of the researched techs — drives the effective caps + build gates in the SHIP SYSTEMS @@ -395,7 +408,7 @@ export function MissionControl() { {/* WORLD MAP TAB — live data from /api/world */} - + {/* LOCAL SYSTEM TAB — live data (state owned by MissionControl; scans report up) */} diff --git a/components/world-map.tsx b/components/world-map.tsx index ec86609..e1bf5e7 100644 --- a/components/world-map.tsx +++ b/components/world-map.tsx @@ -11,9 +11,10 @@ import type { WorldState } from '@/lib/game/save' interface WorldMapPanelProps { height?: number + onJumped?: () => void // #30 — let the parent refresh /api/system after a successful jump } -export function WorldMapPanel({ height = 380 }: WorldMapPanelProps) { +export function WorldMapPanel({ height = 380, onJumped }: WorldMapPanelProps) { const [world, setWorld] = useState(null) const [error, setError] = useState(null) const [jumpingId, setJumpingId] = useState(null) @@ -47,12 +48,13 @@ export function WorldMapPanel({ height = 380 }: WorldMapPanelProps) { const body = (await res.json()) as WorldState & { error?: string } if (!res.ok) throw new Error(body.error ?? `HTTP ${res.status}`) setWorld(body) // fresh state straight from the response — no refetch needed + onJumped?.() // #30 — parent re-fetches /api/system (LOCAL SYSTEM + CARGO HOLD mirror it) } catch (err: unknown) { setError(err instanceof Error ? err.message : String(err)) } finally { setJumpingId(null) } - }, []) + }, [onJumped]) if (error && !world) { return ( diff --git a/e2e/smoke.spec.ts b/e2e/smoke.spec.ts index 7e8a2b8..981ed81 100644 --- a/e2e/smoke.spec.ts +++ b/e2e/smoke.spec.ts @@ -112,6 +112,30 @@ test('jumping to an adjacent system updates the world state and turn counter', a expect(after.systems.find((s) => s.current)?.id).toBe(targetId); }); +test('local system shows the new current system after a jump, without a page reload (#30)', async ({ page }) => { + const before = (await (await page.request.get('/api/world')).json()) as WorldState; + const currentId = before.systems.find((s) => s.current)!.id; + const edge = before.edges.find(([a, b]) => a === currentId || b === currentId)!; + const targetId = edge[0] === currentId ? edge[1] : edge[0]; + const targetName = before.systems.find((s) => s.id === targetId)!.name; + + await page.goto('/'); + // Jump through the UI: click the target's chart field on WORLD MAP (the default tab). + await page.getByRole('tabpanel').getByRole('button', { name: targetName.toUpperCase(), exact: true }).click(); + + // Wait until the jump response is applied to the chart — server state is committed by then, so + // the /api/world read below can't race the in-flight POST. + await expect(page.getByText(`GALACTIC CHART // JUMP ROUTES — TURN ${before.turn + 1}`)).toBeVisible(); + + const after = (await (await page.request.get('/api/world')).json()) as WorldState; + const newCurrent = after.systems.find((s) => s.current)!; + expect(newCurrent.id).toBe(targetId); // the jump landed where we aimed + + // Switch to LOCAL SYSTEM — it must show the NEW current system without a page reload (#30). + await page.getByRole('tab', { name: 'LOCAL SYSTEM' }).click(); + await expect(page.getByText(`EXPLORABLE WORLDS // ${newCurrent.name.toUpperCase()} — TURN ${after.turn}`)).toBeVisible(); +}); + test('local system shows the current system bodies with staggered moons and explore buttons', async ({ page }) => { const sys = (await (await page.request.get('/api/system')).json()) as SystemState; diff --git a/package.json b/package.json index 4a914e8..207a31d 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "space-wanderer", - "version": "0.9.6", + "version": "0.9.7", "private": true, "scripts": { "dev": "next dev -p 3000",