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. */}
-
-
Mode
-
-
setMode('demo')}>
- 🌍 European grid — demo
-
- The pan-European reference studies (and the French worst-case set).
-
-
- {Object.values(GRADED).map((fam) => (
-
setMode(fam.key)}>
- {fam.button}
-
- {fam.buttonBlurb}
-
-
- ))}
-
-
-
{/* 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' && (
-
- Difficulty (network)
- changeDifficulty(e.target.value as Difficulty)}>
- {DIFFICULTY_TIERS.map((t) => (
- {t.label}
- ))}
-
-
- )}
+
+ Mode
+ setShowModePicker((v) => !v)}>
+ {showModePicker ? 'Hide other modes ▲' : '🌐 Other modes / regions ▼'}
+
+
+
+ {/* 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 && (
+
+
Mode
+
+
setMode('demo')}>
+ 🌍 European grid — demo
+
+ The pan-European reference studies (and the French worst-case set).
+
+
+ {Object.values(GRADED).map((fam) => (
+
setMode(fam.key)}>
+ {fam.button}
+
+ {fam.buttonBlurb}
+
+
+ ))}
+
+
+ )}
+
+ {showModePicker && mode === 'demo' && (
+
+ Difficulty (network)
+ changeDifficulty(e.target.value as Difficulty)}>
+ {DIFFICULTY_TIERS.map((t) => (
+ {t.label}
+ ))}
+
+
+ )}
+
{graded ? graded.configHint : tier.blurb}