From 6bf6eb256da7a68a263fd3eaceaadd947df25122 Mon Sep 17 00:00:00 2001 From: Reza Ilmi Date: Mon, 25 May 2026 08:44:17 +0800 Subject: [PATCH] fix(student-space): expose mature island preview --- src/components/DevPalette.tsx | 33 ++++++++------------- src/components/student-space/EngineHost.tsx | 8 +---- test/components/DevPalette.test.tsx | 33 +++++++++++++++++---- 3 files changed, 42 insertions(+), 32 deletions(-) diff --git a/src/components/DevPalette.tsx b/src/components/DevPalette.tsx index 2186e62..96030ab 100644 --- a/src/components/DevPalette.tsx +++ b/src/components/DevPalette.tsx @@ -80,25 +80,18 @@ export function DevPalette() { }, } : null - // Dev-only "mature island" preview — flips the sparse-by-default - // Flowers/Tree/Butterflies views into showAll so we can see what a - // saturated island looks like without scripting captures. Toggle off - // restores the sparse state (only what the student has actually - // earned through captures remains visible). - const matureIsland: Command | null = import.meta.env.DEV - ? { - id: 'mature-island', - label: matureIslandOn ? 'Hide mature island' : 'Show mature island', - hint: 'dev preview', - run: () => { - setOpen(false) - window.dispatchEvent( - new CustomEvent('ss:mature-island-toggle', { detail: { on: !matureIslandOn } }), - ) - setMatureIslandOn((v) => !v) - }, - } - : null + const matureIsland: Command = { + id: 'mature-island', + label: matureIslandOn ? 'Hide mature island' : 'Show mature island', + hint: 'island preview', + run: () => { + setOpen(false) + window.dispatchEvent( + new CustomEvent('ss:mature-island-toggle', { detail: { on: !matureIslandOn } }), + ) + setMatureIslandOn((v) => !v) + }, + } return [ { id: 'ui', label: 'Switch to UI mode', hint: '/', run: go('/') }, { @@ -118,7 +111,7 @@ export function DevPalette() { }, ...(cameraTuner ? [cameraTuner] : []), ...(hatchTuner ? [hatchTuner] : []), - ...(matureIsland ? [matureIsland] : []), + matureIsland, { id: 'restart-onboarding', label: 'Restart onboarding', diff --git a/src/components/student-space/EngineHost.tsx b/src/components/student-space/EngineHost.tsx index 90da055..445fab0 100644 --- a/src/components/student-space/EngineHost.tsx +++ b/src/components/student-space/EngineHost.tsx @@ -279,7 +279,7 @@ export function EngineHost({ {showOnboardingFlow ? : null} {import.meta.env.DEV && game ? : null} - {import.meta.env.DEV && game ? : null} + {game ? : null} {children} @@ -292,12 +292,6 @@ export function EngineHost({ * static framing without a remount. The HUD itself is hidden until the * palette dispatches CAMERA_TUNER_OPEN_EVENT. */ -/** - * DEV-only bridge: listens for the dev palette's `ss:mature-island-toggle` - * event and flips the sparse-by-default Flowers / Tree / Butterflies views - * into showAll (or back to hidden — only what the student has earned by - * capturing stays visible). - */ function MatureIslandBridge({ game }: { game: Game }) { useEffect(() => { const view = ( diff --git a/test/components/DevPalette.test.tsx b/test/components/DevPalette.test.tsx index 80d2f0b..f985dcc 100644 --- a/test/components/DevPalette.test.tsx +++ b/test/components/DevPalette.test.tsx @@ -63,7 +63,9 @@ beforeEach(() => { afterEach(() => { document.body.classList.remove('is-dev-overlay-hidden') + document.body.classList.remove('is-world-controls-visible') navigate.mockClear() + vi.unstubAllEnvs() Object.defineProperty(window.location, 'assign', { configurable: true, writable: true, @@ -117,14 +119,35 @@ describe('DevPalette', () => { render() await user.keyboard('{Meta>}k{/Meta}') + await user.click(screen.getByRole('option', { name: /show world controls/i })) + expect(document.body).toHaveClass('is-world-controls-visible') + expect(localStorage.getItem('sm:world-controls-visible')).toBe('1') + + await user.keyboard('{Meta>}k{/Meta}') await user.click(screen.getByRole('option', { name: /hide world controls/i })) - expect(document.body).toHaveClass('is-dev-overlay-hidden') - expect(localStorage.getItem('sm:dev-overlay-hidden')).toBe('1') + expect(document.body).not.toHaveClass('is-world-controls-visible') + expect(localStorage.getItem('sm:world-controls-visible')).toBeNull() + }) + + it('keeps mature island preview available in production builds', async () => { + vi.stubEnv('DEV', false) + const user = userEvent.setup() + const matureIslandListener = vi.fn() + window.addEventListener('ss:mature-island-toggle', matureIslandListener) + render() await user.keyboard('{Meta>}k{/Meta}') - await user.click(screen.getByRole('option', { name: /show world controls/i })) - expect(document.body).not.toHaveClass('is-dev-overlay-hidden') - expect(localStorage.getItem('sm:dev-overlay-hidden')).toBeNull() + expect(screen.getByRole('option', { name: /show mature island/i })).toBeInTheDocument() + expect(screen.queryByRole('option', { name: /show camera tuner/i })).not.toBeInTheDocument() + expect(screen.queryByRole('option', { name: /show hatch tuner/i })).not.toBeInTheDocument() + + await user.click(screen.getByRole('option', { name: /show mature island/i })) + expect(matureIslandListener).toHaveBeenCalledTimes(1) + expect(matureIslandListener.mock.calls[0]?.[0]).toMatchObject({ + detail: { on: true }, + }) + + window.removeEventListener('ss:mature-island-toggle', matureIslandListener) }) it('restarts onboarding from Cmd-K without clearing the full student-space state', async () => {