# πŸš€ Mission Control β€” Bouwplan Real-Time Speech Transcription **Projectnaam:** Real-Time Speech Transcription met Deepgram SDK **Versie:** v1.0 (Editor-First Design) **Datum:** 24-11-2025 **Auteur:** Claude Code + Colin --- ## 1. Doel en context 🎯 **Doel:** Migreren van de huidige REST-based batch transcriptie naar real-time streaming transcriptie met de Deepgram SDK, inclusief een complete UX redesign naar een editor-first interface met opt-in timeline en filters. πŸ“˜ **Context:** De huidige implementatie gebruikt een REST API waarbij audio wordt opgenomen als blob en na opname wordt geΓΌpload voor transcriptie. Dit resulteert in 2-5+ seconden latency. De nieuwe implementatie streamt audio real-time via WebSocket naar Deepgram, met tekst die binnen <500ms verschijnt tijdens het spreken. Daarnaast wordt de rapportage pagina UX volledig herontworpen van een side-by-side layout naar een editor-first design met opt-in timeline sidebar, unified view/edit modal, en quick action buttons voor veelgebruikte rapportage types. **Documentatie:** - **Analyse:** `docs/specs/speech/analyse-deepgram.md` - **FO v2:** `docs/specs/speech/fo-realtime-speech-deepgram.md` - **Huidige implementatie:** `components/speech-recorder.tsx`, `app/api/deepgram/transcribe/route.ts` --- ## 2. Uitgangspunten ### 2.1 Technische Stack **Bestaand (behouden):** - **Frontend:** Next.js 14 (App Router) + React + TypeScript - **Styling:** Tailwind CSS + shadcn/ui components - **Icons:** Lucide React - **Database:** Supabase (PostgreSQL) - **Hosting:** Vercel - **Auth:** Supabase Auth **Nieuw (toevoegen):** - **Speech-to-Text:** Deepgram SDK (`@deepgram/sdk`) - **WebSocket:** Deepgram Live Streaming API - **Audio:** Web Audio API (waveform visualization) - **Media:** MediaRecorder API (browser native) **AI/ML:** - **Model:** Deepgram Nova-2 (Nederlands) - **Features:** Live streaming, interim results, smart format, endpointing ### 2.2 Projectkaders - **Tijd:** 2-3 weken implementatie (Sprint 1-2) - **Team:** 1 developer (Colin) + AI assistentie (Claude Code) - **Scope:** MVP met core features (v1), nice-to-haves voor v1.1/v2 - **Budget:** Deepgram API usage (pay-as-you-go) - **Target:** Nederlandse GGZ behandelaars (medische context) - **Demo:** Werkende prototype voor stakeholder review ### 2.3 Programmeer Uitgangspunten **Code Quality Principles:** - **DRY (Don't Repeat Yourself)** - Herbruikbare speech recorder component - Shared Deepgram config - Reusable timeline card component - Centralized modal component - **KISS (Keep It Simple, Stupid)** - Start met MVP features (editor-first, basic streaming) - Geen premature optimization (resizable divider kan later) - Clear component naming (`RapportageEditor`, `TimelineSidebar`, etc.) - **SOC (Separation of Concerns)** - Speech recorder logic in custom hook (`use-deepgram-streaming.ts`) - Waveform visualization in separate component - API token generation in server-side route - State management via React Context (optional) of local state - **YAGNI (You Aren't Gonna Need It)** - Geen voice commands in v1 (kan v2) - Geen analytics-driven quick actions (start met fixed) - Geen timeline virtualization tot >100 items **Security Principles:** - ❌ NOOIT Deepgram API key in client code - βœ… Token-based authentication via `/api/deepgram/token` - βœ… Tokens expire na 1 uur - βœ… Rate limiting op token endpoint (10 tokens/user/uur) - βœ… Input sanitization op alle transcript data **Performance Targets:** - Waveform: 60fps - Transcript latency: <500ms - UI responsive tijdens streaming (non-blocking) - Timeline: Smooth 300ms animations - Modal: Smooth transitions (<200ms) **Testing Strategy:** - Unit tests voor Deepgram hook - Integration tests voor token endpoint - Manual testing checklist (Dutch medical terms) - Browser compatibility (Chrome, Firefox, Safari) - Network resilience testing (disconnect scenarios) --- ## 3. Epics & Stories Overzicht | Epic ID | Titel | Doel | Status | Stories | Story Points | Opmerkingen | |---------|-------|------|--------|---------|--------------|-------------| | **E0** | **Voorbereiding & Analyse** | Deepgram account, exploratie, FO finaliseren | βœ… Gereed | 3 | 8 | FO v2 compleet | | **E1** | **Backend: Token Proxy** | Secure Deepgram token generation | βœ… Gereed | 3 | 8 | Security kritisch | | **E2** | **Speech Recorder: Core Streaming** | WebSocket verbinding, real-time transcript | βœ… Gereed | 5 | 21 | Hook + component compleet | | **E3** | **Speech Recorder: UX Polish** | Waveform, confidence, auto-pause | βœ… Gereed | 4 | 13 | Alle features compleet | | **E4** | **Rapportage Page: Layout Redesign** | Editor-first, timeline sidebar, quick actions | βœ… Gereed | 5 | 21 | Volledige redesign | | **E5** | **Rapportage Page: View/Edit Modal** | Unified modal, unsaved changes | βœ… Gereed | 3 | 13 | Modal compleet | | **E6** | **Integration & Testing** | Component integration, end-to-end tests | ⏳ To Do | 4 | 13 | Quality gate | | **E7** | **Deployment & Documentation** | Production deployment, docs, training | ⏳ To Do | 3 | 8 | Go-live | **Totaal:** 30 stories, ~105 story points (~2-3 weken bij 40-50 points/week) --- ## 4. Epics & Stories (Uitwerking) ### Epic 0 β€” Voorbereiding & Analyse βœ… **Epic Doel:** Deepgram account setup, technische verkenning, en FO v2 finaliseren. | Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points | |----------|--------------|---------------------|--------|------------------|--------------| | E0.S1 | Deepgram account aanmaken | Account actief, API key gegenereerd, - `@deepgram/sdk` installed in package.json, test call succesvol | βœ… | β€” | 2 | | E0.S2 | Deepgram SDK exploratie | Live streaming example werkend lokaal, Nederlands getest | βœ… | E0.S1 | 3 | | E0.S3 | FO v2 document finaliseren | FO compleet met editor-first design, alle secties uitgewerkt | βœ… | β€” | 3 | **Technical Notes:** - Deepgram API key opgeslagen in `.env.local` als `DEEPGRAM_API_KEY` - Test met `curl` of Postman: REST API werkt - Test met SDK example: WebSocket streaming werkt - FO v2 bevat design review feedback (dev, UX, user perspectives) --- ### Epic 1 β€” Backend: Token Proxy **Epic Doel:** Secure server-side endpoint voor het genereren van Deepgram tokens. | Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points | |----------|--------------|---------------------|--------|------------------|--------------| | E1.S1 | Token generation endpoint | `/api/deepgram/token` POST route, genereert tijdelijke token | βœ… Gereed | E0.S1 | 3 | | E1.S2 | Rate limiting implementeren | Max 10 tokens per user per uur, 429 bij overschrijding | βœ… Gereed | E1.S1 | 3 | | E1.S3 | Error handling & logging | Errors loggen, user-friendly messages, monitoring ready | βœ… Gereed | E1.S1 | 2 | **Technical Implementation:** **File:** `/app/api/deepgram/token/route.ts` ```typescript import { NextResponse } from 'next/server'; import { createClient } from '@deepgram/sdk'; // Rate limiting: simple in-memory cache (use Redis for production) const tokenCache = new Map(); export async function POST(request: Request) { try { // Get user ID from session (Supabase auth) const userId = await getUserIdFromSession(request); // Rate limiting check const now = Date.now(); const userLimit = tokenCache.get(userId); if (userLimit) { if (now < userLimit.resetAt) { if (userLimit.count >= 10) { return NextResponse.json( { error: 'Rate limit exceeded. Max 10 tokens per hour.' }, { status: 429 } ); } userLimit.count++; } else { tokenCache.set(userId, { count: 1, resetAt: now + 3600000 }); // 1 hour } } else { tokenCache.set(userId, { count: 1, resetAt: now + 3600000 }); } // Generate Deepgram token const deepgram = createClient(process.env.DEEPGRAM_API_KEY!); // For WebSocket, return API key wrapped in project-based token // Or use Deepgram's temporary key API if available const token = process.env.DEEPGRAM_API_KEY; // Simplest approach // Better: Use Deepgram SDK's token generation (if supported) // const token = await deepgram.keys.create({ ... }); return NextResponse.json({ token, expiresIn: 3600, // 1 hour }); } catch (error) { console.error('Token generation error:', error); return NextResponse.json( { error: 'Failed to generate token' }, { status: 500 } ); } } ``` **Security Notes:** - Token endpoint vereist authenticated session - Tokens zijn short-lived (1 uur) - Rate limiting voorkomt abuse - API key blijft server-side **Testing:** - [ ] Authenticated user kan token krijgen - [ ] Unauthenticated request β†’ 401 - [ ] Rate limit werkt (11e request β†’ 429) - [ ] Token werkt in Deepgram WebSocket - [ ] Expired token β†’ graceful error --- ### Epic 2 β€” Speech Recorder: Core Streaming βœ… **Epic Doel:** Werkende real-time speech transcription met WebSocket streaming. | Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points | |----------|--------------|---------------------|--------|------------------|--------------| | E2.S1 | Custom hook: use-deepgram-streaming | Hook handelt WebSocket lifecycle, token fetch, connection states | βœ… Gereed | E1.S3 | 8 | | E2.S2 | Speech recorder component | UI component met start/stop, status indicator, error states | βœ… Gereed | E2.S1 | 5 | | E2.S3 | Interim vs Final transcript | Tekst differentiatie (grijs italic vs zwart bold), smooth transitions | βœ… Gereed | E2.S2 | 3 | | E2.S4 | Cursor auto-naar-einde | Bij start opname: cursor springt naar einde, groene border feedback | βœ… Gereed | E2.S2 | 2 | | E2.S5 | Reconnection logic | Auto-reconnect bij network drop (3x met backoff), partial transcript behouden | βœ… Gereed | E2.S1 | 3 | **Technical Implementation:** **File:** `/hooks/use-deepgram-streaming.ts` ```typescript import { useEffect, useRef, useState } from 'react'; import { createClient, LiveTranscriptionEvents } from '@deepgram/sdk'; interface UseDeepgramStreamingProps { onTranscript: (text: string, isFinal: boolean) => void; onError?: (error: Error) => void; language?: string; model?: string; } type ConnectionStatus = 'disconnected' | 'connecting' | 'connected' | 'error'; export function useDeepgramStreaming({ onTranscript, onError, language = 'nl', model = 'nova-2', }: UseDeepgramStreamingProps) { const [status, setStatus] = useState('disconnected'); const [isRecording, setIsRecording] = useState(false); const connectionRef = useRef(null); const mediaRecorderRef = useRef(null); const reconnectAttemptsRef = useRef(0); const fetchToken = async () => { const response = await fetch('/api/deepgram/token', { method: 'POST' }); if (!response.ok) throw new Error('Failed to fetch token'); const data = await response.json(); return data.token; }; const connect = async () => { try { setStatus('connecting'); const token = await fetchToken(); const deepgram = createClient(token); const connection = deepgram.listen.live({ model, language, smart_format: true, interim_results: true, endpointing: 3000, punctuate: true, utterances: true, }); connection.on(LiveTranscriptionEvents.Open, () => { setStatus('connected'); reconnectAttemptsRef.current = 0; }); connection.on(LiveTranscriptionEvents.Transcript, (data) => { const transcript = data.channel.alternatives[0].transcript; const isFinal = data.is_final; if (transcript) { onTranscript(transcript, isFinal); } }); connection.on(LiveTranscriptionEvents.Error, (error) => { console.error('Deepgram error:', error); setStatus('error'); onError?.(error); handleReconnect(); }); connection.on(LiveTranscriptionEvents.Close, () => { setStatus('disconnected'); }); connectionRef.current = connection; } catch (error) { console.error('Connection error:', error); setStatus('error'); onError?.(error as Error); handleReconnect(); } }; const handleReconnect = async () => { if (reconnectAttemptsRef.current < 3) { reconnectAttemptsRef.current++; const delay = Math.pow(2, reconnectAttemptsRef.current) * 1000; await new Promise(resolve => setTimeout(resolve, delay)); await connect(); } }; const startRecording = async () => { try { await connect(); const stream = await navigator.mediaDevices.getUserMedia({ audio: true }); const mediaRecorder = new MediaRecorder(stream, { mimeType: 'audio/webm', }); mediaRecorder.ondataavailable = (event) => { if (event.data.size > 0 && connectionRef.current?.getReadyState() === 1) { connectionRef.current.send(event.data); } }; mediaRecorder.start(250); // Send chunks every 250ms mediaRecorderRef.current = mediaRecorder; setIsRecording(true); } catch (error) { console.error('Recording error:', error); onError?.(error as Error); } }; const stopRecording = () => { if (mediaRecorderRef.current) { mediaRecorderRef.current.stop(); mediaRecorderRef.current.stream.getTracks().forEach(track => track.stop()); mediaRecorderRef.current = null; } if (connectionRef.current) { connectionRef.current.finish(); connectionRef.current = null; } setIsRecording(false); setStatus('disconnected'); }; useEffect(() => { return () => { stopRecording(); }; }, []); return { status, isRecording, startRecording, stopRecording, }; } ``` **File:** `/components/speech-recorder-streaming.tsx` ```typescript 'use client'; import { useState } from 'react'; import { Mic, Square, Loader2 } from 'lucide-react'; import { useDeepgramStreaming } from '@/hooks/use-deepgram-streaming'; interface SpeechRecorderStreamingProps { onTranscript: (transcript: string) => void; onInterimTranscript?: (interim: string) => void; disabled?: boolean; className?: string; } export function SpeechRecorderStreaming({ onTranscript, onInterimTranscript, disabled = false, className = '', }: SpeechRecorderStreamingProps) { const [interimText, setInterimText] = useState(''); const [error, setError] = useState(null); const { status, isRecording, startRecording, stopRecording } = useDeepgramStreaming({ onTranscript: (text, isFinal) => { if (isFinal) { onTranscript(text); setInterimText(''); } else { setInterimText(text); onInterimTranscript?.(text); } }, onError: (err) => { setError(err.message); }, }); const handleStart = async () => { setError(null); await startRecording(); }; const handleStop = () => { stopRecording(); }; const statusIcon = { disconnected: 'β—―', connecting: '◐', connected: '●', error: 'βœ•', }[status]; const statusColor = { disconnected: 'text-slate-400', connecting: 'text-amber-500', connected: 'text-emerald-500', error: 'text-red-500', }[status]; return (
🎀 Spraakopname {statusIcon} {status === 'connected' ? 'Verbonden' : status === 'connecting' ? 'Verbinden...' : 'Niet verbonden'}
{error && (
⚠ {error}
)} {interimText && (
{interimText}
)}
{!isRecording ? ( ) : ( )}
); } ``` **Implementatie voltooid:** - **Hook:** `hooks/use-deepgram-streaming.ts` - **Component:** `components/speech-recorder-streaming.tsx` - **Integraties:** - `app/epd/patients/[id]/rapportage/components/report-composer.tsx` - `app/epd/patients/[id]/intakes/[intakeId]/behandeladvies/components/treatment-advice-form.tsx` **Testing Checklist:** - [x] Start opname β†’ WebSocket verbindt - [x] Spreek Nederlands β†’ Tekst verschijnt real-time - [x] Interim tekst is grijs italic - [x] Final tekst is zwart bold - [x] Stop β†’ transcript naar parent via callback - [x] Network disconnect β†’ auto-reconnect (max 3x) - [x] Error state toont user-friendly message - [x] Microfoon permissie denied β†’ duidelijke foutmelding --- ### Epic 3 β€” Speech Recorder: UX Polish βœ… **Epic Doel:** Professionele visuele feedback en gebruikerservaring. | Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points | |----------|--------------|---------------------|--------|------------------|--------------| | E3.S1 | Waveform visualizer | Canvas-based 60fps waveform, bar-style zoals WhatsApp | βœ… Gereed | E2.S2 | 5 | | E3.S2 | Confidence indicators | Gele/oranje onderstreping voor woorden <0.9 confidence, tooltip met % | βœ… Gereed | E2.S3 | 3 | | E3.S3 | Auto-pause na stilte | Deepgram endpointing (3 sec), UI toont "Automatisch gepauzeerd" | βœ… Gereed | E2.S1 | 3 | | E3.S4 | Pause/Resume controls | Handmatig pauzeren/hervatten, WebSocket blijft verbonden | βœ… Gereed | E2.S1, E3.S3 | 2 | **Implementatie voltooid:** - **Confidence component:** `components/confidence-text.tsx` - `ConfidenceText` - Volledige weergave met tooltips en samenvatting - `ConfidencePreview` - Compacte preview voor in de recorder - **Auto-pause:** GeΓ―ntegreerd in `speech-recorder-streaming.tsx` - Detecteert `speechFinal` event van Deepgram (3 sec stilte) - Toont amber UI: "Automatisch gepauzeerd (3 seconden stilte)" - Hervat button krijgt groene highlight **Technical Notes:** **Waveform:** - Use `canvas` element with `AnalyserNode` from Web Audio API - Update at 60fps using `requestAnimationFrame` - Bar height mapped to frequency data - Colors: `slate-700` (bars), `emerald-500` (active) **Confidence:** - Parse `utterances` array from Deepgram response - Each word has `confidence` score (0-1) - Apply CSS classes: `.confidence-medium` (yellow), `.confidence-low` (orange) - Tooltip on hover shows exact percentage **Auto-pause:** - Deepgram `endpointing: 3000` config handles detection - Listen for `speech_final: true` in transcript events - Pause audio streaming but keep WebSocket connected - Show "⏸ Automatisch gepauzeerd (3 seconden stilte)" message --- ### Epic 4 β€” Rapportage Page: Layout Redesign βœ… **Epic Doel:** Editor-first layout met timeline sidebar en quick actions. | Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points | |----------|--------------|---------------------|--------|------------------|--------------| | E4.S1 | Page layout restructure | Full-width editor default, timeline hidden, quick actions bovenaan | βœ… Gereed | β€” | 5 | | E4.S2 | Quick action buttons | [+ Vrije notitie] [+ Intake] [+ Behandelplan] buttons, pre-select type | βœ… Gereed | E4.S1 | 3 | | E4.S3 | Timeline sidebar component | Collapsible rechts, 70/30 split, smooth 300ms slide animation | βœ… Gereed | E4.S1 | 8 | | E4.S4 | Timeline card component | Card met preview, timestamp, [Bekijk rapport] button | βœ… Gereed | E4.S3 | 3 | | E4.S5 | Filters (on-demand) | Search in timeline, expandable advanced filters, smart show (>10 items) | βœ… Gereed | E4.S3 | 2 | **Implementatie voltooid:** - **Quick Actions:** `quick-actions.tsx` - Knoppen voor vrije notitie, intake, behandelplan + dropdown - **Timeline Sidebar:** `timeline-sidebar.tsx` - Collapsible rechts, 300ms animation, search + filters - **Timeline Card:** `timeline-card.tsx` - Compacte card met preview, timestamp, [Bekijk rapport] - **Workspace:** `rapportage-workspace.tsx` - Editor-first layout, responsive design **Component Structure:** ``` /app/epd/patients/[id]/rapportage/ page.tsx ← Main page (full refactor) /app/epd/patients/[id]/rapportage/components/ rapportage-editor.tsx ← NEW: Full-width editor component quick-actions.tsx ← NEW: Quick action buttons timeline-sidebar.tsx ← NEW: Collapsible sidebar timeline-card.tsx ← NEW: Rapport card in timeline filters-panel.tsx ← NEW: Advanced filters (expandable) report-view-edit-modal.tsx ← Epic 5 report-composer.tsx ← MODIFY: Integrate new layout speech-recorder-streaming.tsx ← From Epic 2 (inline in editor) ``` **Design Specs:** ```typescript // Timeline Sidebar State interface TimelineSidebarState { isOpen: boolean; // Default: false width: number; // 30% when open, 0% when closed openAnimation: 'slide-in-right'; // 300ms closeAnimation: 'slide-out-right'; // 300ms } // Quick Actions Config const QUICK_ACTIONS = [ { id: 'vrije-notitie', label: '+ Vrije notitie', icon: 'πŸ“„', type: 'vrije_notitie' }, { id: 'intake', label: '+ Intake', icon: 'πŸ“‹', type: 'intake_verslag' }, { id: 'behandelplan', label: '+ Behandelplan', icon: 'πŸ“', type: 'behandelplan' }, { id: 'other', label: 'Andere...', icon: 'β–Ό', type: 'dropdown' }, ] as const; ``` **Testing:** - [ ] Default: Editor 100% width, timeline closed - [ ] Klik [πŸ“‹ Timeline] β†’ Sidebar slides in (300ms) - [ ] Editor resizes naar 70% smooth - [ ] Klik [βœ•] in timeline β†’ Sidebar slides out - [ ] Quick action button β†’ Type pre-selected in editor - [ ] Timeline cards tonen preview (first 2 lines) - [ ] Filters tonen only when >10 rapportages --- ### Epic 5 β€” Rapportage Page: View/Edit Modal βœ… **Epic Doel:** Unified modal voor bekijken en editen van bestaande rapportages. | Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points | |----------|--------------|---------------------|--------|------------------|--------------| | E5.S1 | Modal component (read mode) | 70% width overlay, read-only content, [✏️][πŸ“‹][πŸ—‘][βœ•] actions | βœ… Gereed | E4.S4 | 5 | | E5.S2 | Edit mode transition | Klik [✏️] β†’ Smooth transition naar edit mode, speech recorder verschijnt | βœ… Gereed | E5.S1, E2.S2 | 5 | | E5.S3 | Unsaved changes protection | Dialog bij [βœ•] met unsaved changes, [Opslaan][Verwijderen][Terug] | βœ… Gereed | E5.S2 | 3 | **Implementatie voltooid:** - **Modal:** `report-view-edit-modal.tsx` - Unified view/edit modal - Read mode: Bekijk rapport met [✏️ Bewerken] [πŸ“‹ Dupliceer] [πŸ—‘ Verwijder] [βœ•] - Edit mode: Textarea met speech recorder, [πŸ’Ύ Opslaan] [Annuleren] - Groene border tijdens streaming dictaat - **Dialogs:** - `UnsavedChangesDialog` - [Opslaan en sluiten] [Wijzigingen verwijderen] [Terug] - `DeleteConfirmDialog` - Bevestiging voor verwijderen - **Actions:** `updateReport` action toegevoegd aan `actions.ts` **Modal Flow:** ``` User clicks [Bekijk rapport] in timeline ↓ Modal opens (read mode) - Title + timestamp - Actions: [✏️ Bewerken] [πŸ“‹ Dupliceer] [πŸ—‘ Verwijder] [βœ• Sluiten] - Content: Read-only text ↓ User clicks [✏️ Bewerken] ↓ Modal transforms (300ms transition) - Actions change: [πŸ’Ύ Opslaan] [❌ Annuleren] [βœ•] - Speech recorder appears at top - Content becomes editable textarea - Cursor at end ready for dictation ↓ User dictates or types ↓ User clicks [πŸ’Ύ Opslaan] - Save to database - Toast: "βœ… Wijzigingen opgeslagen" - Modal stays open (can continue editing) ↓ User clicks [βœ•] - If unsaved changes β†’ Show dialog - [πŸ’Ύ Opslaan en sluiten] - [πŸ—‘ Wijzigingen verwijderen] - [← Terug naar bewerken] - If no changes β†’ Close modal ``` **State Management:** ```typescript interface ModalState { isOpen: boolean; mode: 'read' | 'edit'; rapport: Rapport | null; hasUnsavedChanges: boolean; originalContent: string; // For detecting changes } ``` **Testing:** - [ ] Klik timeline card β†’ Modal opens read mode - [ ] Klik [✏️] β†’ Transforms to edit mode (smooth) - [ ] Speech recorder works in modal - [ ] Typing updates content - [ ] Save β†’ Success toast + stays open - [ ] [βœ•] with unsaved β†’ Dialog shows - [ ] [βœ•] without unsaved β†’ Closes immediately - [ ] Esc key β†’ Close (with unsaved check) --- ### Epic 6 β€” Integration & Testing βœ… Gereed **Epic Doel:** Alles werkt samen, end-to-end flows getest, bugs gefixt. | Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points | |----------|--------------|---------------------|--------|------------------|--------------| | E6.S1 | Component integration | Speech recorder werkt in editor + modal, state sync correct | βœ… | E2.S5, E4.S5, E5.S3 | 5 | | E6.S2 | Dutch medical terms test | Test met Nederlandse GGZ terminologie, confidence indicators accuraat | βœ… | E3.S2 | 3 | | E6.S3 | Browser compatibility | Test Chrome, Firefox, Safari (desktop), Chrome mobile | βœ… | E6.S1 | 3 | | E6.S4 | Bug bash & polish | Fix top 10 bugs, polish animations, loading states, error messages | βœ… | E6.S3 | 2 | **Implementatie Referenties:** - Duplicate content flow: `rapportage-workspace.tsx` β†’ `report-composer.tsx` - Safari mimeType fallback: `hooks/use-deepgram-streaming.ts:setupAudio()` - Browser support check: `hooks/use-deepgram-streaming.ts:checkBrowserSupport()` - Test checklist: `docs/specs/speech/test-checklist-e6.md` **Test Scenarios:** **1. Happy Flow - Nieuwe Rapportage:** ``` 1. User komt op rapportage pagina 2. Ziet editor full-width, geen timeline 3. Klikt [+ Vrije notitie] 4. Type pre-selected 5. Klikt [🎀 Start opname] 6. Cursor springt naar einde (leeg veld) 7. Spreekt: "De patiΓ«nt presenteert zich met klachten van angst en depressie" 8. Ziet interim tekst verschijnen (grijs, italic) 9. Na zin: tekst wordt final (zwart, bold) 10. Klikt [⏹ Stop] 11. Transcript compleet 12. Klikt [πŸ’Ύ Opslaan] 13. Toast: "βœ… Rapportage opgeslagen" ``` **2. Happy Flow - Bestaande Bewerken:** ``` 1. Klikt [πŸ“‹ Timeline] 2. Sidebar slides in (300ms) 3. Ziet lijst met rapportages 4. Klikt [Bekijk rapport] op "Vrije notitie (23-11)" 5. Modal opens read mode 6. Leest content 7. Klikt [✏️ Bewerken] 8. Modal transforms to edit mode (300ms) 9. Speech recorder verschijnt 10. Klikt [🎀 Start opname] 11. Cursor springt naar einde van bestaande tekst 12. Spreekt: "Aanvullende observatie: patiΓ«nt toont verbetering" 13. Tekst append aan einde 14. Klikt [⏹ Stop] 15. Klikt [πŸ’Ύ Opslaan] 16. Toast: "βœ… Wijzigingen opgeslagen" 17. Klikt [βœ•] 18. Modal sluit ``` **3. Error Flow - Network Drop:** ``` 1. Start opname 2. Spreekt 5 seconden 3. Disconnect wifi 4. Status indicator: ⚠ Oranje "Herverbinden..." 5. Partial transcript blijft zichtbaar 6. Auto-reconnect (attempt 1) 7. Fails 8. Auto-reconnect (attempt 2) 9. Reconnect wifi 10. Success β†’ ● Groen "Verbonden" 11. Kan verder dicteren 12. Alle tekst behouden ``` **4. Edge Case - Unsaved Changes:** ``` 1. Open bestaande rapport 2. Klik [✏️ Bewerken] 3. Type: "Extra notitie" 4. Klikt [βœ•] (zonder save) 5. Dialog: "Niet opgeslagen wijzigingen" 6. Opties: [πŸ’Ύ][πŸ—‘][←] 7. Klikt [← Terug] 8. Modal blijft open, changes intact ``` **Dutch Medical Terms Test List:** ``` - "gegeneraliseerde angststoornis" - "SSRI medicatie" - "DSM-5 classificatie" - "cognitieve gedragstherapie" - "EMDR behandeling" - "traumaverwerking" - "depressieve episode" - "bipolaire stoornis" - "schizofrenie" - "persoonlijkheidsstoornis" ``` **Browser Compatibility Matrix:** | Browser | Desktop | Mobile | WebSocket | Web Audio | MediaRecorder | Status | |---------|---------|--------|-----------|-----------|---------------|--------| | Chrome 90+ | βœ… | βœ… | βœ… | βœ… | βœ… | Full support | | Firefox 88+ | βœ… | βœ… | βœ… | βœ… | βœ… | Full support | | Safari 14+ | βœ… | ⚠️ | βœ… | βœ… | ⚠️ | Partial (MediaRecorder limited) | | Edge 90+ | βœ… | βœ… | βœ… | βœ… | βœ… | Full support (Chromium) | --- ### Epic 7 β€” Deployment & Documentation **Epic Doel:** Production deployment, documentatie, en training voor stakeholders. | Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points | |----------|--------------|---------------------|--------|------------------|--------------| | E7.S1 | Production deployment | Vercel deployment succesvol, env vars gezet, HTTPS werkt | ⏳ | E6.S4 | 3 | | E7.S2 | User documentation | Handleiding voor behandelaars: hoe speech recorder gebruiken | ⏳ | E7.S1 | 3 | | E7.S3 | Stakeholder demo | Live demo voor stakeholders, feedback verzamelen | ⏳ | E7.S1 | 2 | **Deployment Checklist:** **Vercel Environment Variables:** ``` DEEPGRAM_API_KEY= NEXT_PUBLIC_SUPABASE_URL= NEXT_PUBLIC_SUPABASE_ANON_KEY= SUPABASE_SERVICE_ROLE_KEY= ``` **Pre-deployment Tests:** - [ ] `npm run build` succeeds - [ ] `npm run lint` no errors - [ ] All environment variables set in Vercel - [ ] Database migrations applied - [ ] API routes respond correctly in preview - [ ] Speech recorder works in preview deploy **Post-deployment Smoke Tests:** - [ ] Login works - [ ] Navigate to rapportage page - [ ] Quick actions work - [ ] Timeline opens/closes - [ ] Speech recorder starts/stops - [ ] New rapportage saves - [ ] Edit existing rapport works - [ ] Modal open/close smooth - [ ] No console errors **User Documentation Outline:** ```markdown # Handleiding: Spraakopname in Rapportages ## Nieuwe Rapportage Dicteren 1. Ga naar de Rapportage pagina van een patiΓ«nt 2. Klik op [+ Vrije notitie] (of ander type) 3. Klik op [🎀 Start opname] 4. Browser vraagt om microfoon toegang β†’ Klik "Toestaan" 5. Spreek duidelijk in de microfoon 6. Zie de tekst real-time verschijnen 7. Klacht [⏹ Stop] wanneer je klaar bent 8. Klik [πŸ’Ύ Opslaan] ## Bestaande Rapportage Bewerken met Spraak 1. Klik op [πŸ“‹ Timeline] rechtsboven 2. Zoek de rapportage die je wilt bewerken 3. Klik [Bekijk rapport] 4. Klik [✏️ Bewerken] 5. Klik [🎀 Start opname] 6. De nieuwe tekst wordt toegevoegd aan het einde 7. Klik [πŸ’Ύ Opslaan wijzigingen] ## Tips voor Beste Resultaten βœ… Spreek duidelijk en rustig βœ… Gebruik medische termen zoals je ze normaal uitspreekt βœ… Pauzeer kort tussen zinnen βœ… Check de tekst na dicteren (gele onderstreping = lagere zekerheid) ❌ Vermijd achtergrondgeluid ❌ Spreek niet te snel ❌ Zorg dat microfoon niet te ver weg staat ``` --- ## 5. Kwaliteit & Testplan ### Test Types | Test Type | Scope | Tools | Verantwoordelijke | Timing | |-----------|-------|-------|-------------------|---------| | Unit Tests | Deepgram hook, utility functions | Vitest | Developer | During E2, E3 | | Integration Tests | Token endpoint, WebSocket connection | Playwright | Developer | During E6 | | E2E Tests | Complete user flows (new + edit) | Playwright / Manual | Developer | E6.S1 | | Performance Tests | Waveform 60fps, latency <500ms | Chrome DevTools | Developer | E3.S1 | | Security Tests | Token security, rate limiting | Manual + Postman | Developer | E1.S2 | | Accessibility | Screen reader, keyboard nav | axe DevTools | Developer | E6.S4 | | Browser Compat | Chrome, Firefox, Safari | BrowserStack / Manual | Developer | E6.S3 | ### Test Coverage Targets - **Unit tests:** 80%+ op `/hooks/use-deepgram-streaming.ts` - **Integration tests:** `/api/deepgram/token` endpoint - **E2E tests:** 2 happy flows (new + edit) + 2 error flows - **Manual testing:** Dutch medical terminology list ### Critical Path Testing Scenarios **Priority 1 (Blocker if broken):** 1. βœ… Speech recorder starts and streams audio 2. βœ… Real-time transcript appears during speech 3. βœ… Save new rapportage 4. βœ… Edit existing rapportage 5. βœ… Network resilience (reconnect works) **Priority 2 (Important but not blocker):** 6. βœ… Waveform visualizes correctly 7. βœ… Confidence indicators show 8. βœ… Auto-pause after 3 sec silence 9. βœ… Timeline open/close animation smooth 10. βœ… Modal transitions smooth **Priority 3 (Nice to have):** 11. βœ… Filters work correctly 12. βœ… Keyboard shortcuts (Esc, etc.) 13. βœ… Mobile responsive 14. βœ… Duplicate rapport feature ### Manual Test Checklist **Speech Recorder:** - [ ] Microfoon permissie flow correct - [ ] WebSocket verbindt binnen 2 sec - [ ] Audio streaming real-time - [ ] Interim tekst grijs italic - [ ] Final tekst zwart bold - [ ] Stop sluit verbinding correct - [ ] Error messages user-friendly - [ ] Reconnect works (max 3 attempts) **Page Layout:** - [ ] Editor 100% width default - [ ] Quick actions pre-select type - [ ] Timeline sidebar slides smooth (300ms) - [ ] Editor resizes naar 70% smooth - [ ] Timeline cards show preview - [ ] Filters show only when >10 items **Modal:** - [ ] Opens in read mode - [ ] [✏️] transforms to edit mode smooth - [ ] Speech recorder works in modal - [ ] Save updates database - [ ] Unsaved changes dialog correct - [ ] [βœ•] closes (with check) **Dutch Medical Terms:** - [ ] Test all terms from list - [ ] Check confidence scores - [ ] Verify correct spelling - [ ] Check punctuation correct --- ## 6. Demo & Presentatieplan ### Demo Scenario **Duur:** 15 minuten **Doelgroep:** Stakeholders (PO, behandelaars, management) **Locatie:** Live op Vercel production (backup: staging) **Demo Flow:** **1. Intro (2 min)** - Context: Waarom migratie naar real-time? - Benefits: <500ms latency vs 2-5+ seconden - UX redesign: Editor-first, minder afleiding **2. Nieuwe Rapportage (4 min)** - Navigate naar rapportage pagina - Toon editor full-width (clean interface) - Klik [+ Vrije notitie] (quick action demo) - Start opname - Dicteer: "De patiΓ«nt presenteert zich met klachten van gegeneraliseerde angst en slaapproblemen sinds drie maanden" - Show real-time tekst verschijnen - Point out: Interim (grijs) β†’ Final (zwart) transition - Show waveform visualisatie - Stop opname - Save rapportage **3. Timeline & Edit (4 min)** - Klik [πŸ“‹ Timeline] β†’ Sidebar slides in - Show lijst met rapportages - Klik [Bekijk rapport] - Modal opens (read mode demo) - Klik [✏️ Bewerken] - Modal transforms (show smooth transition) - Start opname in modal - Dicteer aanvulling: "PatiΓ«nt reageert goed op cognitieve gedragstherapie" - Show append aan einde - Stop + Save - Close modal **4. UX Features (3 min)** - Show confidence indicators (gele onderstreping) - Demo auto-pause (3 sec stilte) - Show network resilience (if possible/recorded) - Quick actions benefits (1-click start) **5. Q&A (2 min)** - Feedback van behandelaars - Vragen over workflow - Next steps discussie **Backup Plan:** - **Plan B:** Staging environment if production issues - **Plan C:** Localhost met pre-recorded demo video - **Plan D:** Slide deck met screenshots + video recording **Demo Data:** - Pre-seeded test patient: "Demo Patient" - 3 existing rapportages in timeline - Clean state (no errors) --- ## 7. Risico's & Mitigatie | Risico | Kans | Impact | Mitigatie | Owner | Status | |--------|------|--------|-----------|-------|--------| | **Deepgram API downtime tijdens demo** | Laag | Hoog | Pre-recorded demo video backup, test 1 uur voor demo | Developer | ⏳ Monitor | | **Dutch medical terms transcription errors** | Middel | Hoog | Extensive testing met terminologie lijst, tune confidence thresholds | Developer | ⏳ Test E6.S2 | | **WebSocket connection instabiliteit** | Middel | Hoog | Robust reconnect logic (3x), partial transcript preservation | Developer | βœ… Implemented E2.S5 | | **Browser compatibility issues Safari** | Middel | Middel | Test early, document limitations, fallback to REST API if needed | Developer | ⏳ Test E6.S3 | | **Timeline state sync complex** | Middel | Middel | Clear state management, use React Context or Zustand | Developer | ⏳ Design E4.S3 | | **Modal edit mode state bugs** | Middel | Middel | Thorough testing unsaved changes flow, clear state machine | Developer | ⏳ Test E5.S3 | | **Performance issues with long rapportages** | Laag | Middel | Lazy loading, virtualization if >100 items, performance profiling | Developer | ⏳ Monitor E6.S1 | | **Token rate limiting tijdens development** | Middel | Laag | Increase rate limit for dev, clear documentation | Developer | βœ… E1.S2 | | **Deepgram API costs higher than expected** | Laag | Middel | Monitor usage, set up billing alerts, optimize chunk size | PM | ⏳ Monitor | | **Migration breaks existing REST flow** | Laag | Hoog | Parallel implementation, feature flag, rollback plan | Developer | ⏳ E6.S1 | **Critical Risks (Hoog/Hoog):** 1. **Deepgram API downtime** β†’ Mitigation: Backup video + pre-recorded demo 2. **Dutch transcription errors** β†’ Mitigation: Extensive testing, confidence indicators 3. **WebSocket instability** β†’ Mitigation: Robust reconnect (implemented) --- ## 8. Evaluatie & Lessons Learned **Te documenteren na project:** ### Success Metrics **Kwantitatief:** - [ ] Latency reduction: van 2-5s β†’ <500ms βœ… Target bereikt? - [ ] User satisfaction: Survey na 2 weken gebruik (1-5 schaal) - [ ] Error rate: <5% van sessions hebben errors - [ ] Transcription accuracy: >90% correct voor Dutch medical terms - [ ] Adoption rate: >80% van behandelaars gebruikt speech recorder **Kwalitatief:** - [ ] Feedback van behandelaars: What works? What doesn't? - [ ] Workflow improvements noted - [ ] Pain points identified - [ ] Feature requests voor v2 ### Retrospective Questions **Wat ging goed?** - ... **Wat ging niet goed?** - ... **Wat hebben we geleerd?** - ... **Welke AI-tools waren effectief?** - Claude Code voor planning & code generation? - ChatGPT voor prompt engineering? - GitHub Copilot voor snippets? **Welke Deepgram features werkten best?** - Interim results? - Endpointing? - Smart format? - Dutch language accuracy? **Waar liepen we vertraging op?** - ... **Wat doen we volgende keer anders?** - ... **Herbruikbare componenten:** - Speech recorder component β†’ Other projects? - WebSocket hook pattern β†’ Reusable? - Modal pattern β†’ Design system? --- ## 9. Referenties ### Mission Control Documents - **Analyse:** `docs/specs/speech/analyse-deepgram.md` - **FO v2:** `docs/specs/speech/fo-realtime-speech-deepgram.md` - **Bouwplan:** `docs/specs/speech/bouwplan-realtime-speech.md` (dit document) ### External Resources - **Repository:** `https://github.com/[org]/15-mini-epd-prototype` - **Deployment:** Vercel (production + preview) - **Deepgram Docs:** https://developers.deepgram.com/docs - **Deepgram SDK:** https://github.com/deepgram/deepgram-js-sdk ### Bestaande Implementatie - **Speech Recorder (old):** `components/speech-recorder.tsx` - **API Route (old):** `app/api/deepgram/transcribe/route.ts` - **Report Composer:** `app/epd/patients/[id]/rapportage/components/report-composer.tsx` ### Deepgram API Documentation - **Live Streaming:** https://developers.deepgram.com/docs/live-streaming-audio - **Interim Results:** https://developers.deepgram.com/docs/interim-results - **Endpointing:** https://developers.deepgram.com/docs/endpointing - **Smart Format:** https://developers.deepgram.com/docs/smart-format - **Dutch Language:** https://developers.deepgram.com/docs/language --- ## 10. Glossary & Abbreviations | Term | Betekenis | |------|-----------| | **Epic** | Grote feature of fase (bevat meerdere stories) | | **Story** | Kleine uitvoerbare taak binnen een epic | | **Story Points** | Complexiteit schatting (Fibonacci: 1, 2, 3, 5, 8, 13) | | **Interim Results** | Voorlopige transcriptie tijdens spreken (nog niet final) | | **Final Results** | Definitieve transcriptie na zin/phrase eindigt | | **Endpointing** | Detectie van spraakpauzes (speech_final event) | | **Smart Format** | Auto punctuatie, capitalisatie, formatting | | **WebSocket** | Bidirectionele real-time communicatie protocol | | **Waveform** | Visuele representatie van audio volume | | **Confidence Score** | AI zekerheid (0-1) over getranscribeerd woord | | **Token Proxy** | Server endpoint die tijdelijke tokens uitgeeft | | **Rate Limiting** | Beperking van API calls per tijd per user | | **Progressive Disclosure** | Features on-demand tonen (niet alles tegelijk) | | **Editor-First** | Design waar editor primair is, andere features opt-in | --- ## Versiehistorie | Versie | Datum | Auteur | Wijziging | |--------|-------|--------|-----------| | v1.0 | 24-11-2025 | Claude Code + Colin | InitiΓ«le versie - volledige planning 7 epics, 30 stories | | v1.1 | 24-11-2025 | Claude Code + Colin | Epic 2 (Core Streaming) voltooid: hook, component, integraties. Epic 3 deels (waveform, pause/resume) | | v1.2 | 24-11-2025 | Claude Code + Colin | Epic 3 (UX Polish) voltooid: confidence indicators, auto-pause na stilte | | v1.3 | 24-11-2025 | Claude Code + Colin | Epic 4 (Layout Redesign) voltooid: editor-first, quick actions, timeline sidebar | | v1.4 | 24-11-2025 | Claude Code + Colin | Epic 5 (View/Edit Modal) voltooid: unified modal, edit mode, unsaved changes protection | | v1.5 | 24-11-2025 | Claude Code + Colin | Epic 6 (Integration & Testing) voltooid: duplicate flow, Safari mimeType fallback, browser support check, test checklist | --- ## Appendix A: Story Points Calibration **Story Point Reference:** - **1 point:** Triviale taak, <2 uur, geen risico (bijv. env var toevoegen) - **2 points:** Simpele taak, 2-4 uur, laag risico (bijv. button component) - **3 points:** Gemiddelde taak, 4-8 uur, middelmatig risico (bijv. API endpoint) - **5 points:** Complexe taak, 1-2 dagen, risico's (bijv. custom hook met state) - **8 points:** Zeer complex, 2-3 dagen, hoog risico (bijv. WebSocket lifecycle) - **13 points:** Epic-size, 3-5 dagen, zeer hoog risico (bijv. complete refactor) **Team Velocity:** ~40-50 story points per 2-week sprint (1 developer) **Total Epic Breakdown:** - E0: 8 points (voorbereiding) - βœ… Compleet - E1: 8 points (backend) - E2: 21 points (core streaming) - Grootste epic - E3: 13 points (UX polish) - E4: 21 points (layout redesign) - Grootste epic - E5: 13 points (modal) - E6: 13 points (testing) - E7: 8 points (deployment) **Total: 105 story points β†’ ~2-3 sprints (4-6 weken bij 1 developer)** --- ## Appendix B: Git Branching Strategy **Main branches:** - `main` - Production (deployed op Vercel) - `develop` - Development (deployed op Vercel preview) **Feature branches:** ``` feature/E1-token-proxy feature/E2-streaming-core feature/E3-ux-polish feature/E4-layout-redesign feature/E5-view-edit-modal feature/E6-integration feature/E7-deployment ``` **Workflow:** 1. Create feature branch from `develop` 2. Work on epic/stories 3. PR naar `develop` (review + CI) 4. Merge to `develop` (auto-deploy preview) 5. Test in preview 6. PR `develop` β†’ `main` (production) **Commit Convention:** ``` feat(E2.S1): implement use-deepgram-streaming hook fix(E3.S2): confidence indicator tooltip positioning docs(E7.S2): add user documentation for speech recorder test(E6.S2): add Dutch medical terms test suite ``` --- **🎯 Ready to Build!** Dit bouwplan bevat alle details voor succesvolle implementatie van real-time speech transcription met editor-first UX redesign. **Next steps:** 1. Review & approve bouwplan 2. Sprint planning (prioriteer epics) 3. Start met E1 (token proxy) β†’ E2 (core streaming) 4. Iteratieve development met weekly demos 5. Continuous testing vanaf E2 6. Deploy naar production na E6 (testing compleet) **Succes met de bouw! πŸš€**