From c7bb9a09a893264b53cb410b74873fbd58c82145 Mon Sep 17 00:00:00 2001 From: Antoine Marot Date: Mon, 3 Aug 2026 09:26:09 +0000 Subject: [PATCH] feat(game): hide graded modes behind a Configure-settings toggle MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Game Mode's landing page always showed a top-level Mode card with three choices (European grid — demo, France THT — graded, France EHV Matpower — graded) before the player had entered anything. The two graded families are advanced datasets a newcomer doesn't need to see, and demo is the only mode most sessions ever use. The Mode card is now hidden by default. Its former slot inside Configure settings — the "Difficulty (network)" select, which only ever applied to demo mode — is replaced by a toggle button ("🌐 Other modes / regions"). Activating it reveals the exact same Mode picker (buttons + blurbs, unchanged) plus, once demo is selected, the difficulty select right below it, matching the current layout. Updated GameConfigScreen.test.tsx: tests that exercised France THT / Matpower now open Configure settings and the new mode toggle first (openModePicker() helper), and the settings-toggle test asserts the mode buttons and difficulty select stay hidden until the new toggle is used. Full suite: 1917 passed / 3 skipped. tsc -b clean, lint 0 errors, code-quality gate OK, production build OK. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01FSWwhkaPiAsxasECiBws8k Signed-off-by: Antoine Marot --- frontend/src/game/GameConfigScreen.test.tsx | 32 +++++- frontend/src/game/GameConfigScreen.tsx | 113 ++++++++++++-------- 2 files changed, 98 insertions(+), 47 deletions(-) diff --git a/frontend/src/game/GameConfigScreen.test.tsx b/frontend/src/game/GameConfigScreen.test.tsx index cf9017f..48e6976 100644 --- a/frontend/src/game/GameConfigScreen.test.tsx +++ b/frontend/src/game/GameConfigScreen.test.tsx @@ -23,6 +23,12 @@ afterEach(() => { cleanup(); vi.clearAllMocks(); }); const sessionInput = () => screen.getByTestId('game-session-name') as HTMLInputElement; +/** France THT / Matpower live behind Configure settings' mode toggle. */ +const openModePicker = () => { + fireEvent.click(screen.getByTestId('game-settings-toggle')); + fireEvent.click(screen.getByTestId('game-mode-picker-toggle')); +}; + describe('GameConfigScreen landing', () => { it('keeps Start disabled until a player name is entered', () => { render(); @@ -109,15 +115,30 @@ describe('GameConfigScreen landing', () => { expect(screen.getByTestId('game-network-preview')).toBeInTheDocument(); }); - it('hides settings by default and reveals timer / difficulty / studies on toggle', () => { + it('hides settings by default and reveals timer / mode toggle / studies on toggle', () => { render(); expect(screen.queryByText(/Time limit per study/)).not.toBeInTheDocument(); fireEvent.click(screen.getByTestId('game-settings-toggle')); expect(screen.getByText(/Time limit per study/)).toBeInTheDocument(); - expect(screen.getByText(/Difficulty \(network\)/)).toBeInTheDocument(); + expect(screen.getByTestId('game-mode-picker-toggle')).toBeInTheDocument(); expect(screen.getByText(/Studies \(/)).toBeInTheDocument(); }); + it('keeps other modes and the network-difficulty picker hidden until the mode toggle is used', () => { + render(); + fireEvent.click(screen.getByTestId('game-settings-toggle')); + expect(screen.queryByTestId('game-mode-tht')).not.toBeInTheDocument(); + expect(screen.queryByTestId('game-mode-matpower')).not.toBeInTheDocument(); + expect(screen.queryByText(/Difficulty \(network\)/)).not.toBeInTheDocument(); + + fireEvent.click(screen.getByTestId('game-mode-picker-toggle')); + expect(screen.getByTestId('game-mode-demo')).toBeInTheDocument(); + expect(screen.getByTestId('game-mode-tht')).toBeInTheDocument(); + expect(screen.getByTestId('game-mode-matpower')).toBeInTheDocument(); + // Demo is still the active mode, so its network-difficulty select shows too. + expect(screen.getByText(/Difficulty \(network\)/)).toBeInTheDocument(); + }); + it('starts a session with the entered config', async () => { const onStart = vi.fn(); render(); @@ -140,6 +161,7 @@ describe('GameConfigScreen — France THT mode', () => { render(); // Demo is the default: its studies summary + network preview are shown. expect(screen.getByTestId('game-studies-summary')).toBeInTheDocument(); + openModePicker(); fireEvent.click(screen.getByTestId('game-mode-tht')); expect(screen.getByTestId('game-tht-difficulty')).toBeInTheDocument(); expect(screen.getByTestId('game-tht-count')).toBeInTheDocument(); @@ -156,6 +178,7 @@ describe('GameConfigScreen — France THT mode', () => { render(); fireEvent.change(screen.getByTestId('game-player'), { target: { value: 'amarot' } }); await waitFor(() => expect(sessionInput().value).toBe('amarot — session 3')); + openModePicker(); fireEvent.click(screen.getByTestId('game-mode-tht')); fireEvent.change(screen.getByTestId('game-tht-count'), { target: { value: '3' } }); fireEvent.click(screen.getByTestId('game-start')); @@ -171,6 +194,7 @@ describe('GameConfigScreen — France THT mode', () => { it('caps the number of cases at the pool size', () => { render(); + openModePicker(); fireEvent.click(screen.getByTestId('game-mode-tht')); const count = screen.getByTestId('game-tht-count') as HTMLInputElement; fireEvent.change(count, { target: { value: '999999' } }); @@ -181,6 +205,7 @@ describe('GameConfigScreen — France THT mode', () => { it('still requires a player name to start in THT mode', () => { render(); + openModePicker(); fireEvent.click(screen.getByTestId('game-mode-tht')); expect(screen.getByTestId('game-start')).toBeDisabled(); }); @@ -189,6 +214,7 @@ describe('GameConfigScreen — France THT mode', () => { describe('GameConfigScreen — France EHV (Matpower) mode', () => { it('is a third top-level mode, exclusive with demo and France THT', () => { render(); + openModePicker(); fireEvent.click(screen.getByTestId('game-mode-matpower')); expect(screen.getByTestId('game-matpower-difficulty')).toBeInTheDocument(); expect(screen.getByTestId('game-matpower-count')).toBeInTheDocument(); @@ -208,6 +234,7 @@ describe('GameConfigScreen — France EHV (Matpower) mode', () => { render(); fireEvent.change(screen.getByTestId('game-player'), { target: { value: 'amarot' } }); await waitFor(() => expect(sessionInput().value).toBe('amarot — session 3')); + openModePicker(); fireEvent.click(screen.getByTestId('game-mode-matpower')); fireEvent.change(screen.getByTestId('game-matpower-count'), { target: { value: '1' } }); fireEvent.click(screen.getByTestId('game-start')); @@ -223,6 +250,7 @@ describe('GameConfigScreen — France EHV (Matpower) mode', () => { it('still requires a player name to start', () => { render(); + openModePicker(); fireEvent.click(screen.getByTestId('game-mode-matpower')); expect(screen.getByTestId('game-start')).toBeDisabled(); }); diff --git a/frontend/src/game/GameConfigScreen.tsx b/frontend/src/game/GameConfigScreen.tsx index eb8ce25..cf43221 100644 --- a/frontend/src/game/GameConfigScreen.tsx +++ b/frontend/src/game/GameConfigScreen.tsx @@ -157,7 +157,12 @@ export default function GameConfigScreen({ onStart }: GameConfigScreenProps) { const [previewError, setPreviewError] = useState(false); // Top-level mode: the European demo grid (curated reference studies) vs one - // of the difficulty-graded scenario databases (sampled by level). + // of the difficulty-graded scenario databases (sampled by level). The demo + // grid is the only mode a participant sees by default — France THT and + // France EHV (Matpower) stay hidden behind this toggle inside Configure + // settings so the landing page doesn't ask newcomers to choose a dataset + // family they don't yet have context for. + const [showModePicker, setShowModePicker] = useState(false); const [mode, setMode] = useState('demo'); const [thtDifficulty, setThtDifficulty] = useState('easy'); const [numCases, setNumCases] = useState(5); @@ -301,39 +306,6 @@ export default function GameConfigScreen({ onStart }: GameConfigScreenProps) { under settings.

- {/* Mode: European demo grid vs France THT graded scenarios. */} -
- -
- - {Object.values(GRADED).map((fam) => ( - - ))} -
-
- {/* Landing — the only things a participant needs to start. */}
@@ -526,18 +498,69 @@ export default function GameConfigScreen({ onStart }: GameConfigScreenProps) { setMaxActions(Math.min(3, Math.max(1, Number(e.target.value))))} />
- {mode === 'demo' && ( -
- - -
- )} +
+ + +
+ + {/* Hidden by default — France THT and France EHV (Matpower) + are advanced/graded datasets a newcomer doesn't need to + see on first load. Revealed verbatim (same buttons + + blurbs) by the toggle above. */} + {showModePicker && ( +
+ +
+ + {Object.values(GRADED).map((fam) => ( + + ))} +
+
+ )} + + {showModePicker && mode === 'demo' && ( +
+ + +
+ )} +

{graded ? graded.configHint : tier.blurb}