From e79015b15cea002d3c16b86b44a5079e6cfb1152 Mon Sep 17 00:00:00 2001 From: Kamil Krzywda Date: Mon, 28 Sep 2026 19:13:47 +0000 Subject: [PATCH 1/4] ui: copy node-graph + progress components from scificn-ui (adds @radix-ui/react-progress) --- bun.lock | 3 + components/ui/node-graph.tsx | 299 +++++++++++++++++++++++++++++++++++ components/ui/progress.tsx | 80 ++++++++++ package.json | 1 + 4 files changed, 383 insertions(+) create mode 100644 components/ui/node-graph.tsx create mode 100644 components/ui/progress.tsx diff --git a/bun.lock b/bun.lock index 2c4a8ee..9d1f9ce 100644 --- a/bun.lock +++ b/bun.lock @@ -5,6 +5,7 @@ "": { "name": "space-wanderer", "dependencies": { + "@radix-ui/react-progress": "^1.1.8", "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-tabs": "^1.1.13", "class-variance-authority": "^0.7.1", @@ -136,6 +137,8 @@ "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.10", "", { "dependencies": { "@radix-ui/react-slot": "1.3.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-MucOnzh6hR5mid6VpkbglRAMYMjKLqRnGBbjXkzjK52fuQDd1qbkx78a5P40mkcnVXJdEVxm26E9OPAiUq7nBg=="], + "@radix-ui/react-progress": ["@radix-ui/react-progress@1.1.16", "", { "dependencies": { "@radix-ui/react-context": "1.2.2", "@radix-ui/react-primitive": "2.1.10" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-5XnomAsoZZCY+KNTxbIghpGqPruZvKFNlvcAljVAOdDRDsH4/OZQxhtwo5wdtoDM5R6MhJBb2sPnDuRFep3lzg=="], + "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.19", "", { "dependencies": { "@radix-ui/primitive": "1.1.7", "@radix-ui/react-collection": "1.1.15", "@radix-ui/react-compose-refs": "1.1.5", "@radix-ui/react-context": "1.2.2", "@radix-ui/react-direction": "1.1.4", "@radix-ui/react-id": "1.1.4", "@radix-ui/react-primitive": "2.1.10", "@radix-ui/react-use-callback-ref": "1.1.4", "@radix-ui/react-use-controllable-state": "1.2.6", "@radix-ui/react-use-is-hydrated": "0.1.3", "@radix-ui/react-use-layout-effect": "1.1.4" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-V9jI6hDjT7l3jsCQD9bLNvDLM3tH/gdbOTp7Tefp3hbbgCGQoK7tUvrWiRlcoBHIZ809ElXwNQwVo0B98LuTXQ=="], "@radix-ui/react-slot": ["@radix-ui/react-slot@1.3.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.5" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-qx7oqnYbxnK9kYI9m317qmFmEgo6ywqWvbTogdj7cL9p3/yx4M48p7Rnw5z3H890cL/ow/EeWJsuTykeZVXP5Q=="], diff --git a/components/ui/node-graph.tsx b/components/ui/node-graph.tsx new file mode 100644 index 0000000..9c64ba6 --- /dev/null +++ b/components/ui/node-graph.tsx @@ -0,0 +1,299 @@ +'use client' + +import * as React from 'react' +import { cn } from '@/lib/utils' + +export type NodeGraphVariant = 'DEFAULT' | 'ACTIVE' | 'WARNING' | 'CRITICAL' +export type NodeStatus = 'ACTIVE' | 'OFFLINE' | 'WARNING' | 'CRITICAL' | 'NEUTRAL' + +export interface NodeGraphNode { + id: string + label: string + x: number // 0–100 coordinate space + y: number + status?: NodeStatus + sublabel?: string +} + +export interface NodeGraphEdge { + from: string + to: string + label?: string + animated?: boolean +} + +export interface NodeGraphProps extends React.HTMLAttributes { + nodes: NodeGraphNode[] + edges: NodeGraphEdge[] + variant?: NodeGraphVariant + title?: string + height?: number + directed?: boolean +} + +const VARIANT_COLORS: Record = { + DEFAULT: 'var(--text-secondary)', + ACTIVE: 'var(--color-green)', + WARNING: 'var(--color-amber)', + CRITICAL: 'var(--color-red)', +} + +const STATUS_COLOR: Record = { + ACTIVE: 'var(--color-green)', + OFFLINE: 'var(--border)', + WARNING: 'var(--color-amber)', + CRITICAL: 'var(--color-red)', + NEUTRAL: 'var(--text-muted)', +} + +const NODE_W = 100 +const NODE_H = 36 + +const NodeGraph = React.forwardRef( + ( + { + className, + nodes, + edges, + variant = 'ACTIVE', + title, + height = 320, + directed = false, + style, + ...props + }, + ref + ) => { + const containerRef = React.useRef(null) + const [svgW, setSvgW] = React.useState(600) + const [hovered, setHovered] = React.useState(null) + + React.useEffect(() => { + if (!containerRef.current) return + const ro = new ResizeObserver(entries => { + setSvgW(entries[0].contentRect.width) + }) + ro.observe(containerRef.current) + return () => ro.disconnect() + }, []) + + const color = VARIANT_COLORS[variant] + const markerId = `arrow-${variant}` + + function nodePx(node: NodeGraphNode) { + return (node.x / 100) * svgW + } + + function nodePy(node: NodeGraphNode) { + return (node.y / 100) * height + } + + function edgeEndpoints(fromNode: NodeGraphNode, toNode: NodeGraphNode) { + const fx = nodePx(fromNode) + const fy = nodePy(fromNode) + const tx = nodePx(toNode) + const ty = nodePy(toNode) + const dx = tx - fx + const dy = ty - fy + const dist = Math.sqrt(dx * dx + dy * dy) + if (dist < 1) return { x1: fx, y1: fy, x2: tx, y2: ty } + + const ux = dx / dist + const uy = dy / dist + const hw = NODE_W / 2 + const hh = NODE_H / 2 + + const tW = Math.abs(ux) > 0.001 ? hw / Math.abs(ux) : Infinity + const tH = Math.abs(uy) > 0.001 ? hh / Math.abs(uy) : Infinity + const t = Math.min(tW, tH) + + return { + x1: fx + ux * t, + y1: fy + uy * t, + x2: tx - ux * t, + y2: ty - uy * t, + } + } + + const nodeMap = new Map(nodes.map(n => [n.id, n])) + const hasAnimatedEdge = edges.some(e => e.animated) + + return ( +
+ {title && ( +
+ {title} +
+ )} + +
+ + + {directed && ( + + + + )} + + + {hasAnimatedEdge && ( + + )} + + {/* Edges */} + {edges.map((edge, ei) => { + const fromNode = nodeMap.get(edge.from) + const toNode = nodeMap.get(edge.to) + if (!fromNode || !toNode) return null + + const { x1, y1, x2, y2 } = edgeEndpoints(fromNode, toNode) + const mx = (x1 + x2) / 2 + const my = (y1 + y2) / 2 + + return ( + + + {edge.label && ( + + {edge.label} + + )} + + ) + })} + + {/* Nodes */} + {nodes.map(node => { + const cx = nodePx(node) + const cy = nodePy(node) + const isHovered = hovered === node.id + const statusColor = node.status ? STATUS_COLOR[node.status] : color + + return ( + setHovered(node.id)} + onMouseLeave={() => setHovered(null)} + style={{ cursor: 'default' }} + > + {/* Background */} + + {/* Hover tint */} + {isHovered && ( + + )} + {/* Border */} + + + {/* Label */} + + {node.label.toUpperCase()} + + + {/* Sublabel */} + {node.sublabel && ( + + {node.sublabel} + + )} + + ) + })} + +
+
+ ) + } +) +NodeGraph.displayName = 'NodeGraph' + +export { NodeGraph } diff --git a/components/ui/progress.tsx b/components/ui/progress.tsx new file mode 100644 index 0000000..62bccac --- /dev/null +++ b/components/ui/progress.tsx @@ -0,0 +1,80 @@ +import * as React from 'react' +import * as ProgressPrimitive from '@radix-ui/react-progress' +import { cn } from '@/lib/utils' + +export interface ProgressProps + extends React.ComponentPropsWithoutRef { + showValue?: boolean + label?: string +} + +const Progress = React.forwardRef< + React.ElementRef, + ProgressProps +>(({ className, value = 0, showValue = true, label, ...props }, ref) => { + const pct = Math.min(Math.max(value ?? 0, 0), 100) + const filled = Math.round((pct / 100) * 20) + const empty = 20 - filled + const barText = '='.repeat(filled) + ' '.repeat(empty) + + return ( +
+ {label && ( + + {label} + + )} + + + {/* Bracket-style visual */} +
+ [ + {barText} + ] + {showValue && ( + + {Math.round(pct)}% + + )} +
+ + {/* Accessible indicator (visually hidden, only for Radix a11y) */} + +
+
+ ) +}) +Progress.displayName = ProgressPrimitive.Root.displayName + +export { Progress } diff --git a/package.json b/package.json index 45b1db7..4b49326 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { + "@radix-ui/react-progress": "^1.1.8", "@radix-ui/react-slot": "^1.2.4", "@radix-ui/react-tabs": "^1.1.13", "class-variance-authority": "^0.7.1", -- 2.54.0 From 5291e0fa6ed265390c690a70691bf01cfca931ab Mon Sep 17 00:00:00 2001 From: Kamil Krzywda Date: Mon, 28 Sep 2026 19:18:34 +0000 Subject: [PATCH 2/4] =?UTF-8?q?app:=20rebuild=20operations=20tabs=20as=207?= =?UTF-8?q?-tab=20game=20template=20(world=20map,=20local=20system,=20crew?= =?UTF-8?q?,=20ship=20systems,=20skill=20tree,=20missions,=20event=20log)?= =?UTF-8?q?=20=E2=80=94=20visual=20only?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/mission-control.tsx | 436 +++++++++++++++++++++++---------- 1 file changed, 301 insertions(+), 135 deletions(-) diff --git a/components/mission-control.tsx b/components/mission-control.tsx index bceb7f4..0741b72 100644 --- a/components/mission-control.tsx +++ b/components/mission-control.tsx @@ -2,11 +2,12 @@ import { useEffect, useState } from 'react' import { Badge } from '@/components/ui/badge' -import { BarChart } from '@/components/ui/bar-chart' import { Alert, AlertDescription, AlertTitle } from '@/components/ui/alert' -import { LineChart } from '@/components/ui/line-chart' +import { Button } from '@/components/ui/button' +import { Card, CardContent, CardFooter, CardHeader, CardTitle } from '@/components/ui/card' +import { NodeGraph, type NodeGraphEdge, type NodeGraphNode } from '@/components/ui/node-graph' import { Panel, PanelContent, PanelHeader, PanelTitle } from '@/components/ui/panel' -import { ProgressRing } from '@/components/ui/progress-ring' +import { Progress } from '@/components/ui/progress' import { StatCard } from '@/components/ui/stat-card' import { StatusGrid, type SystemEntry } from '@/components/ui/status-grid' import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs' @@ -42,19 +43,82 @@ const shipSystems: SystemEntry[] = [ { name: 'COMMS', status: 'ACTIVE' }, ] -const bayOutput = [ - { label: 'PWR', value: 14 }, - { label: 'NAV', value: 9 }, - { label: 'COM', value: 6 }, - { label: 'WPN', value: 3 }, - { label: 'LSS', value: 12 }, - { label: 'ENG', value: 11 }, - { label: 'BAY', value: 7 }, - { label: 'MED', value: 5 }, +// WORLD MAP — every node is a solar system, edges are jump routes. +const worldMapNodes: NodeGraphNode[] = [ + { id: 'kepler', label: 'Kepler Drift', x: 50, y: 48, status: 'ACTIVE', sublabel: 'CURRENT' }, + { id: 'vesper', label: 'Vesper Reach', x: 22, y: 26, status: 'NEUTRAL', sublabel: '3 WORLDS' }, + { id: 'hollow', label: 'Hollow Gate', x: 74, y: 20, status: 'NEUTRAL', sublabel: '5 WORLDS' }, + { id: 'cinder', label: 'Cinder Field', x: 86, y: 56, status: 'WARNING', sublabel: 'RADIATION' }, + { id: 'lantern', label: 'Lantern Void', x: 30, y: 74, status: 'NEUTRAL', sublabel: '2 WORLDS' }, + { id: 'sable', label: 'Sable Reach', x: 8, y: 46, status: 'OFFLINE', sublabel: 'UNKNOWN' }, + { id: 'orpheus', label: 'Orpheus Deep', x: 52, y: 10, status: 'NEUTRAL', sublabel: '7 WORLDS' }, + { id: 'mire', label: 'Mire Station', x: 68, y: 84, status: 'ACTIVE', sublabel: 'OUTPOST' }, + { id: 'thalassa', label: 'Thalassa Bend', x: 12, y: 86, status: 'NEUTRAL', sublabel: '4 WORLDS' }, + { id: 'kuiper', label: 'Kuiper Shard', x: 90, y: 30, status: 'CRITICAL', sublabel: 'WARZONE' }, ] -const hullTrack = [100, 98, 97, 96, 94, 95, 92, 90, 89, 87] -const hullLabels = hullTrack.map((_, i) => `T-${hullTrack.length - 1 - i}`) +const worldMapEdges: NodeGraphEdge[] = [ + { from: 'kepler', to: 'vesper', animated: true }, + { from: 'kepler', to: 'hollow', label: 'JUMP-2' }, + { from: 'kepler', to: 'lantern', animated: true }, + { from: 'kepler', to: 'mire' }, + { from: 'vesper', to: 'orpheus' }, + { from: 'hollow', to: 'kuiper', label: 'JUMP-1' }, + { from: 'cinder', to: 'kuiper' }, + { from: 'lantern', to: 'thalassa' }, + { from: 'sable', to: 'vesper' }, + { from: 'mire', to: 'cinder' }, +] + +// LOCAL SYSTEM — the current system (regenerated per save after each jump, later). +const localSystemNodes: NodeGraphNode[] = [ + { id: 'star', label: 'Kepler-442', x: 50, y: 50, status: 'ACTIVE', sublabel: 'G2 STAR' }, + { id: 'aurelle', label: 'Aurelle', x: 18, y: 30, status: 'ACTIVE', sublabel: 'TERRESTRIAL' }, + { id: 'brimstone', label: 'Brimstone', x: 76, y: 24, status: 'NEUTRAL', sublabel: 'GAS GIANT' }, + { id: 'ashfall', label: 'Ashfall', x: 90, y: 52, status: 'WARNING', sublabel: 'MOON' }, + { id: 'cinderrock', label: 'Cinderrock', x: 74, y: 80, status: 'NEUTRAL', sublabel: 'ROCKY' }, + { id: 'driftband', label: 'Driftband', x: 30, y: 78, status: 'WARNING', sublabel: 'ASTEROID RING' }, + { id: 'palemoon', label: 'Palemoon', x: 8, y: 54, status: 'NEUTRAL', sublabel: 'MOON' }, +] + +const localSystemEdges: NodeGraphEdge[] = [ + { from: 'star', to: 'aurelle' }, + { from: 'star', to: 'brimstone' }, + { from: 'brimstone', to: 'ashfall', label: 'MOON' }, + { from: 'star', to: 'cinderrock' }, + { from: 'star', to: 'driftband' }, + { from: 'aurelle', to: 'palemoon', label: 'MOON' }, +] + +const explorablePlanets = [ + { name: 'AURELLE', type: 'TERRESTRIAL // OCEAN CONTINENTS', state: 'UNEXPLORED' as const }, + { name: 'BRIMSTONE', type: 'GAS GIANT // CLOUD DECKS', state: 'SCANNED' as const }, + { name: 'ASHFALL (MOON)', type: 'VOLCANIC MOON // SULFUR VEINS', state: 'UNEXPLORED' as const }, + { name: 'CINDERROCK', type: 'ROCKY // IRON RICH', state: 'UNEXPLORED' as const }, + { name: 'DRIFTBAND', type: 'ASTEROID FIELD // HAZARD', state: 'HAZARD' as const }, +] + +const crew = [ + { name: 'CMDR. E. VANCE', role: 'COMMANDER', good: 'STEADY UNDER PRESSURE', bad: 'RIGID — SLOW TO ADAPT' }, + { name: 'LT. M. OKONKWO', role: 'PILOT', good: 'RECKLESS COURAGE', bad: 'SHORT FUSE' }, + { name: 'DR. I. SORENSEN', role: 'MEDIC', good: 'CALM IN A CRISIS', bad: 'OVERCAUTIOUS' }, + { name: 'R. "SPARK" DELACRUZ', role: 'ENGINEER', good: 'FIXES ANYTHING', bad: 'CARELESS WITH FUSES' }, +] + +const shipSystemCards = [ + { name: 'REACTOR', level: 'LV 2', status: 'ACTIVE' as const, actions: ['EXPAND', 'UPGRADE'] }, + { name: 'LIFE SUPPORT', level: 'LV 1', status: 'ACTIVE' as const, actions: ['EXPAND', 'UPGRADE'] }, + { name: 'NAV COMPUTER', level: 'LV 3', status: 'SCANNING' as const, actions: ['EXPAND', 'UPGRADE'] }, + { name: 'WEAPONS', level: 'OFFLINE', status: 'OFFLINE' as const, actions: ['BUILD NEW'] }, + { name: 'COMMS ARRAY', level: 'LV 1', status: 'ACTIVE' as const, actions: ['EXPAND', 'UPGRADE'] }, +] + +const skillCards = [ + { name: 'ADVANCED FUSION CORE', unlocks: 'REACTOR LV 4 + OVERCLOCK', state: 'AVAILABLE' as const }, + { name: 'HULL PLATING SCHEMATICS', unlocks: 'BUILD ARMOR BAY MODULES', state: 'AVAILABLE' as const }, + { name: 'CREW TRAINING PROTOCOLS', unlocks: 'RETRAIN CREW TRAITS', state: 'LOCKED' as const }, + { name: 'JUMP DRIVE TUNING', unlocks: 'SHORTER JUMP COOLDOWN', state: 'LOCKED' as const }, +] const encounters = [ { name: 'DERELICT FREIGHTER', when: 'T-07:14', id: '#E-102' }, @@ -237,7 +301,7 @@ export function MissionControl() { background: 'var(--border)', }} > - {/* ── LEFT: SHIP SYSTEMS + VESSEL METRICS ── */} + {/* ── LEFT: SHIP SYSTEMS + VESSEL METRICS (mirrors the SHIP SYSTEMS tab) ── */}
VESSEL METRICS -
- - - -
- -
- {[ - { label: 'HULL INTEGRITY', note: 'last scan T-02' }, - { label: 'REACTOR OUTPUT', note: 'idle cycle' }, - { label: 'CREW ROSTER', note: 'of 6 berths' }, - ].map((item) => ( -
-
- {item.label} -
-
- {item.note} -
-
- ))} +
+ + +
- {/* ── CENTER: OPERATIONS TABS ── */} + {/* ── CENTER: GAME TEMPLATE TABS (visual only — no handlers, placeholder data) ── */}
- + - SYSTEMS - NEXT MISSION - LOG - HULL TRACK + WORLD MAP + LOCAL SYSTEM + CREW + SHIP SYSTEMS + SKILL TREE + MISSIONS + EVENT LOG - {/* SYSTEMS TAB */} - - + -
- {[ - { label: 'TOTAL MODULES', value: 34 }, - { label: 'ONLINE', value: 29 }, - { label: 'EFFICIENCY', value: '85%' }, - ].map((s) => ( + + + {/* LOCAL SYSTEM TAB */} + +
+ + +
-
- {s.label} -
-
- {s.value} -
+ EXPLORABLE WORLDS // FIND THINGS HERE (PLACEHOLDER)
+ {explorablePlanets.map((planet, i) => ( +
+
+
+ {planet.name} +
+
+ {planet.type} +
+
+ + {planet.state} + +
+ ))} +
+
+
+ + {/* CREW TAB */} + +
+ {crew.map((member) => ( + + +
+ {member.name} + + ACTIVE + +
+

+ {member.role} +

+
+ +
+ + + {member.good} +
+
+ - + {member.bad} +
+
+
))}
- {/* NEXT MISSION TAB */} - + {/* SHIP SYSTEMS TAB */} + +
+ {shipSystemCards.map((system) => ( + + +
+ {system.name} + + {system.level} + +
+
+ +

+ MODULE BAY // PLACEHOLDER READOUT +

+
+ + {system.actions.map((action) => ( + + ))} + +
+ ))} +
+
+ + {/* SKILL TREE TAB */} + +
+ {skillCards.map((skill) => ( + + +
+ {skill.name} + + {skill.state} + +
+
+ +

+ UNLOCKS: {skill.unlocks} +

+
+ + + +
+ ))} +
+
+ + {/* MISSIONS TAB */} +
@@ -466,8 +644,8 @@ export function MissionControl() {
- {/* LOG TAB */} - + {/* EVENT LOG TAB */} + - - {/* HULL TRACK TAB */} - - - -- 2.54.0 From 4be04a779a115fbd42a84c3ae7639e63393591dd Mon Sep 17 00:00:00 2001 From: Kamil Krzywda Date: Mon, 28 Sep 2026 19:19:33 +0000 Subject: [PATCH 3/4] e2e: smoke spec for the 7-tab game template (role=tab triggers + event log switch) --- e2e/smoke.spec.ts | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/e2e/smoke.spec.ts b/e2e/smoke.spec.ts index 1b89f6e..e912262 100644 --- a/e2e/smoke.spec.ts +++ b/e2e/smoke.spec.ts @@ -5,18 +5,19 @@ test('landing page loads with the SpaceWanderer title', async ({ page }) => { await expect(page.getByRole('heading', { name: 'SpaceWanderer' })).toBeVisible(); }); -test('mission control layout renders scificn-ui components', async ({ page }) => { +test('game template tabs render (scificn-ui component layer wired in)', async ({ page }) => { await page.goto('/'); - // Stat card, tab trigger and footer badge prove the copied component layer is wired in. + // Stat card + footer badge prove the layout shell; Radix Tabs.Trigger renders role="tab". await expect(page.getByText('NEXT ENCOUNTER')).toBeVisible(); - // Radix Tabs.Trigger renders role="tab", not a plain button. - await expect(page.getByRole('tab', { name: 'SYSTEMS' })).toBeVisible(); + await expect(page.getByRole('tab', { name: 'WORLD MAP' })).toBeVisible(); + await expect(page.getByRole('tab', { name: 'CREW' })).toBeVisible(); + await expect(page.getByRole('tab', { name: 'SKILL TREE' })).toBeVisible(); await expect(page.getByText('SYS ONLINE')).toBeVisible(); }); -test('operations tabs switch panels client-side', async ({ page }) => { +test('tabs switch panels client-side (event log terminal)', async ({ page }) => { await page.goto('/'); - await page.getByRole('tab', { name: 'LOG' }).click(); + await page.getByRole('tab', { name: 'EVENT LOG' }).click(); await expect(page.getByText('SHIP LOG // PLACEHOLDER FEED')).toBeVisible(); }); -- 2.54.0 From 9faf0f3a33502df6e573cf58562e81f030c8b8c1 Mon Sep 17 00:00:00 2001 From: Kamil Krzywda Date: Mon, 28 Sep 2026 19:20:08 +0000 Subject: [PATCH 4/4] =?UTF-8?q?release:=20v0.2.0=20=E2=80=94=20game=20tab?= =?UTF-8?q?=20template=20(CHANGELOG=20+=20version=20bump)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CHANGELOG.md | 5 +++++ ORCHESTRATOR.md | 2 +- package.json | 2 +- 3 files changed, 7 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0a20daf..a3ba43f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## v0.2.0 — 2026-09-28 19:19 +- Game interface template (visual only, zero game logic): the operations panel in `components/mission-control.tsx` is now seven tabs — WORLD MAP (NodeGraph of 10 placeholder solar systems linked by jump routes), LOCAL SYSTEM (NodeGraph of star + planets/moons plus an explorable-worlds list), CREW (crew cards with name/role and good/bad trait placeholders), SHIP SYSTEMS (system cards with inert EXPAND / UPGRADE / BUILD NEW affordances), SKILL TREE (tech cards unlocking new system builds/upgrades), MISSIONS (previous NEXT MISSION content), EVENT LOG (ship log terminal). All data is local placeholder constants. +- Left column: VESSEL METRICS progress rings replaced with scificn Progress bracket bars; SHIP SYSTEMS status panel, header stat cards and right column unchanged. +- New scificn-ui components copied into `components/ui/`: NodeGraph, Progress (+ `@radix-ui/react-progress` dependency). + ## v0.1.0 — 2026-09-28 15:47 - Initial scaffold (stack proof): base stack copied/adapted from Flight-Ops (`../flight`) — Next.js 16 App Router + React 19 + TypeScript strict, bun package manager, Node 22 in CI/Docker. - Single-player only by design: NO auth/accounts/multiplayer anywhere (Flight-Ops's users/sessions/admin/leaderboard patterns deliberately not copied). diff --git a/ORCHESTRATOR.md b/ORCHESTRATOR.md index 46041de..a591d83 100644 --- a/ORCHESTRATOR.md +++ b/ORCHESTRATOR.md @@ -46,7 +46,7 @@ Credentials are deliberately NOT stored in this file — see §1 for where they ## 7. App architecture quick map - **Scaffolded (v0.1.0)** — stack proof only, no game systems yet; the rest of this section is what's built + what lands next. - Next.js 16 App Router + React 19 + TypeScript strict, bun package manager (bun.lock), Node 22 in CI/Docker. -- **Frontend component layer: scificn-ui v0.1.0** (https://scificn.dev) — copy-paste model on Radix + Tailwind v4; components live in `components/ui/` (Button, Panel, Badge, Card, Alert, Tabs, StatCard, Terminal, StatusGrid, ProgressRing, BarChart, LineChart), theme tokens in `app/globals.css`. The app's interface base is the scificn-ui **Mission Control** showcase layout (https://www.scificn.dev/showcase/mission-control) rebuilt as `components/mission-control.tsx` and rendered from `app/page.tsx`, with placeholder ship content — no game logic yet. Deliberate deviation from upstream: `--text-muted` brightened `#4A4A40` → `#6B6B5C` (+12pt lightness) for readability on the near-black background. Upstream repo has **no LICENSE file** — user-approved risk, keep components minimal and re-verify licensing before any redistribution of this codebase. +- **Frontend component layer: scificn-ui v0.1.0** (https://scificn.dev) — copy-paste model on Radix + Tailwind v4; components live in `components/ui/` (Button, Panel, Badge, Card, Alert, Tabs, StatCard, Terminal, StatusGrid, ProgressRing, BarChart, LineChart, NodeGraph, Progress), theme tokens in `app/globals.css`. The app's interface base is the scificn-ui **Mission Control** showcase layout (https://www.scificn.dev/showcase/mission-control) rebuilt as `components/mission-control.tsx` and rendered from `app/page.tsx`, with placeholder ship content — no game logic yet. As of v0.2.0 the operations panel is a 7-tab visual template (WORLD MAP, LOCAL SYSTEM, CREW, SHIP SYSTEMS, SKILL TREE, MISSIONS, EVENT LOG) built on NodeGraph + Card widgets; all data are local placeholder constants until real game systems land. Deliberate deviation from upstream: `--text-muted` brightened `#4A4A40` → `#6B6B5C` (+12pt lightness) for readability on the near-black background. Upstream repo has **no LICENSE file** — user-approved risk, keep components minimal and re-verify licensing before any redistribution of this codebase. - SQLite via Drizzle ORM + sql.js (WASM, kept external in next.config.ts) at db/space-wanderer.db volume-mounted; schema designed to switch to Postgres later. Schema is EMPTY at scaffold stage — first game tables land with the turn engine. - Vitest unit tests colocated (`**/*.test.ts`); Playwright e2e in `e2e/`, CI-only. - Server-authoritative TURN engine in lib/ — unlike Flight-Ops there is NO tick loop or sim clock: state advances only when a player action resolves a turn, and every action endpoint returns the full post-action snapshot which the client applies immediately (polling optional/later for multiplayer). diff --git a/package.json b/package.json index 4b49326..f17d50c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "space-wanderer", - "version": "0.1.0", + "version": "0.2.0", "private": true, "scripts": { "dev": "next dev -p 3000", -- 2.54.0