fix(onboarding): hide DictationDemo when sample assets are absent (#119 follow-up) (#153)

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) <noreply@anthropic.com>
This commit is contained in:
Palash Debnath
2026-05-29 23:14:14 +05:30
committed by GitHub
co-authored by Claude Opus 4.8
parent 993e6cf6a5
commit 034d1c4811
2 changed files with 28 additions and 0 deletions
+18
View File
@@ -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 (
<section className={`dictation-demo ${embedded ? 'dictation-demo--embedded' : ''}`}>
<header className="dictation-demo__head">
+10
View File
@@ -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(<DictationDemo />));
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