From 83a255754cbcb57ca80106e4f763198f64705506 Mon Sep 17 00:00:00 2001 From: Lucas Winther Date: Tue, 20 Oct 2026 03:39:52 +0200 Subject: [PATCH] feat(settings): keep the game tab when switching games Codes in one game now opens Codes in the next. The reader app holds the tab and GameSettings reads it, instead of each game page minting its own. Co-Authored-By: Claude Opus 5.5 --- docs/DESIGN.md | 3 ++- src/client/ReaderApp.tsx | 14 ++++++++----- .../components/redesign/GameSettings.tsx | 15 +++++++++---- test/game-settings.test.tsx | 21 +++++++++++++++++++ 4 files changed, 43 insertions(+), 10 deletions(-) diff --git a/docs/DESIGN.md b/docs/DESIGN.md index c58dd35..06cc33d 100644 --- a/docs/DESIGN.md +++ b/docs/DESIGN.md @@ -637,7 +637,8 @@ legend on the same baseline as "Next: …". - **Settings.** About, Changelog, Data & sources (list options first), Region & time, Appearance (interface, theme, scheme, urgency colours, default view, game order, timeline design, focus bar, checklist group), Your own events and games, Backup (tiers plus a manual "Your backups" list), and - per-game Setup / Codes / About. + per-game Setup / Codes / About. Switching to another game keeps the open tab (2026-10-20, owner): + Codes in one game opens Codes in the next. - **Redeem codes (2026-10-18, owner).** A game's Codes tab lists the codes still to use first. A code redeemed on every server the reader plays, or past its stated expiry, is greyed and struck through in a "Redeemed or expired" section below, with its Redeemed box still there to undo. A diff --git a/src/client/ReaderApp.tsx b/src/client/ReaderApp.tsx index bbc30ae..30f781a 100644 --- a/src/client/ReaderApp.tsx +++ b/src/client/ReaderApp.tsx @@ -88,7 +88,10 @@ import { type SettingsPage, } from "./components/redesign/SettingsLayout.tsx"; import { ChangelogPage } from "./components/redesign/ChangelogPage.tsx"; -import { GameSettings } from "./components/redesign/GameSettings.tsx"; +import { + GameSettings, + type GameTab, +} from "./components/redesign/GameSettings.tsx"; import { RoutinesPage } from "./components/redesign/RoutinesPage.tsx"; import { DailyPanel } from "./components/redesign/DailyPanel.tsx"; import { RoutineGroup } from "./components/redesign/RoutineGroup.tsx"; @@ -292,7 +295,7 @@ function ReaderContent({ }, [tour]); const touring = tour === "new" || tour === "returning" || tour === "replay"; const tourForced = tour === "new" || tour === "returning"; - const [gameTab, setGameTab] = useState<"setup" | "codes">("setup"); + const [gameTab, setGameTab] = useState("setup"); const [codesGame, setCodesGame] = useState( startRoute?.game, ); @@ -1021,8 +1024,8 @@ function ReaderContent({ setSettingsPage(p); }} onGame={(g) => { + // The tab stays: Codes in one game opens Codes in the next. setCodesGame(g); - setGameTab("setup"); setSettingsPage("games"); }} > @@ -1041,8 +1044,9 @@ function ReaderContent({ /> ) : settingsPage === "games" && codesGame ? ( Promise; +export const GAME_TABS = ["setup", "codes", "about"] as const; +export type GameTab = (typeof GAME_TABS)[number]; const hostOf = (url: string) => { try { return new URL(url).hostname.replace(/^www\./, ""); @@ -42,14 +44,17 @@ export function GameSettings({ onAction, codes = null, initialGame, - initialTab = "setup", + tab: heldTab, + onTab, }: { state: ReaderState; sources: SourceHealth[]; onAction: ActionHandler; codes?: CodesFeed | null; initialGame?: string | undefined; - initialTab?: "setup" | "codes"; + // When the parent holds the tab, switching games keeps the reader on it. + tab?: GameTab; + onTab?: (tab: GameTab) => void; }) { const renderedMeta = useGameMeta(); const games = orderGames( @@ -72,7 +77,9 @@ export function GameSettings({ games[0] ?? "", ); - const [tab, setTab] = useState(initialTab); + const [ownTab, setOwnTab] = useState("setup"); + const tab = heldTab ?? ownTab, + setTab = onTab ?? setOwnTab; const [trackingAccount, setTrackingAccount] = useState(null); const [addingServer, setAddingServer] = useState(false); const [server, setServer] = useState("asia"); @@ -138,7 +145,7 @@ export function GameSettings({ ) : null}
- {["setup", "codes", "about"].map((id) => ( + {GAME_TABS.map((id) => (