# Mission Control β€” Bouwplan Swift v2.5 **Projectnaam:** Swift β€” Contextual UI EPD **Versie:** v2.5 **Datum:** 27-12-2024 **Auteur:** Colin Lit / Development Team **Status:** πŸ”„ **86% Complete** - MVP binnen handbereik! --- ## 🎯 Project Status Summary | Metric | Waarde | Progress | |--------|--------|----------| | **Story Points** | 62 / 72 SP | 86% β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘ | | **Epics Compleet** | 4.75 / 5 | E0 βœ… E1 βœ… E2 βœ… E3 βœ… E4 βœ… E5 πŸ”„ | | **Stories Compleet** | 27 / 29 | 93% | | **Remaining Work** | 2 SP (1 story) | E5.S4 only! | | **Estimated Time** | 1-2 uur | Voor MVP compleet | **πŸŽ‰ Laatste sprint! Nog 1 story en de MVP is klaar voor demo!** --- ## Changelog v2.5 > **Belangrijke wijzigingen t.o.v. v2.4:** > - E5.S3 compleet: Keyboard shortcuts geverifieerd en uitgebreid > - ⌘Enter / Ctrl+Enter voor quick submit in CommandInput > - ⌘Enter / Ctrl+Enter voor quick save in DagnotatieBlock > - Visual hints toegevoegd (βŒ˜β†΅ op buttons) > - Keyboard shortcuts reference document > - Totalen bijgewerkt: 62 SP done (86%), 10 SP remaining ## Changelog v2.4 > **Belangrijke wijzigingen t.o.v. v2.3:** > - E5.S2 compleet: Error handling geΓ―mplementeerd met gecentraliseerde utilities > - OfflineBanner component voor offline detection > - safeFetch wrapper met 30s timeout en retry logic > - User-friendly Nederlandse error messages voor alle HTTP status codes > - Retry logic met exponential backoff voor transient errors > - Alle blocks en command input gebruiken nieuwe error handler > - Totalen bijgewerkt: 60 SP done (83%), 12 SP remaining ## Changelog v2.3 > **Belangrijke wijzigingen t.o.v. v2.2:** > - E5.S1 compleet: Block animaties geΓ―mplementeerd volgens UX specificatie > - Slide up/down animaties met fade en scale (200ms) > - CanvasArea animaties verbeterd: slide down bij sluiten (niet omhoog) > - BlockContainer animaties consistent gemaakt met CanvasArea > - Totalen bijgewerkt: 58 SP done (81%), 14 SP remaining ## Changelog v2.2 > **Belangrijke wijzigingen t.o.v. v2.1:** > - Epic 3 compleet: Alle P1 blocks geΓ―mplementeerd (E3.S0-S6) > - PatientContextCard toegevoegd: Auto-open na patient selectie > - OverdrachtBlock met AI samenvattingen per patiΓ«nt > - Patient search API geΓ―mplementeerd > - Technische debt opgelost: BlockContainer animaties, CanvasArea rendering > - Totalen bijgewerkt: 56 SP done (78%), 16 SP remaining ## Changelog v2.0 > **Belangrijke wijzigingen t.o.v. v1.5:** > - E3.S0 toegevoegd: CanvasArea block rendering (kritieke blokkade) > - Technische debt sectie toegevoegd (type duplicatie, keyboard shortcuts) > - Diagnostiek Workflow als uitbreiding opgenomen > - Sprint planning aangepast aan huidige voortgang > - Totalen bijgewerkt: 29 stories, 72 SP --- ## 1. Doel en context ### 1.1 Projectdoel Swift is een **Contextual UI** interface voor het Mini-EPD systeem. In plaats van navigatie door menu's spreekt of typt de gebruiker een intentie β€” en het juiste UI-blok verschijnt voorgevuld met relevante data. **Kernbelofte:** > Van 12 klikken en 3 minuten naar 1 zin en 15 seconden. ### 1.2 Business Case | Metric | Huidig | Met Swift | Besparing | |--------|--------|-----------|-----------| | Dagnotitie maken | 3-5 min | 15 sec | 95% | | PatiΓ«nt zoeken | 1-2 min | 5 sec | 95% | | Overdracht maken | 20-30 min | 5 min | 80% | | Rapportage schrijven | 8-15 min | 2-3 min | 75% | **Per verpleegkundige per dag: ~4 uur terug naar zorg** ### 1.3 Relatie met Documentatie | Document | Beschrijft | Locatie | |----------|------------|---------| | PRD | Product visie, requirements | `swift-prd.md` | | FO | Functionele flows, blocks | `swift-fo-ai.md` | | TO | Technische architectuur | `to-swift-v1.md` | | UX | Visuele specificaties | `swift-ux-v2.1.md` | | **Diagnostiek FO** | Behandelaar workflow | `swift-fo-diagnostiek-workflow.md` | | **Diagnostiek Bouwplan** | Uitbreiding voor behandelaars | `bouwplan-swift-diagnostiek-workflow.md` | --- ## 2. Uitgangspunten ### 2.1 Technische Stack **Bestaand (hergebruiken):** | Component | Technologie | Versie | |-----------|-------------|--------| | Framework | Next.js | 14.2.18 | | UI Library | React | 18.3.1 | | Styling | TailwindCSS | 3.4.18 | | Components | shadcn/ui | - | | Command Palette | cmdk | 1.1.1 | | Animations | framer-motion | 12.23.24 | | Database | Supabase | 2.81.1 | | Speech-to-Text | Deepgram | 4.11.2 | | AI | Claude API | - | | Validation | Zod | 4.1.12 | | Forms | react-hook-form | 7.66.1 | **Nieuw toegevoegd:** | Component | Technologie | Versie | Status | |-----------|-------------|--------|--------| | State Management | Zustand | 5.0.9 | βœ… GeΓ―nstalleerd | ### 2.2 Projectkaders | Kader | Waarde | |-------|--------| | **Bouwtijd** | 4 weken (4 sprints) | | **Team** | 1 developer | | **Scope** | MVP: P1 blocks (dagnotitie, zoeken, overdracht) | | **Uitbreiding** | Diagnostiek workflow (optioneel, +22 SP) | | **Data** | Bestaande Supabase database | | **Doel** | Werkende demo + user testing | ### 2.3 Programmeer Uitgangspunten **Code Quality Principles:** - **DRY (Don't Repeat Yourself)** - Herbruikbare block componenten - Centrale intent classificatie logica - Shared hooks voor common patterns - **Let op:** Types importeren uit `lib/swift/types.ts`, niet dupliceren - **KISS (Keep It Simple, Stupid)** - Local-first intent classificatie (regex) - AI alleen als fallback - Minimale state complexity - **SOC (Separation of Concerns)** - UI blocks gescheiden van intent logic - API routes gescheiden van business logic - Store slices per domein - **YAGNI (You Aren't Gonna Need It)** - Alleen P1 blocks in MVP - Geen toggle tussen interfaces - Geen advanced analytics in v1 **Development Practices:** ```typescript // βœ… Goede structuur voor Swift components components/ β”œβ”€β”€ swift/ β”‚ β”œβ”€β”€ command-center/ β”‚ β”‚ β”œβ”€β”€ command-center.tsx // Main container β”‚ β”‚ β”œβ”€β”€ command-input.tsx // Input component β”‚ β”‚ β”œβ”€β”€ canvas-area.tsx // Block rendering ← KRITIEK β”‚ β”‚ └── index.ts // Barrel export β”‚ └── blocks/ β”‚ β”œβ”€β”€ block-container.tsx // Wrapper met animaties β”‚ β”œβ”€β”€ dagnotitie-block.tsx β”‚ β”œβ”€β”€ zoeken-block.tsx β”‚ β”œβ”€β”€ overdracht-block.tsx β”‚ └── index.ts // βœ… Store importeert types (geen duplicatie) stores/ └── swift-store.ts // Importeert uit lib/swift/types.ts // βœ… Intent classification lib/ └── swift/ β”œβ”€β”€ types.ts // SINGLE SOURCE OF TRUTH voor types β”œβ”€β”€ intent-classifier.ts // Local classification β”œβ”€β”€ intent-classifier-ai.ts // AI fallback └── entity-extractor.ts // Entity parsing ``` --- ## 3. Epics & Stories Overzicht | Epic ID | Titel | Doel | Status | Stories | Effort | |---------|-------|------|--------|---------|--------| | E0 | Setup & Foundation | Zustand, routing, base layout | βœ… Done | 4 | 8 SP | | E1 | Command Center | Input, voice, context bar | βœ… Done | 5 | 13 SP | | E2 | Intent Classification | Local + AI fallback + wiring | βœ… Done | 5 | 12 SP | | E3 | P1 Blocks | Dagnotitie, Zoeken, Overdracht | βœ… Done | 7 | 23 SP | | E4 | Navigation & Auth | Login keuze, routing, preferences | βœ… Done | 4 | 8 SP | | E5 | Polish & Testing | Animaties, error handling, tests | πŸ”„ In Progress | 4 | 8 SP | **Totaal: 29 stories, 72 story points** | Categorie | SP | |-----------|----:| | βœ… Done (E0 + E1 + E2 + E3 + E4 + E5.S1 + E5.S2 + E5.S3) | 62 | | ⏳ Remaining | 10 | ### Epic Completion Details | Epic | Stories | SP | Status | Completion | |------|---------|----:|--------|------------| | **E0: Setup & Foundation** | 4/4 | 8/8 | βœ… **DONE** | 100% β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ | | **E1: Command Center** | 5/5 | 13/13 | βœ… **DONE** | 100% β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ | | **E2: Intent Classification** | 5/5 | 12/12 | βœ… **DONE** | 100% β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ | | **E3: P1 Blocks** | 7/7 | 23/23 | βœ… **DONE** | 100% β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ | | **E4: Navigation & Auth** | 4/4 | 8/8 | βœ… **DONE** | 100% β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆ | | **E5: Polish & Testing** | 3/4 | 6/8 | πŸ”„ **IN PROGRESS** | 75% β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘β–‘β–‘β–‘ | | **TOTAAL** | **27/29** | **62/72** | 🎯 **86%** | β–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–ˆβ–‘β–‘ | **Laatste Sprint:** - βœ… E5.S1 - Block animaties (2 SP) - βœ… E5.S2 - Error handling (2 SP) - βœ… E5.S3 - Keyboard shortcuts (2 SP) - ⏳ E5.S4 - Smoke tests (2 SP) ← **ALLEEN DIT NOG!** **Belangrijk:** - Bouw per epic en per story, niet alles tegelijk - Dependencies installeren: eerst aan Colin melden - Database migraties: eerst aan Colin melden --- ## 4. Epics & Stories (Uitwerking) ### Epic 0 β€” Setup & Foundation βœ… DONE **Epic Doel:** Werkende development omgeving met Zustand store en Swift routing. | Story ID | Beschrijving | Acceptatiecriteria | Status | Afh. | SP | |----------|--------------|---------------------|--------|------|----| | E0.S1 | Zustand installeren | `pnpm add zustand` succesvol, import werkt | βœ… | β€” | 1 | | E0.S2 | Swift store opzetten | `stores/swift-store.ts` met basis state | βœ… | E0.S1 | 2 | | E0.S3 | Swift route aanmaken | `/epd/swift` route met eigen layout | βœ… | E0.S2 | 2 | | E0.S4 | Swift folder structuur | `components/swift/`, `lib/swift/` aangemaakt | βœ… | E0.S3 | 3 | --- ### Epic 1 β€” Command Center βœ… DONE **Epic Doel:** Werkende command center met tekst en voice input. | Story ID | Beschrijving | Acceptatiecriteria | Status | Afh. | SP | |----------|--------------|---------------------|--------|------|----| | E1.S1 | Command Center layout | 4-zone layout (context, canvas, recent, input), keyboard shortcuts (⌘K, Escape) | βœ… | E0.S4 | 3 | | E1.S2 | Context Bar | Dienst indicator, patiΓ«nt chip, user info | βœ… | E1.S1 | 2 | | E1.S3 | Command Input | Tekst input met placeholder, focus state, send button | βœ… | E1.S1 | 2 | | E1.S4 | Voice Input integratie | Deepgram streaming, waveform visualisatie | βœ… | E1.S3 | 3 | | E1.S5 | Recent Strip | Laatste 5 acties als chips, click-to-repeat | βœ… | E1.S1 | 3 | **Technical Notes:** ``` Command Center Layout: β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ Context Bar (48px) β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ β”‚ β”‚ Canvas Area (flex) ← Blocks hier β”‚ β”‚ β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ Recent Strip (48px) β”‚ β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ β”‚ Command Input (64px, fixed bottom) β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ ``` --- ### Epic 2 β€” Intent Classification βœ… DONE **Epic Doel:** Two-tier intent classificatie (local + AI fallback) + wiring naar blocks. | Story ID | Beschrijving | Acceptatiecriteria | Status | Afh. | SP | |----------|--------------|---------------------|--------|------|----| | E2.S1 | Local classifier | Regex patterns voor P1 intents, <50ms | βœ… | E0.S4 | 3 | | E2.S2 | Entity extraction | Patient naam, categorie uit input | βœ… | E2.S1 | 3 | | E2.S3 | AI fallback | Claude Haiku bij confidence <0.8 | βœ… | E2.S2 | 2 | | E2.S4 | Intent API route | POST /api/intent/classify met logging | βœ… | E2.S3 | 2 | | E2.S5 | Input β†’ Block wiring | CommandInput.handleSubmit β†’ API β†’ openBlock | βœ… | E2.S4 | 2 | **E2.S5 Technical Notes (βœ… GEÏMPLEMENTEERD):** ```typescript // components/swift/command-center/command-input.tsx // IMPLEMENTATIE: const handleSubmit = async (e: React.FormEvent) => { e.preventDefault(); if (!hasValue || isProcessing) return; const inputText = inputValue.trim(); setIsProcessing(true); try { const response = await fetch('/api/intent/classify', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ input: inputText }), }); if (!response.ok) { const errorData = await response.json().catch(() => ({ error: 'Onbekende fout' })); throw new Error(errorData.error || `HTTP ${response.status}`); } const { intent, confidence, entities } = await response.json(); if (intent !== 'unknown' && confidence >= 0.5) { openBlock(intent as BlockType, entities); addRecentAction({ intent, label: inputText.slice(0, 50), patientName: entities.patientName }); clearInput(); } else { // Tijdelijke fallback naar dagnotitie (wordt vervangen door FallbackPicker in E4.S4) openBlock('dagnotitie', { content: inputText }); addRecentAction({ intent: 'dagnotitie', label: inputText.slice(0, 50) }); clearInput(); } } catch (error) { // Error handling met fallback naar dagnotitie openBlock('dagnotitie', { content: inputText }); addRecentAction({ intent: 'dagnotitie', label: inputText.slice(0, 50) }); clearInput(); } finally { setIsProcessing(false); } }; ``` --- ### Epic 3 β€” P1 Blocks βœ… DONE **Epic Doel:** Werkende DagnotatieBlock, ZoekenBlock en OverdrachtBlock. | Story ID | Beschrijving | Acceptatiecriteria | Status | Afh. | SP | |----------|--------------|---------------------|--------|------|----| | E3.S0 | CanvasArea block rendering | Switch/case voor block types, prefill doorgeven | βœ… | E2.S5 | 2 | | E3.S1 | Block Container | Animatie wrapper, close button, sizes | βœ… | E1.S1 | 2 | | E3.S2 | DagnotatieBlock | Patient, categorie, tekst, opslaan naar /api/reports | βœ… | E3.S0, E3.S1 | 5 | | E3.S3 | Patient search API | GET /api/patients/search?q= fuzzy search | βœ… | E0.S4 | 3 | | E3.S4 | ZoekenBlock | Input, resultaten, selectie β†’ store | βœ… | E3.S0, E3.S3 | 3 | | E3.S5 | PatientContextCard | Na selectie: notities, vitals, diagnose | βœ… | E3.S4 | 5 | | E3.S6 | OverdrachtBlock | AI samenvatting per patiΓ«nt (bestaande API) | βœ… | E3.S0 | 3 | **E3.S0 Technical Notes (βœ… GEÏMPLEMENTEERD):** ```typescript // components/swift/command-center/canvas-area.tsx // IMPLEMENTATIE: import { DagnotatieBlock } from '../blocks/dagnotitie-block'; import { ZoekenBlock } from '../blocks/zoeken-block'; import { OverdrachtBlock } from '../blocks/overdracht-block'; import { PatientContextCard } from '../blocks/patient-context-card'; function renderBlock(activeBlock: BlockType, prefillData: BlockPrefillData) { switch (activeBlock) { case 'dagnotitie': return ; case 'zoeken': return ; case 'overdracht': return ; default: return null; } } // In CanvasArea: {activeBlock ? ( {renderBlock(activeBlock, prefillData)} ) : activePatient ? ( ) : ( )} ``` **E3.S2 Technical Notes (βœ… GEÏMPLEMENTEERD):** ```typescript // components/swift/blocks/dagnotitie-block.tsx // IMPLEMENTATIE: // 1. Patient search: Debounced search met /api/fhir/Patient?q=... // 2. Category selector: 5 categorie buttons (medicatie, adl, gedrag, incident, observatie) // 3. Tekst input: Textarea met character counter (max 500) // 4. Opslaan: POST /api/reports met type 'verpleegkundig' // 5. Success toast + auto-close block na 500ms ``` --- ### Epic 4 β€” Navigation & Auth βœ… DONE **Epic Doel:** Login pagina met interface keuze, routing naar Swift/Klassiek. | Story ID | Beschrijving | Acceptatiecriteria | Status | Afh. | SP | |----------|--------------|---------------------|--------|------|----| | E4.S1 | Login form uitbreiden | Interface selector (Swift/Klassiek) | βœ… | E0.S3 | 2 | | E4.S2 | Preference opslag | user_metadata.preferred_interface | βœ… | E4.S1 | 2 | | E4.S3 | Redirect middleware | /epd β†’ preference route | βœ… | E4.S2 | 2 | | E4.S4 | Fallback Picker | Visuele keuze bij lage confidence | βœ… | E3.S0 | 2 | --- ### Epic 5 β€” Polish & Testing πŸ”„ IN PROGRESS **Epic Doel:** Gepolijste UX met animaties, error handling en tests. | Story ID | Beschrijving | Acceptatiecriteria | Status | Afh. | SP | |----------|--------------|---------------------|--------|------|----| | E5.S1 | Block animaties | Slide up/down met framer-motion | βœ… | E3.S0 | 2 | | E5.S2 | Error handling | Network errors, validation, toasts | βœ… | E3.S6 | 2 | | E5.S3 | Keyboard shortcuts | Verificatie + ⌘Enter shortcuts | βœ… | E1.S1 | 2 | | E5.S4 | Smoke tests | Happy flow tests voor alle P1 blocks | ⏳ | E5.S2 | 2 | **E5.S2 Technical Notes (βœ… GEÏMPLEMENTEERD):** ```typescript // lib/swift/error-handler.ts // IMPLEMENTATIE: Gecentraliseerde error handling utility // 1. Offline detection export function isOffline(): boolean { return typeof navigator !== 'undefined' && !navigator.onLine; } // 2. Network error detection export function isNetworkError(error: unknown): boolean { if (error instanceof TypeError) { return error.message.includes('fetch') || error.message.includes('network') || error.message.includes('Failed to fetch'); } return false; } // 3. User-friendly error messages (Dutch) export function getErrorInfo(error: unknown, context?: ErrorContext): ErrorInfo { if (isOffline()) { return { title: 'Geen internetverbinding', description: 'Controleer je internetverbinding en probeer het opnieuw.', retryable: true, }; } // HTTP status code mapping if (context?.statusCode) { switch (context.statusCode) { case 401: return { title: 'Niet geautoriseerd', ... }; case 404: return { title: 'Niet gevonden', ... }; case 500: return { title: 'Serverfout', ... }; // etc. } } // ... } // 4. Safe fetch wrapper met timeout (30s) export async function safeFetch( url: string, options?: RequestInit, context?: ErrorContext ): Promise { if (isOffline()) throw new Error('Geen internetverbinding'); const response = await fetch(url, { ...options, signal: AbortSignal.timeout(30000), // 30s timeout }); if (!response.ok) { const errorData = await parseErrorResponse(response); throw new Error(errorData.error); } return response; } // 5. Retry logic met exponential backoff export async function retryFetch( fn: () => Promise, maxRetries: number = 3, delayMs: number = 1000 ): Promise { for (let attempt = 0; attempt < maxRetries; attempt++) { try { return await fn(); } catch (error) { const errorInfo = getErrorInfo(error); if (!errorInfo.retryable || attempt === maxRetries - 1) { throw error; } await new Promise(r => setTimeout(r, delayMs * (attempt + 1))); } } } // components/swift/command-center/offline-banner.tsx // IMPLEMENTATIE: Offline detection banner export function OfflineBanner() { const [isOffline, setIsOffline] = useState(false); useEffect(() => { setIsOffline(!navigator.onLine); window.addEventListener('online', () => setIsOffline(false)); window.addEventListener('offline', () => setIsOffline(true)); // cleanup }, []); if (!isOffline) return null; return (
Geen internetverbinding
); } // Usage in blocks: // - safeFetch() voor alle API calls (vervangt raw fetch) // - getErrorInfo() voor user-friendly toast messages // - retryFetch() voor dagnotitie/overdracht save operaties (max 3 retries) ``` **E5.S3 Technical Notes (βœ… GEÏMPLEMENTEERD):** ```typescript // components/swift/command-center/command-input.tsx // IMPLEMENTATIE: ⌘Enter quick submit // Keyboard shortcut: Cmd/Ctrl+Enter to submit useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { // Cmd/Ctrl+Enter: submit command if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') { e.preventDefault(); handleSubmit(); } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [handleSubmit]); // components/swift/blocks/dagnotitie-block.tsx // IMPLEMENTATIE: ⌘Enter quick save // Keyboard shortcut: Cmd/Ctrl+Enter to save useEffect(() => { const handleKeyDown = (e: KeyboardEvent) => { // Cmd/Ctrl+Enter: save dagnotitie if ((e.metaKey || e.ctrlKey) && e.key === 'Enter') { e.preventDefault(); handleSave(); } }; window.addEventListener('keydown', handleKeyDown); return () => window.removeEventListener('keydown', handleKeyDown); }, [handleSave]); // Visual hint op submit button ``` **Bestaande shortcuts (E1.S1, E4.S4) geverifieerd:** - **⌘K / Ctrl+K** - Focus command input (CommandCenter) - **Escape** - Close active block (CommandCenter + FallbackPicker) - **1-3** - Quick select in FallbackPicker - **Enter** - Native form submit in CommandInput **Nieuwe shortcuts (E5.S3):** - **⌘Enter / Ctrl+Enter** - Quick submit in CommandInput - **⌘Enter / Ctrl+Enter** - Quick save in DagnotatieBlock **Documentatie:** - `docs/swift/keyboard-shortcuts-reference.md` - Complete shortcut reference - `docs/swift/test-plan-e5-s3-keyboard-shortcuts.md` - Test plan **E5.S1 Technical Notes (βœ… GEÏMPLEMENTEERD):** ```typescript // components/swift/command-center/canvas-area.tsx // IMPLEMENTATIE volgens UX specificatie (sectie 11.1): // Openen: Slide up + fade in (200ms), Scale: 0.95 β†’ 1.0 // Sluiten: Slide down + fade out (200ms), Scale: 1.0 β†’ 0.95 const blockAnimations = { initial: { opacity: 0, y: 20, scale: 0.95 }, animate: { opacity: 1, y: 0, scale: 1, transition: { duration: 0.2, ease: [0.4, 0, 0.2, 1] }, }, exit: { opacity: 0, y: 20, // Slide down (niet omhoog) scale: 0.95, transition: { duration: 0.2, ease: [0.4, 0, 0.2, 1] }, }, }; // AnimatePresence met mode="wait" voor soepele transitions {activeBlock && ( {renderBlock(activeBlock, prefillData)} )} ``` **Nota:** Keyboard shortcuts (⌘K focus, Escape close) zijn al geΓ―mplementeerd in E1.S1. E5.S3 is nu verificatie + eventuele uitbreiding (Enter submit, etc.). --- ## 5. Technische Debt & Bekende Issues ### 5.1 Type Duplicatie (Medium Priority) **Probleem:** ```typescript // lib/swift/types.ts - BRON export type SwiftIntent = 'dagnotitie' | 'zoeken' | 'overdracht' | 'unknown'; // stores/swift-store.ts - DUPLICAAT (moet verwijderd worden) export type SwiftIntent = 'dagnotitie' | 'zoeken' | 'overdracht' | 'unknown'; ``` **Impact:** Bij uitbreiding intents (diagnostiek workflow) moet op twee plekken gewijzigd worden. **Oplossing:** Verwijder duplicaat uit `swift-store.ts`, importeer uit `lib/swift/types.ts`: ```typescript // stores/swift-store.ts import type { SwiftIntent, BlockType, ShiftType } from '@/lib/swift/types'; ``` **Wanneer:** Voorafgaand aan E-D1.S1 (diagnostiek intent patterns). ### 5.2 BlockContainer Animaties (RESOLVED) **Probleem:** BlockContainer bestaat maar heeft nog geen framer-motion animaties. **Oplossing:** GeΓ―mplementeerd in E3.S1. BlockContainer heeft nu volledige animatie support met framer-motion. ### 5.3 CanvasArea Placeholder (RESOLVED) **Probleem:** CanvasArea toont placeholder tekst i.p.v. blocks. **Oplossing:** GeΓ―mplementeerd in E3.S0. CanvasArea heeft nu volledige block rendering met switch/case en animaties. --- ## 6. Kwaliteit & Testplan ### 6.1 Test Types | Test Type | Scope | Tools | Wanneer | |-----------|-------|-------|---------| | Unit Tests | Intent classifier, entity extraction | Vitest | E2 | | Integration Tests | API endpoints | Vitest + MSW | E2, E3 | | Component Tests | Blocks, Command Center | React Testing Library | E3, E5 | | E2E Tests | Complete flows | Playwright (optioneel) | E5 | | Manual Tests | Demo scenarios | Checklist | E5 | ### 6.2 Test Coverage Targets | Area | Target | Reden | |------|--------|-------| | Intent classifier | 90%+ | Kritiek voor UX | | API routes | 80%+ | Data integrity | | UI components | 60%+ | Belangrijkste flows | ### 6.3 Manual Test Checklist (MVP Demo) **Status:** πŸ”„ 12/14 scenarios geΓ―mplementeerd (86%) **Happy Flows:** - [x] User kan inloggen en Swift kiezen (E4) βœ… - [x] Command input krijgt focus met Cmd+K (E1.S1) βœ… - [x] ⌘Enter quick submit β†’ block opent (E5.S3) βœ… NEW! - [x] "notitie jan medicatie" β†’ DagnotatieBlock opent met prefill (E3.S2) βœ… - [x] Dagnotitie opslaan β†’ toast + block sluit (E3.S2) βœ… - [x] ⌘Enter in dagnotitie β†’ quick save (E5.S3) βœ… NEW! - [x] "zoek marie" β†’ ZoekenBlock met resultaten (E3.S4) βœ… - [x] PatiΓ«nt selecteren β†’ PatientContextCard (E3.S5) βœ… - [x] "overdracht" β†’ OverdrachtBlock met AI samenvatting (E3.S6) βœ… - [x] Voice input β†’ transcript in command input (E1.S4) βœ… **Error Scenarios:** - [x] Onbekende intent β†’ FallbackPicker (E4.S4) βœ… - [x] Offline mode β†’ banner + error toast (E5.S2) βœ… NEW! - [x] Network error β†’ toast met retry (E5.S2) βœ… - [x] Lege notitie β†’ validation error (E3.S2) βœ… - [x] Geen zoekresultaten β†’ "Geen patiΓ«nten gevonden" (E3.S4) βœ… **Keyboard Shortcuts:** - [x] Escape sluit block (E1.S1) βœ… - [x] 1-3 in FallbackPicker β†’ quick select (E4.S4) βœ… **Te Testen (E5.S4):** - [ ] End-to-end smoke test alle flows - [ ] Performance check (< 100ms block open) - [ ] Cross-browser test (Chrome, Safari, Firefox) --- ## 7. Demo & Presentatieplan ### 7.1 Demo Scenario **Duur:** 10 minuten **Doelgroep:** Zorgprofessionals, management **Locatie:** Live op Vercel **Flow:** ``` 1. INTRO (1 min) "40% van je tijd gaat naar administratie. Wat als je gewoon kon zeggen wat je wilt?" 2. CONTRAST (2 min) Klassiek EPD: Dashboard β†’ PatiΓ«nten β†’ Jan β†’ Rapportages β†’ Nieuwe β†’ Type β†’ Tekst β†’ Opslaan Swift: "notitie jan medicatie gegeven" β†’ Opslaan 3. DAGNOTITIE FLOW (2 min) - Typ: "notitie jan medicatie uitgereikt" - Block verschijnt voorgevuld - Één klik: opgeslagen 4. VOICE DEMO (2 min) - Klik microfoon - Spreek: "marie had een rustige nacht, goed geslapen" - Block verschijnt met transcript 5. OVERDRACHT (2 min) - "overdracht" - AI genereert samenvatting per patiΓ«nt - Toon bronverwijzingen 6. AFSLUITING (1 min) - Tijdsbesparing recap - Vragen ``` ### 7.2 Backup Plan | Probleem | Oplossing | |----------|-----------| | Internet issues | Localhost met demo data | | Voice niet werkt | Type-only demo | | AI API down | Pre-cached responses | | Complete failure | Video recording | --- ## 8. Risico's & Mitigatie **Status Update (27-12-2024):** Meeste risico's zijn gemitigeerd! βœ… | Risico | Kans | Impact | Mitigatie | Status | |--------|------|--------|-----------|--------| | Voice accuracy NL | Middel | Hoog | Deepgram NL model, fallback naar tekst | βœ… **Gemitigeerd** - Voice werkt met transcript fallback | | Intent misclassificatie | Laag ↓ | Middel ↓ | Two-tier systeem, FallbackPicker | βœ… **Gemitigeerd** - FallbackPicker geΓ―mplementeerd (E4.S4) | | AI latency | Laag | Middel | Local-first, Haiku model | βœ… **Gemitigeerd** - Local classifier <50ms | | User adoption | Middel | Middel | Keuze behouden, geen dwang | βœ… **Gemitigeerd** - Interface selector (E4.S1-S3) | | Scope creep | Laag ↓ | Laag ↓ | Strict P1-only, backlog voor rest | βœ… **Onder controle** - P1 scope behouden | | Performance | Laag | Laag | Code splitting, lazy loading, animaties | βœ… **Gemitigeerd** - <200ms transitions (E5.S1) | | Network errors | Laag | Laag | Offline detection, retry logic | βœ… **Gemitigeerd** - Error handler (E5.S2) | | ~~CanvasArea blocking~~ | ~~Hoog~~ | ~~Hoog~~ | ~~E3.S0 prioriteit~~ | βœ… **OPGELOST** - E3.S0 compleet | **Nieuwe Risico's:** | Risico | Kans | Impact | Mitigatie | Status | |--------|------|--------|-----------|--------| | Demo preparatie | Laag | Middel | E5.S4 smoke tests, rehearsal | ⏳ **In behandeling** | | Cross-browser issues | Laag | Laag | Test in Chrome/Safari/Firefox | ⏳ **E5.S4** | | Production deployment | Laag | Middel | Vercel deployment check | ⏳ **Post-MVP** | **Conclusie:** Project risico's zijn minimaal. MVP is stabiel en klaar voor testing. βœ… --- ## 9. Sprint Planning (Aangepast) ### Huidige Status (27-12-2024) - βœ… E0: Setup & Foundation (8 SP) β€” DONE - βœ… E1: Command Center (13 SP) β€” DONE - βœ… E2: Intent Classification (12 SP) β€” DONE - βœ… E3: P1 Blocks (23 SP) β€” DONE - βœ… E4: Navigation & Auth (8 SP) β€” DONE - πŸ”„ E5: Polish & Testing (8 SP) β€” IN PROGRESS (6/8 SP done) - βœ… E5.S1: Block animaties (2 SP) β€” DONE - βœ… E5.S2: Error handling (2 SP) β€” DONE - βœ… E5.S3: Keyboard shortcuts (2 SP) β€” DONE - ⏳ E5.S4: Smoke tests (2 SP) β€” TO DO **Totaal Done: 62 SP / 72 SP (86%)** ### Sprint 3 (Voltooid): Core Wiring + Blocks - βœ… E2.S5: Input β†’ Block wiring (2 SP) β€” DONE - βœ… E3.S0: CanvasArea block rendering (2 SP) β€” DONE - βœ… E3.S1: Block Container (2 SP) β€” DONE - βœ… E3.S2: DagnotatieBlock (5 SP) β€” DONE - **Deliverable:** "notitie jan" β†’ DagnotatieBlock werkt end-to-end βœ… ### Sprint 4 (Voltooid): Remaining Blocks - βœ… E3.S3: Patient search API (3 SP) β€” DONE - βœ… E3.S4: ZoekenBlock (3 SP) β€” DONE - βœ… E3.S5: PatientContextCard (5 SP) β€” DONE - βœ… E3.S6: OverdrachtBlock (3 SP) β€” DONE - **Deliverable:** Alle P1 blocks werken βœ… ### Sprint 5: Polish & Ship (In Progress) - βœ… E4: Navigation & Auth (8 SP) β€” DONE - πŸ”„ E5: Polish & Testing (8 SP) β€” IN PROGRESS (6/8 SP done) - βœ… E5.S1: Block animaties (2 SP) β€” DONE - βœ… E5.S2: Error handling (2 SP) β€” DONE - βœ… E5.S3: Keyboard shortcuts (2 SP) β€” DONE - ⏳ E5.S4: Smoke tests (2 SP) β€” TO DO - Technische debt opruimen - **Deliverable:** Demo-ready MVP **Laatste 2 SP voor MVP compleet! 🎯** --- ## 10. Uitbreidingen (Backlog) ### Diagnostiek Workflow (22 SP) **Status:** Gepland na MVP **Documentatie:** `bouwplan-swift-diagnostiek-workflow.md` | Epic | Stories | SP | Vereisten | |------|---------|----:|-----------| | E-D1 | Afspraak & Rapportage | 12 | E3 compleet | | E-D2 | Diagnose Beheer | 10 | E3 compleet | **Pre-requisites:** 1. Swift MVP compleet (E0-E5) 2. Report type "diagnostiek" toevoegen aan REPORT_TYPES 3. SwiftIntent uitbreiden met nieuwe types 4. Entity extraction voor datum/tijd **Zie:** `bouwplan-swift-diagnostiek-workflow-beoordeling.md` voor details. --- ## 11. Definition of Done Een story is **Done** wanneer: - [ ] Code geschreven en werkend - [ ] TypeScript types correct (geen duplicaten) - [ ] Component responsive (mobile + desktop) - [ ] Error states afgehandeld - [ ] Toegankelijkheid basics (focus, labels) - [ ] Getest in Chrome + Safari - [ ] PR reviewed (indien team) - [ ] Gemerged naar main Een epic is **Done** wanneer: - [ ] Alle stories Done - [ ] Integration test passed - [ ] Demo scenario werkt --- ## 12. Referenties ### Project Documenten - PRD: `docs/swift/swift-prd.md` - FO: `docs/swift/swift-fo-ai.md` - TO: `docs/swift/to-swift-v1.md` - UX: `docs/swift/swift-ux-v2.1.md` - Diagnostiek FO: `docs/swift/swift-fo-diagnostiek-workflow.md` - Diagnostiek Bouwplan: `docs/swift/bouwplan-swift-diagnostiek-workflow.md` - Beoordeling: `docs/swift/bouwplan-swift-diagnostiek-workflow-beoordeling.md` ### Bestaande Code Referenties - Swift Store: `stores/swift-store.ts` - Swift Types: `lib/swift/types.ts` (SINGLE SOURCE) - Intent Classifier: `lib/swift/intent-classifier.ts` - Intent API: `app/api/intent/classify/route.ts` - Command Center: `components/swift/command-center/` - Block Container: `components/swift/blocks/block-container.tsx` ### Bestaande EPD Code (Hergebruik) - Command component: `components/ui/command.tsx` - Speech streaming: `components/speech-recorder-streaming.tsx` - Overdracht API: `app/api/overdracht/generate/route.ts` - Report types: `lib/types/report.ts` - Reports API: `app/api/reports/route.ts` ### External - Zustand: https://zustand-demo.pmnd.rs/ - cmdk: https://cmdk.paco.me/ - Deepgram: https://developers.deepgram.com/docs - Claude API: https://docs.anthropic.com/ - Framer Motion: https://www.framer.com/motion/ --- ## 13. Glossary | Term | Betekenis | |------|-----------| | Swift | Projectnaam voor Contextual UI EPD | | Command Center | Hoofdscherm met één input | | Block | Ephemeral UI component (dagnotitie, zoeken, etc.) | | Intent | Gebruikersintentie (dagnotitie, zoeken, overdracht) | | Entity | GeΓ«xtraheerde data (patiΓ«ntnaam, categorie) | | Prefill | Vooraf ingevulde data in block | | Klassiek EPD | Traditionele menu-gebaseerde interface | | P1 | Prioriteit 1 (MVP scope) | | SP | Story Points (Fibonacci: 1, 2, 3, 5, 8) | | Wiring | Koppeling tussen componenten (input β†’ API β†’ block) | --- **Versiehistorie:** | Versie | Datum | Auteur | Wijziging | |--------|-------|--------|-----------| | v1.0 | 23-12-2024 | Colin Lit | InitiΓ«le versie | | v1.1 | 23-12-2024 | Claude | E0 + E1 voltooid (21 SP) | | v1.2 | 23-12-2024 | Claude | E2.S1 + E2.S2 voltooid (27 SP) | | v1.3 | 23-12-2024 | Claude | E2.S3 AI fallback voltooid (29 SP) | | v1.4 | 23-12-2024 | Claude | E2.S1-S4 voltooid (31 SP) | | v1.5 | 24-12-2024 | Claude | E2.S5 toegevoegd: Input β†’ Block wiring (+2 SP) | | **v2.0** | **24-12-2024** | **Claude** | **Major update: E3.S0 toegevoegd, technische debt sectie, diagnostiek workflow referentie, sprint planning aangepast (29 stories, 72 SP)** | | **v2.1** | **24-12-2024** | **Claude** | **E2.S5 voltooid: Input β†’ Block wiring geΓ―mplementeerd, Epic 2 compleet (33 SP done, 46%)** | | **v2.2** | **24-12-2024** | **Claude** | **Epic 3 compleet: Alle P1 blocks geΓ―mplementeerd (E3.S0-S6), PatientContextCard toegevoegd, OverdrachtBlock met AI samenvattingen (56 SP done, 78%)** | | **v2.3** | **24-12-2024** | **Claude** | **E5.S1 compleet: Block animaties geΓ―mplementeerd volgens UX specificatie (slide up/down met fade en scale, 200ms), Epic 4 compleet (58 SP done, 81%)** | | **v2.4** | **27-12-2024** | **Claude** | **E5.S2 compleet: Error handling met gecentraliseerde utilities, OfflineBanner, safeFetch, retry logic, Nederlandse error messages (60 SP done, 83%)** | | **v2.5** | **27-12-2024** | **Claude** | **E5.S3 compleet: Keyboard shortcuts geverifieerd en uitgebreid, ⌘Enter quick submit/save, visual hints, shortcuts reference (62 SP done, 86%)** |