From 034d1c4811d77458dd147cc9fbbd9bb225464647 Mon Sep 17 00:00:00 2001 From: Palash Debnath Date: Fri, 29 May 2026 23:14:14 +0530 Subject: [PATCH] fix(onboarding): hide DictationDemo when sample assets are absent (#119 follow-up) (#153) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit DubbingDemo and DemoPresetGrid already degrade gracefully (hide) when their assets / is_demo profiles are missing, but DictationDemo always rendered its three hardcoded cards — which fail on click without the bundled sample WAVs (rendered by scripts/build_demos.sh; absent in a plain source checkout). Add a mount-time HEAD probe of the first sample; if it's not present, hide the whole demo (mirrors DubbingDemo's missing-manifest behavior). When assets are present, behavior is unchanged. Test: HEAD 404 → demo renders nothing. Co-authored-by: Claude Opus 4.8 (1M context) --- frontend/src/components/DictationDemo.jsx | 18 ++++++++++++++++++ frontend/src/test/DictationDemo.test.jsx | 10 ++++++++++ 2 files changed, 28 insertions(+) diff --git a/frontend/src/components/DictationDemo.jsx b/frontend/src/components/DictationDemo.jsx index 50b86b93..ed9a422d 100644 --- a/frontend/src/components/DictationDemo.jsx +++ b/frontend/src/components/DictationDemo.jsx @@ -61,8 +61,23 @@ export default function DictationDemo({ embedded = false }) { const [hotkeyState, setHotkeyState] = useState('unknown'); // unknown | registered | verified const [playingId, setPlayingId] = useState(null); const [transcripts, setTranscripts] = useState({}); // {scriptId: {state, text, error}} + // null = probing, true/false once the demo assets are confirmed present. + // The sample WAVs are rendered by scripts/build_demos.sh and may be absent + // (e.g. source checkout without a render step). When absent we hide the demo + // rather than show cards that fail on click (#119/#124 follow-up). + const [assetsAvailable, setAssetsAvailable] = useState(null); const audioRef = useRef(null); + // Probe whether the bundled dictation samples actually exist; hide the whole + // demo if not, mirroring DubbingDemo's missing-manifest behavior. + useEffect(() => { + let cancelled = false; + fetch(`${API}${SCRIPTS[0].wav}`, { method: 'HEAD' }) + .then((r) => { if (!cancelled) setAssetsAvailable(r.ok); }) + .catch(() => { if (!cancelled) setAssetsAvailable(false); }); + return () => { cancelled = true; }; + }, []); + // Read the registered hotkey on mount. useEffect(() => { if (!isTauri()) return; @@ -179,6 +194,9 @@ export default function DictationDemo({ embedded = false }) { } })(); + // No bundled samples on disk → don't render a demo that can't work. + if (assetsAvailable === false) return null; + return (
diff --git a/frontend/src/test/DictationDemo.test.jsx b/frontend/src/test/DictationDemo.test.jsx index 09e260f9..08d70f0c 100644 --- a/frontend/src/test/DictationDemo.test.jsx +++ b/frontend/src/test/DictationDemo.test.jsx @@ -34,6 +34,16 @@ describe('DictationDemo', () => { expect(screen.getByText(/No hotkey registered/i)).toBeInTheDocument(); }); + it('hides the demo when the sample assets are missing (HEAD 404)', async () => { + // The mount probe HEAD-checks the first sample; a 404 means no rendered + // assets on disk → the whole demo should disappear rather than show cards + // that fail on click. + global.fetch = vi.fn(() => Promise.resolve({ ok: false, status: 404 })); + const { container } = render(withI18n()); + await waitFor(() => expect(container).toBeEmptyDOMElement()); + expect(screen.queryByText(/Schedule a meeting with Pat/)).not.toBeInTheDocument(); + }); + it('POSTs the bundled WAV to /transcribe when Replay is clicked', async () => { const wavBlob = new Blob([new Uint8Array([0, 0, 0, 0])], { type: 'audio/wav' }); // Make the recognized text deliberately different from the on-card