From ef1d282770c49b6107f735e0a58a431dfc17f279 Mon Sep 17 00:00:00 2001 From: Kamil Krzywda Date: Tue, 29 Sep 2026 08:00:44 +0000 Subject: [PATCH 1/2] =?UTF-8?q?feat(ui):=20world=20map=20=E2=80=94=20jump?= =?UTF-8?q?=20by=20clicking=20chart=20fields=20(#19)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Remove the bottom JUMP ROUTES button list; systems adjacent to the current one become clickable galactic-chart nodes (same POST /api/jump flow, same state refreshes). Current + unreachable systems are dimmed and not clickable. NodeGraph gains optional onNodeClick + per-node disabled (backward-compatible); two smoke tests updated to click chart fields. --- components/ui/node-graph.tsx | 13 +++++++- components/world-map.tsx | 65 +++++++++++------------------------- e2e/smoke.spec.ts | 16 ++++++--- 3 files changed, 43 insertions(+), 51 deletions(-) diff --git a/components/ui/node-graph.tsx b/components/ui/node-graph.tsx index 9c64ba6..8cee4b7 100644 --- a/components/ui/node-graph.tsx +++ b/components/ui/node-graph.tsx @@ -13,6 +13,7 @@ export interface NodeGraphNode { y: number status?: NodeStatus sublabel?: string + disabled?: boolean // not clickable even when onNodeClick is set (e.g. the current system) } export interface NodeGraphEdge { @@ -29,6 +30,7 @@ export interface NodeGraphProps extends React.HTMLAttributes { title?: string height?: number directed?: boolean + onNodeClick?: (id: string) => void // makes non-disabled nodes clickable buttons } const VARIANT_COLORS: Record = { @@ -59,6 +61,7 @@ const NodeGraph = React.forwardRef( title, height = 320, directed = false, + onNodeClick, style, ...props }, @@ -219,13 +222,21 @@ const NodeGraph = React.forwardRef( const cy = nodePy(node) const isHovered = hovered === node.id const statusColor = node.status ? STATUS_COLOR[node.status] : color + const clickable = !!onNodeClick && !node.disabled return ( setHovered(node.id)} onMouseLeave={() => setHovered(null)} - style={{ cursor: 'default' }} + onClick={clickable ? () => onNodeClick(node.id) : undefined} + onKeyDown={clickable + ? (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onNodeClick(node.id) } } + : undefined} + role={clickable ? 'button' : undefined} + tabIndex={clickable ? 0 : undefined} + aria-label={clickable ? node.label.toUpperCase() : undefined} + style={{ cursor: clickable ? 'pointer' : 'default' }} > {/* Background */} s.current)! - const reachable = world.edges - .filter(([a, b]) => a === current.id || b === current.id) - .map(([a, b]) => (a === current.id ? b : a)) - .map((id) => world.systems.find((s) => s.id === id)!) + // Systems one jump away — the only clickable chart fields (the same set as the old JUMP ROUTES list). + const reachableIds = new Set( + world.edges.filter(([a, b]) => a === current.id || b === current.id).map(([a, b]) => (a === current.id ? b : a)) + ) return (
({ - id: String(s.id), - label: s.name, - x: s.x, - y: s.y, - status: s.current ? 'ACTIVE' : s.discovered ? 'NEUTRAL' : 'OFFLINE', - sublabel: s.current ? 'CURRENT' : s.discovered ? 'DISCOVERED' : 'UNKNOWN', - }))} + nodes={world.systems.map((s) => { + const jumpable = !s.current && reachableIds.has(s.id) + return { + id: String(s.id), + label: s.name, + x: s.x, + y: s.y, + status: s.current ? 'ACTIVE' : jumpable ? 'NEUTRAL' : 'OFFLINE', + sublabel: s.current ? 'CURRENT' : jumpable ? (s.discovered ? 'DISCOVERED // JUMP' : 'UNKNOWN // JUMP') : s.discovered ? 'DISCOVERED' : 'UNKNOWN', + disabled: !jumpable || jumpingId !== null, // dimmed + not clickable; all locked while a jump is in flight + } + })} edges={world.edges.map(([a, b]) => ({ from: String(a), to: String(b) }))} variant="ACTIVE" title={`GALACTIC CHART // JUMP ROUTES — TURN ${world.turn}`} height={height} style={{ width: '100%' }} + onNodeClick={(id) => void jump(Number(id))} /> -
-
- JUMP ROUTES // FROM {current.name.toUpperCase()} -
-
- {reachable.map((s) => ( - - ))} -
-
- {error && (
diff --git a/e2e/smoke.spec.ts b/e2e/smoke.spec.ts index fc7d4e0..b7ceb22 100644 --- a/e2e/smoke.spec.ts +++ b/e2e/smoke.spec.ts @@ -71,12 +71,17 @@ test('tabs switch panels client-side (event log terminal)', async ({ page }) => }); test('world map shows the generated starting system and its neighbours', async ({ page }) => { + const world = (await (await page.request.get('/api/world')).json()) as WorldState; + await page.goto('/'); - // Live chart title carries the turn counter; a fresh save sits at the start with six jump routes. + // Live chart title carries the turn counter. await expect(page.getByText(/GALACTIC CHART \/\/ JUMP ROUTES — TURN \d+/)).toBeVisible(); - const jumpButtons = page.getByRole('button', { name: /\/\/ JUMP$/ }); - await expect(jumpButtons.first()).toBeVisible(); - await expect(jumpButtons).toHaveCount(6); + // The current system is marked, and every adjacent system is a clickable chart field (role=button). + const current = world.systems.find((s) => s.current)!; + await expect(page.getByText('CURRENT', { exact: true })).toBeVisible(); + const panel = page.getByRole('tabpanel'); + const adjacent = world.edges.filter(([a, b]) => a === current.id || b === current.id); // six on a fresh save + await expect(panel.getByRole('button')).toHaveCount(adjacent.length); }); test('jumping to an adjacent system updates the world state and turn counter', async ({ page }) => { @@ -87,7 +92,8 @@ test('jumping to an adjacent system updates the world state and turn counter', a const targetName = before.systems.find((s) => s.id === targetId)!.name; await page.goto('/'); - await page.getByRole('button', { name: `${targetName.toUpperCase()} // JUMP` }).click(); + // Click the target's chart field — clickable nodes are role=button with their name as accessible label. + await page.getByRole('tabpanel').getByRole('button', { name: targetName.toUpperCase(), exact: true }).click(); const after = (await (await page.request.get('/api/world')).json()) as WorldState; expect(after.turn).toBe(before.turn + 1); -- 2.54.0 From 32dda04e921e59bb33a73f89a65ba304f24bfb79 Mon Sep 17 00:00:00 2001 From: Kamil Krzywda Date: Tue, 29 Sep 2026 08:00:44 +0000 Subject: [PATCH 2/2] release v0.9.2 --- CHANGELOG.md | 6 ++++++ package.json | 2 +- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b15613e..2f0fab6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## v0.9.2 — 2026-09-29 08:00 +- WORLD MAP (#19): the bottom JUMP ROUTES button list is gone — jumps now happen by clicking galactic chart fields. Systems adjacent to the current one (the same set as before, undiscovered included) are clickable nodes with a pointer cursor and a `// JUMP` sublabel; the current system stays bright green with CURRENT, unreachable systems are dimmed (OFFLINE) and not clickable. +- NodeGraph (`components/ui/node-graph.tsx`) gained two optional, backward-compatible additions: an `onNodeClick(id)` prop that turns non-disabled nodes into buttons (click + Enter/Space, role=button, aria-label, focusable), and a per-node `disabled` flag — while a jump is in flight every node locks, mirroring the old disabled-button behaviour. +- The jump flow itself is unchanged: same POST /api/jump call, world state refreshed from the response (turn counter, current system, new routes), event log entry written server-side as before. +- e2e: two smoke tests touched the removed buttons and were updated — "world map shows…" now asserts the adjacent systems render as clickable chart fields (role=button count matches API adjacency) and "jumping to an adjacent system" clicks the target node by its accessible name. All 15 specs compile; `jumpToFreshSystem` is API-driven and needed no change. + ## v0.9.1 — 2026-09-29 06:47 - UI layout pass (visual only — no game logic or API changes): CARGO HOLD // INVENTORY moved out of the LOCAL SYSTEM tab into a persistent panel in the right column; the UPCOMING ENCOUNTERS placeholder was removed entirely; the SHIP DATA placeholder was merged into VESSEL METRICS. - Grid: `260px / 1fr / 240px` → `320px / 1fr / 360px` (left pane ~+23%, right pane ~50% wider). diff --git a/package.json b/package.json index 72ef9b5..c811354 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "space-wanderer", - "version": "0.9.1", + "version": "0.9.2", "private": true, "scripts": { "dev": "next dev -p 3000", -- 2.54.0