feat(ui): keep the active operations tab across page refreshes (#27)
CI / build + unit + e2e (pull_request) Successful in 2m15s

Persist the selected tab ID to localStorage (sw-active-tab) on every change
and restore it on mount when it matches one of the seven valid tab IDs. The
Radix Tabs become controlled for this; SSR and first client render still agree
on the default, so there is no hydration mismatch.
This commit is contained in:
2026-09-29 09:42:18 +00:00
parent 4b030716f7
commit f4690bc1a2
4 changed files with 38 additions and 2 deletions
+21 -1
View File
@@ -38,6 +38,11 @@ function useNarrow(threshold = 900): boolean {
return narrow
}
// Must match the TabsTrigger values in the render below — used to validate the tab persisted for
// #27 (an unknown stored value falls back to the default).
const TAB_IDS = ['world-map', 'local-system', 'crew', 'ship-systems', 'skill-tree', 'missions', 'event-log']
const ACTIVE_TAB_KEY = 'sw-active-tab'
export function MissionControl() {
const narrow = useNarrow()
const [tick, setTick] = useState(0)
@@ -49,6 +54,9 @@ export function MissionControl() {
const [system, setSystem] = useState<SystemState | null>(null)
const [techs, setTechs] = useState<TechDto[] | null>(null)
const [crewCount, setCrewCount] = useState<number | null>(null)
// #27 — the active tab survives a page refresh. Restored from localStorage on mount; null until
// then so SSR and the first client render agree on the default tab (no hydration mismatch).
const [activeTab, setActiveTab] = useState<string | null>(null)
useEffect(() => {
let cancelled = false
@@ -98,6 +106,12 @@ export function MissionControl() {
return () => clearInterval(id)
}, [])
// #27 — restore the persisted tab once on mount (validated against TAB_IDS).
useEffect(() => {
const stored = window.localStorage.getItem(ACTIVE_TAB_KEY)
if (stored && TAB_IDS.includes(stored)) setActiveTab(stored)
}, [])
const timeStr = new Date().toLocaleTimeString('en-GB', { hour12: false })
// Slugs of the researched techs — drives the effective caps + build gates in the SHIP SYSTEMS
@@ -356,7 +370,13 @@ export function MissionControl() {
</PanelHeader>
<PanelContent style={{ padding: 0, flex: 1, minHeight: 0, overflowY: 'auto' }}>
<Tabs defaultValue="world-map">
<Tabs
value={activeTab ?? 'world-map'}
onValueChange={(v) => {
setActiveTab(v)
window.localStorage.setItem(ACTIVE_TAB_KEY, v) // #27 — keep the tab across refreshes
}}
>
<TabsList
style={{
padding: '0 1rem',