# Functioneel & Technisch Ontwerp: Cortex Shared Components
**Projectnaam:** Cortex Intake Intent System - Shared Components
**Versie:** v1.0
**Datum:** 03-02-2026
**Auteur:** Colin Lit (met AI-assistentie)
---
## 1. Doel en Context
### 1.1 Doel van dit document
Dit gecombineerde FO/TO beschrijft de **shared components** die worden hergebruikt door alle Cortex Blocks. Het document combineert:
- **Functioneel (FO):** Wat de gebruiker ziet en ervaart
- **Technisch (TO):** Hoe de componenten worden gebouwd
### 1.2 Relatie met andere documenten
| Document | Relatie |
|----------|---------|
| `intake-process-intents.md` | Beschrijft de intents die deze components gebruiken |
| `gap-analyse-intake-cortex.md` | Identificeert welke components ontbreken |
| `block-template-pattern.md` | Beschrijft het patroon waarop deze components gebaseerd zijn |
### 1.3 Scope
**In scope:**
- 4 nieuwe shared components voor Cortex Blocks
- 2 nieuwe custom hooks voor data fetching en context
**Buiten scope:**
- Individuele intake blocks (apart document)
- Bestaande components (`patient-list-item.tsx`, `linked-evidence.tsx`)
---
## 2. Overzicht Componenten
### 2.1 Component Hiërarchie
```
components/cortex/
├── blocks/
│ └── [intake-block].tsx ← Gebruikt shared components
│
├── shared/
│ ├── patient-list-item.tsx ✅ Bestaat
│ ├── linked-evidence.tsx ✅ Bestaat
│ │
│ ├── block-states.tsx 🆕 NIEUW (dit document)
│ ├── block-section.tsx 🆕 NIEUW (dit document)
│ ├── block-item.tsx 🆕 NIEUW (dit document)
│ └── block-footer.tsx 🆕 NIEUW (dit document)
lib/cortex/hooks/
├── use-patient-search.ts ✅ Bestaat
├── use-patient-selection.ts ✅ Bestaat
│
├── use-block-data.ts 🆕 NIEUW (dit document)
└── use-intake-context.ts 🆕 NIEUW (dit document)
```
### 2.2 Component Overzicht
| Component | Type | Functie | Gebruikt door |
|-----------|------|---------|---------------|
| `BlockLoading` | State | Laad-indicator tijdens data fetch | Alle blocks |
| `BlockError` | State | Foutmelding met retry optie | Alle blocks |
| `BlockEmpty` | State | Lege staat met actie | Alle blocks |
| `BlockSection` | Layout | Sectie met icon en titel | Query blocks |
| `BlockItem` | Layout | Lijst-item met badge | Query blocks |
| `BlockFooter` | Layout | Footer met acties | Alle blocks |
---
## 3. User Stories
### 3.1 Algemene User Stories
| ID | Rol | Doel / Actie | Verwachte waarde | Prioriteit |
|----|-----|--------------|------------------|------------|
| US-SC-01 | Gebruiker | Zien dat data wordt geladen | Weet dat systeem bezig is | Hoog |
| US-SC-02 | Gebruiker | Foutmelding zien bij problemen | Kan retry doen of hulp zoeken | Hoog |
| US-SC-03 | Gebruiker | Lege staat zien met suggestie | Weet wat te doen als geen data | Middel |
| US-SC-04 | Gebruiker | Snel navigeren naar EPD | Kan details bekijken in dossier | Hoog |
| US-SC-05 | Gebruiker | Risico niveau direct zien | Beoordeelt urgentie in één oogopslag | Hoog |
### 3.2 Developer User Stories
| ID | Rol | Doel / Actie | Verwachte waarde | Prioriteit |
|----|-----|--------------|------------------|------------|
| US-DEV-01 | Developer | Consistente states hergebruiken | Sneller nieuwe blocks bouwen | Hoog |
| US-DEV-02 | Developer | Generieke data hook gebruiken | Minder boilerplate code | Hoog |
| US-DEV-03 | Developer | Intake context automatisch hebben | Geen prop drilling nodig | Middel |
---
## 4. Functionele Specificaties per Component
### 4.1 BlockLoading
**Doel:** Toon visuele feedback tijdens het laden van data.
**Functioneel gedrag:**
- Toont geanimeerde spinner (rotatie)
- Toont optionele tekst (bijv. "Risico's laden...")
- Centreert verticaal en horizontaal in block
- Verdwijnt zodra data geladen is
**Visueel:**
```
┌─────────────────────────────────────┐
│ │
│ │
│ [◌ spinner] │
│ Risico's laden... │
│ │
│ │
└─────────────────────────────────────┘
```
**Props:**
| Prop | Type | Default | Beschrijving |
|------|------|---------|--------------|
| `message` | `string` | `"Laden..."` | Tekst onder spinner |
---
### 4.2 BlockError
**Doel:** Toon foutmelding met optie om opnieuw te proberen.
**Functioneel gedrag:**
- Toont rood/oranje waarschuwingsicoon
- Toont foutmelding (gebruikersvriendelijk, Nederlands)
- Optionele "Opnieuw proberen" knop
- Verdwijnt bij retry of bij sluiten block
**Visueel:**
```
┌─────────────────────────────────────┐
│ │
│ [⚠️ icon] │
│ │
│ Kon risico's niet ophalen. │
│ Controleer je internetverbinding. │
│ │
│ [🔄 Opnieuw proberen] │
│ │
└─────────────────────────────────────┘
```
**Props:**
| Prop | Type | Default | Beschrijving |
|------|------|---------|--------------|
| `message` | `string` | - | Foutmelding tekst |
| `onRetry` | `() => void` | `undefined` | Callback voor retry (toont knop indien aanwezig) |
---
### 4.3 BlockEmpty
**Doel:** Toon informatieve lege staat met optionele actie.
**Functioneel gedrag:**
- Toont context-relevant icoon (configureerbaar)
- Toont melding waarom leeg
- Optionele actie-knop (bijv. "Toevoegen in dossier")
- Visueel subtiel (niet alarmerend)
**Visueel:**
```
┌─────────────────────────────────────┐
│ │
│ [📋 icon faded] │
│ │
│ Geen risico's geregistreerd │
│ │
│ [+ Toevoegen in dossier] │
│ │
└─────────────────────────────────────┘
```
**Props:**
| Prop | Type | Default | Beschrijving |
|------|------|---------|--------------|
| `icon` | `LucideIcon` | - | Icoon component |
| `message` | `string` | - | Hoofdboodschap |
| `action` | `{ label: string; onClick: () => void }` | `undefined` | Optionele actie knop |
---
### 4.4 BlockSection
**Doel:** Groepeer gerelateerde content met visuele header.
**Functioneel gedrag:**
- Toont icoon + titel + optionele count
- Bevat children content
- Consistente styling (witte achtergrond, border, padding)
- Optioneel inklapbaar (v2)
**Visueel:**
```
┌─────────────────────────────────────┐
│ [⚠️] Risicotaxatie (3) │ ← Header
├─────────────────────────────────────┤
│ │
│ [Content / children] │ ← Children
│ │
└─────────────────────────────────────┘
```
**Props:**
| Prop | Type | Default | Beschrijving |
|------|------|---------|--------------|
| `icon` | `LucideIcon` | - | Icoon component |
| `iconColor` | `string` | `"text-slate-600"` | Tailwind kleur class |
| `title` | `string` | - | Sectie titel |
| `count` | `number` | `undefined` | Optionele count badge |
| `children` | `ReactNode` | - | Sectie inhoud |
---
### 4.5 BlockItem
**Doel:** Toon een enkel item in een lijst met consistente styling.
**Functioneel gedrag:**
- Toont titel + optionele subtitle
- Toont status badge (kleur-gecodeerd)
- Klikbaar indien `onClick` meegegeven
- Hover state bij klikbaar
- Geen hover bij niet-klikbaar
**Badge varianten:**
| Variant | Kleur | Gebruik |
|---------|-------|---------|
| `default` | Grijs | Neutraal, geen status |
| `success` | Groen | Laag risico, compleet |
| `warning` | Oranje | Gemiddeld risico, aandacht |
| `danger` | Rood | Hoog risico, urgent |
**Visueel:**
```
┌─────────────────────────────────────────────────────┐
│ [Avatar?] Suïcidaliteit [Matig] │ ← Klikbaar
│ 12 jan 2026 • Dr. Jansen │
└─────────────────────────────────────────────────────┘
```
**Props:**
| Prop | Type | Default | Beschrijving |
|------|------|---------|--------------|
| `title` | `string` | - | Hoofdtekst |
| `subtitle` | `string` | `undefined` | Subtekst (datum, auteur, etc.) |
| `badge` | `{ label: string; variant: BadgeVariant }` | `undefined` | Status badge |
| `onClick` | `() => void` | `undefined` | Klik handler (maakt item klikbaar) |
---
### 4.6 BlockFooter
**Doel:** Consistente footer met acties onderaan een block.
**Functioneel gedrag:**
- Links: secundaire actie (ghost button)
- Rechts: primaire actie (solid button)
- Horizontale scheidingslijn boven footer
- Loading state op primaire actie
**Visueel:**
```
─────────────────────────────────────────
[🔗 Bekijk in dossier] [+ Toevoegen]
(secundair) (primair)
```
**Props:**
| Prop | Type | Default | Beschrijving |
|------|------|---------|--------------|
| `secondaryAction` | `{ label, icon?, onClick }` | `undefined` | Linker actie |
| `primaryAction` | `{ label, icon?, onClick, loading? }` | `undefined` | Rechter actie |
---
## 5. UI Overzicht
### 5.1 Component Compositie in een Block
```
┌─────────────────────────────────────────────────────────────┐
│ [Icon] Risicotaxatie [X Close] │ ← BlockContainer
├─────────────────────────────────────────────────────────────┤
│ │
│ ┌───────────────────────────────────────────────────────┐ │
│ │ [⚠️] Huidige risico's (3) │ │ ← BlockSection
│ ├───────────────────────────────────────────────────────┤ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ Suïcidaliteit [Matig] │ │ │ ← BlockItem
│ │ │ 12 jan 2026 │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ ┌─────────────────────────────────────────────────┐ │ │
│ │ │ Agressie [Laag] │ │ │ ← BlockItem
│ │ │ 10 jan 2026 │ │ │
│ │ └─────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └───────────────────────────────────────────────────────┘ │
│ │
│ ───────────────────────────────────────────────────────── │
│ [🔗 Bekijk in dossier] [+ Toevoegen] │ ← BlockFooter
│ │
└─────────────────────────────────────────────────────────────┘
```
### 5.2 State Flows
```
Block Lifecycle:
┌─────────┐ data? ┌─────────┐
│ Loading │───────────▶│ Data │
└─────────┘ yes └─────────┘
│ │
│ error │ empty
▼ ▼
┌─────────┐ ┌─────────┐
│ Error │ │ Empty │
└─────────┘ └─────────┘
│ │
│ retry │ action
▼ ▼
┌─────────┐ ┌─────────┐
│ Loading │ │ EPD │
└─────────┘ │ Navigatie│
└─────────┘
```
---
## 6. Technische Architectuur
### 6.1 Component Structuur
```
components/cortex/shared/
├── block-states.tsx # BlockLoading, BlockError, BlockEmpty
├── block-section.tsx # BlockSection
├── block-item.tsx # BlockItem
├── block-footer.tsx # BlockFooter
└── index.ts # Re-exports
lib/cortex/hooks/
├── use-block-data.ts # Generic data fetching hook
└── use-intake-context.ts # Intake context hook
```
### 6.2 Dependencies
```
Externe dependencies:
├── lucide-react # Icons
├── framer-motion # Animaties (optioneel)
└── @/components/ui/button # shadcn Button component
Interne dependencies:
├── @/lib/utils # cn() helper
├── @/lib/cortex/error-handler # safeFetch, getErrorInfo
├── @/stores/cortex-store # useCortexStore
└── @/hooks/use-toast # Toast notifications
```
---
## 7. Technische Specificaties
### 7.1 block-states.tsx
```typescript
// components/cortex/shared/block-states.tsx
'use client';
import { Loader2, AlertCircle, RefreshCw } from 'lucide-react';
import type { LucideIcon } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
// ============================================================================
// BlockLoading
// ============================================================================
interface BlockLoadingProps {
/** Tekst onder de spinner */
message?: string;
/** Extra CSS classes */
className?: string;
}
export function BlockLoading({
message = 'Laden...',
className
}: BlockLoadingProps) {
return (
{message}
);
}
// ============================================================================
// BlockError
// ============================================================================
interface BlockErrorProps {
/** Foutmelding tekst */
message: string;
/** Callback voor retry knop (toont knop indien aanwezig) */
onRetry?: () => void;
/** Extra CSS classes */
className?: string;
}
export function BlockError({
message,
onRetry,
className
}: BlockErrorProps) {
return (
{message}
{onRetry && (
)}
);
}
// ============================================================================
// BlockEmpty
// ============================================================================
interface BlockEmptyProps {
/** Icoon component */
icon: LucideIcon;
/** Hoofdboodschap */
message: string;
/** Optionele actie knop */
action?: {
label: string;
onClick: () => void;
};
/** Extra CSS classes */
className?: string;
}
export function BlockEmpty({
icon: Icon,
message,
action,
className
}: BlockEmptyProps) {
return (
{message}
{action && (
)}
);
}
```
### 7.2 block-section.tsx
```typescript
// components/cortex/shared/block-section.tsx
'use client';
import type { ReactNode } from 'react';
import type { LucideIcon } from 'lucide-react';
import { cn } from '@/lib/utils';
interface BlockSectionProps {
/** Icoon component */
icon: LucideIcon;
/** Tailwind kleur class voor icoon */
iconColor?: string;
/** Sectie titel */
title: string;
/** Optionele count badge */
count?: number;
/** Sectie inhoud */
children: ReactNode;
/** Extra CSS classes */
className?: string;
}
export function BlockSection({
icon: Icon,
iconColor = 'text-slate-600',
title,
count,
children,
className,
}: BlockSectionProps) {
return (
{/* Header */}
{title}
{count !== undefined && (
({count})
)}
{/* Content */}
{children}
);
}
```
### 7.3 block-item.tsx
```typescript
// components/cortex/shared/block-item.tsx
'use client';
import { cn } from '@/lib/utils';
// ============================================================================
// Types
// ============================================================================
export type BadgeVariant = 'default' | 'success' | 'warning' | 'danger';
interface BlockItemProps {
/** Hoofdtekst */
title: string;
/** Subtekst (datum, auteur, etc.) */
subtitle?: string;
/** Status badge */
badge?: {
label: string;
variant: BadgeVariant;
};
/** Klik handler (maakt item klikbaar) */
onClick?: () => void;
/** Extra CSS classes */
className?: string;
}
// ============================================================================
// Styling
// ============================================================================
const BADGE_STYLES: Record = {
default: 'bg-slate-100 text-slate-700 border-slate-200',
success: 'bg-green-50 text-green-700 border-green-200',
warning: 'bg-amber-50 text-amber-700 border-amber-200',
danger: 'bg-red-50 text-red-700 border-red-200',
};
// ============================================================================
// Component
// ============================================================================
export function BlockItem({
title,
subtitle,
badge,
onClick,
className
}: BlockItemProps) {
const isClickable = Boolean(onClick);
const Component = isClickable ? 'button' : 'div';
return (
{/* Content */}
{title}
{subtitle && (
{subtitle}
)}
{/* Badge */}
{badge && (
{badge.label}
)}
);
}
// ============================================================================
// Helper: Get variant from risk level
// ============================================================================
export function getRiskBadgeVariant(level: string): BadgeVariant {
switch (level.toLowerCase()) {
case 'laag':
return 'success';
case 'gemiddeld':
return 'warning';
case 'hoog':
case 'zeer_hoog':
return 'danger';
default:
return 'default';
}
}
```
### 7.4 block-footer.tsx
```typescript
// components/cortex/shared/block-footer.tsx
'use client';
import type { LucideIcon } from 'lucide-react';
import { Loader2 } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
interface ActionConfig {
/** Knop label */
label: string;
/** Optioneel icoon */
icon?: LucideIcon;
/** Click handler */
onClick: () => void;
/** Loading state (alleen voor primary) */
loading?: boolean;
/** Disabled state */
disabled?: boolean;
}
interface BlockFooterProps {
/** Linker actie (ghost button) */
secondaryAction?: ActionConfig;
/** Rechter actie (solid button) */
primaryAction?: ActionConfig;
/** Extra CSS classes */
className?: string;
}
export function BlockFooter({
secondaryAction,
primaryAction,
className
}: BlockFooterProps) {
// Don't render if no actions
if (!secondaryAction && !primaryAction) {
return null;
}
return (
{/* Secondary Action (left) */}
{secondaryAction ? (
) : (
// Spacer
)}
{/* Primary Action (right) */}
{primaryAction && (
)}
);
}
```
### 7.5 use-block-data.ts
```typescript
// lib/cortex/hooks/use-block-data.ts
'use client';
import { useState, useEffect, useCallback } from 'react';
import { useToast } from '@/hooks/use-toast';
import { safeFetch, getErrorInfo } from '@/lib/cortex/error-handler';
// ============================================================================
// Types
// ============================================================================
interface UseBlockDataOptions {
/** API endpoint (relatief pad) */
endpoint: string;
/** Query parameters */
params?: Record;
/** Of data moet worden opgehaald */
enabled?: boolean;
/** Callback bij error */
onError?: (error: Error) => void;
/** Operatie naam voor error messages */
operationName?: string;
}
interface UseBlockDataResult {
/** Opgehaalde data */
data: T | null;
/** Loading state */
isLoading: boolean;
/** Error message */
error: string | null;
/** Refetch functie */
refetch: () => Promise;
}
// ============================================================================
// Hook
// ============================================================================
export function useBlockData({
endpoint,
params,
enabled = true,
onError,
operationName = 'Data laden',
}: UseBlockDataOptions): UseBlockDataResult {
const [data, setData] = useState(null);
const [isLoading, setIsLoading] = useState(enabled);
const [error, setError] = useState(null);
const { toast } = useToast();
const fetchData = useCallback(async () => {
if (!enabled) return;
setIsLoading(true);
setError(null);
try {
// Build URL with params
const url = new URL(endpoint, window.location.origin);
if (params) {
Object.entries(params).forEach(([key, value]) => {
if (value !== undefined) {
url.searchParams.set(key, value);
}
});
}
const response = await safeFetch(
url.toString(),
undefined,
{ operation: operationName }
);
const result = await response.json();
setData(result);
} catch (err) {
const errorInfo = getErrorInfo(err, { operation: operationName });
setError(errorInfo.description);
onError?.(err as Error);
toast({
variant: 'destructive',
title: errorInfo.title,
description: errorInfo.description,
});
} finally {
setIsLoading(false);
}
}, [endpoint, JSON.stringify(params), enabled, onError, operationName, toast]);
useEffect(() => {
if (enabled) {
fetchData();
}
}, [fetchData, enabled]);
return { data, isLoading, error, refetch: fetchData };
}
```
### 7.6 use-intake-context.ts
```typescript
// lib/cortex/hooks/use-intake-context.ts
'use client';
import { useMemo } from 'react';
import { useCortexStore } from '@/stores/cortex-store';
import type { BlockPrefillData } from '@/stores/cortex-store';
import { formatPatientName } from '@/lib/fhir/patient-mapper';
// ============================================================================
// Types
// ============================================================================
interface UseIntakeContextResult {
/** Patient ID (van prefill of activePatient) */
patientId: string | null;
/** Intake ID (van prefill) */
intakeId: string | null;
/** Patient naam (voor display) */
patientName: string | null;
/** Of er voldoende context is */
hasPatientContext: boolean;
/** Of er intake context is */
hasIntakeContext: boolean;
}
// ============================================================================
// Hook
// ============================================================================
export function useIntakeContext(
prefill?: BlockPrefillData
): UseIntakeContextResult {
const { activePatient } = useCortexStore();
return useMemo(() => {
// Patient context: van prefill of activePatient
const patientId = prefill?.patientId || activePatient?.id || null;
const patientName = prefill?.patientName ||
(activePatient ? formatPatientName(activePatient) : null);
// Intake context: alleen van prefill (TODO: activeIntake in store)
const intakeId = prefill?.intakeId || null;
return {
patientId,
intakeId,
patientName,
hasPatientContext: Boolean(patientId),
hasIntakeContext: Boolean(intakeId),
};
}, [prefill, activePatient]);
}
```
### 7.7 Index Export
```typescript
// components/cortex/shared/index.ts
// Block States
export { BlockLoading, BlockError, BlockEmpty } from './block-states';
// Block Layout
export { BlockSection } from './block-section';
export { BlockItem, getRiskBadgeVariant, type BadgeVariant } from './block-item';
export { BlockFooter } from './block-footer';
// Existing
export { PatientListItem, PatientListEmpty, PatientListLoading } from './patient-list-item';
export { LinkedEvidence } from './linked-evidence';
```
---
## 8. API Ontwerp
### 8.1 Geen nieuwe API endpoints
De shared components maken gebruik van bestaande endpoints en de nieuwe intake endpoints (beschreven in apart document). De hooks abstraheren de API calls.
### 8.2 Error Handling
Alle API errors worden afgehandeld via de bestaande `error-handler.ts`:
```typescript
// Voorbeeld error flow
try {
const response = await safeFetch('/api/cortex/intake/123/risks');
// ...
} catch (err) {
const errorInfo = getErrorInfo(err, { operation: 'Risico's laden' });
// errorInfo.title = "Laden mislukt"
// errorInfo.description = "Kon risico's niet ophalen. Probeer opnieuw."
}
```
---
## 9. Gebruikersrollen en Rechten
De shared components zijn UI-only en hebben geen eigen rechtenmodel. Rechten worden afgedwongen door:
1. **API routes** - RLS policies in Supabase
2. **Store** - `activePatient` alleen beschikbaar na selectie
3. **Blocks** - Tonen alleen data waartoe gebruiker toegang heeft
---
## 10. Performance & Accessibility
### 10.1 Performance
| Aspect | Target | Implementatie |
|--------|--------|---------------|
| Render tijd | < 16ms | Geen zware berekeningen in render |
| Bundle size | < 5KB per component | Tree-shakeable exports |
| Re-renders | Minimaal | `useMemo` voor derived state |
### 10.2 Accessibility
| Aspect | Implementatie |
|--------|---------------|
| Screen readers | `aria-label` op interactieve elementen |
| Keyboard nav | `button` elements met `onClick` |
| Focus visible | Tailwind `focus-visible:ring` |
| Color contrast | WCAG AA compliant kleuren |
| Loading state | `aria-busy="true"` |
---
## 11. Testing Strategie
### 11.1 Unit Tests
```typescript
// __tests__/components/cortex/shared/block-item.test.tsx
describe('BlockItem', () => {
it('renders title and subtitle', () => {
render();
expect(screen.getByText('Test')).toBeInTheDocument();
expect(screen.getByText('Sub')).toBeInTheDocument();
});
it('renders badge with correct variant', () => {
render();
const badge = screen.getByText('Hoog');
expect(badge).toHaveClass('bg-red-50');
});
it('is clickable when onClick provided', () => {
const onClick = jest.fn();
render();
fireEvent.click(screen.getByRole('button'));
expect(onClick).toHaveBeenCalled();
});
it('is not clickable when onClick not provided', () => {
render();
expect(screen.queryByRole('button')).not.toBeInTheDocument();
});
});
```
### 11.2 Visual Tests
Storybook stories voor elke component variant:
```typescript
// stories/BlockItem.stories.tsx
export default {
title: 'Cortex/Shared/BlockItem',
component: BlockItem,
};
export const Default = () => ;
export const WithSubtitle = () => ;
export const WithBadge = () => ;
export const Clickable = () => alert('Clicked!')} />;
```
---
## 12. Implementatie Volgorde
| # | Component | Effort | Dependencies |
|---|-----------|--------|--------------|
| 1 | `block-states.tsx` | S | Geen |
| 2 | `block-section.tsx` | S | Geen |
| 3 | `block-item.tsx` | S | Geen |
| 4 | `block-footer.tsx` | S | `@/components/ui/button` |
| 5 | `use-block-data.ts` | M | `error-handler.ts` |
| 6 | `use-intake-context.ts` | S | `cortex-store` |
| 7 | `index.ts` exports | S | 1-6 |
**Totaal geschat:** ~1 dag
---
## 13. Risico's en Mitigatie
| Risico | Impact | Mitigatie |
|--------|--------|-----------|
| Inconsistente styling | Middel | Design tokens in Tailwind config |
| Over-engineering | Laag | YAGNI - alleen bouwen wat nodig is |
| Bundle bloat | Laag | Tree-shakeable exports |
| Breaking changes | Middel | Semantic versioning, deprecation warnings |
---
## 14. Bijlagen & Referenties
**Projectdocumenten:**
- `docs/intent/intake-intent-proces/intake-process-intents.md` - Intent specificaties
- `docs/intent/intake-intent-proces/gap-analyse-intake-cortex.md` - Gap analyse
- `docs/intent/intake-intent-proces/block-template-pattern.md` - Template patterns
**Bestaande code:**
- `components/cortex/shared/patient-list-item.tsx` - Referentie voor patterns
- `components/cortex/blocks/block-container.tsx` - Parent container
- `lib/cortex/error-handler.ts` - Error handling utilities
**Tech documentatie:**
- Lucide Icons: https://lucide.dev/icons
- Tailwind CSS: https://tailwindcss.com/docs
- shadcn/ui Button: https://ui.shadcn.com/docs/components/button