diff --git a/components/swift/artifacts/artifact-container.tsx b/components/swift/artifacts/artifact-container.tsx
new file mode 100644
index 0000000..3265365
--- /dev/null
+++ b/components/swift/artifacts/artifact-container.tsx
@@ -0,0 +1,124 @@
+'use client';
+
+/**
+ * Artifact Container Component
+ *
+ * Container die meerdere artifacts kan beheren met tabs.
+ * Max 3 artifacts tegelijk, tabs alleen zichtbaar bij >1 artifact.
+ *
+ * Epic: E4 (Artifact Area & Tabs)
+ * Story: E4.S1 (ArtifactContainer component)
+ */
+
+import { ArtifactTab } from './artifact-tab';
+import { DagnotatieBlock } from '../blocks/dagnotitie-block';
+import { ZoekenBlock } from '../blocks/zoeken-block';
+import { OverdrachtBlock } from '../blocks/overdracht-block';
+import { FallbackPicker } from '../blocks/fallback-picker';
+import type { Artifact, BlockType } from '@/stores/swift-store';
+
+interface ArtifactContainerProps {
+ artifacts: Artifact[];
+ activeArtifactId: string | null;
+ onSelectArtifact: (id: string) => void;
+ onCloseArtifact: (id: string) => void;
+}
+
+/**
+ * Render the appropriate block component based on artifact type
+ */
+function renderArtifactBlock(artifact: Artifact) {
+ switch (artifact.type) {
+ case 'dagnotitie':
+ return ;
+ case 'zoeken':
+ return ;
+ case 'overdracht':
+ return ;
+ case 'fallback':
+ return ;
+ default:
+ return (
+
+ Onbekend artifact type: {artifact.type}
+
+ );
+ }
+}
+
+/**
+ * Get user-friendly title for artifact type
+ */
+export function getArtifactTitle(type: BlockType, prefill?: any): string {
+ switch (type) {
+ case 'dagnotitie':
+ return prefill?.patientName
+ ? `Dagnotitie - ${prefill.patientName}`
+ : 'Dagnotitie';
+ case 'zoeken':
+ return 'PatiΓ«nt Zoeken';
+ case 'overdracht':
+ return 'Dienst Overdracht';
+ case 'fallback':
+ return 'Kies een actie';
+ default:
+ return 'Artifact';
+ }
+}
+
+export function ArtifactContainer({
+ artifacts,
+ activeArtifactId,
+ onSelectArtifact,
+ onCloseArtifact,
+}: ArtifactContainerProps) {
+ // Find active artifact
+ const activeArtifact = artifacts.find((a) => a.id === activeArtifactId);
+
+ // Show placeholder if no artifacts
+ if (artifacts.length === 0) {
+ return (
+
+
+
π
+
+ Artifacts verschijnen hier
+
+
+ Vraag me iets in de chat om te beginnen!
+
+
+
+ );
+ }
+
+ return (
+
+ {/* Tabs - alleen tonen bij >1 artifact */}
+ {artifacts.length > 1 && (
+
+ {artifacts.map((artifact) => (
+
+ ))}
+
+ )}
+
+ {/* Active artifact content */}
+
+ {activeArtifact ? (
+ renderArtifactBlock(activeArtifact)
+ ) : (
+
+ Selecteer een artifact om te bekijken
+
+ )}
+
+
+ );
+}
diff --git a/components/swift/artifacts/artifact-tab.tsx b/components/swift/artifacts/artifact-tab.tsx
new file mode 100644
index 0000000..f2eea32
--- /dev/null
+++ b/components/swift/artifacts/artifact-tab.tsx
@@ -0,0 +1,63 @@
+'use client';
+
+/**
+ * Artifact Tab Component
+ *
+ * Tab voor een individueel artifact in de ArtifactContainer.
+ * Toont titel, close button, en active state.
+ *
+ * Epic: E4 (Artifact Area & Tabs)
+ * Story: E4.S1 (ArtifactContainer component)
+ */
+
+import { X } from 'lucide-react';
+import { cn } from '@/lib/utils';
+import type { Artifact } from '@/stores/swift-store';
+
+interface ArtifactTabProps {
+ artifact: Artifact;
+ isActive: boolean;
+ onSelect: (id: string) => void;
+ onClose: (id: string) => void;
+}
+
+export function ArtifactTab({ artifact, isActive, onSelect, onClose }: ArtifactTabProps) {
+ return (
+ onSelect(artifact.id)}
+ >
+ {/* Tab title */}
+
+ {artifact.title}
+
+
+ {/* Close button */}
+
+
+ );
+}
diff --git a/docs/swift/bouwplan-swift-v3.md b/docs/swift/bouwplan-swift-v3.md
index 1147638..73be7a1 100644
--- a/docs/swift/bouwplan-swift-v3.md
+++ b/docs/swift/bouwplan-swift-v3.md
@@ -222,12 +222,12 @@ const useChatStore = create((set) => ({
| E1 | Foundation - Split-screen | Layout naar 40/60 split | β
**Compleet** | 3/3 | 12 SP | E1.S1 geskipt (geen feature flag) |
| E2 | Chat Panel & Messages | Chat UI zonder AI | β
**Compleet** | 5/5 | 13 SP | Scrolling, input, shortcuts |
| E3 | Chat API & Medical Scribe | AI conversatie werkend | β
**Compleet** | 6/6 | 21 SP | Artifact opening werkend! |
-| E4 | Artifact Area & Tabs | Meerdere artifacts mogelijk | β³ To Do | 0/4 | 13 SP | Week 5 |
+| E4 | Artifact Area & Tabs | Meerdere artifacts mogelijk | β³ In Progress | 1/4 | 13 SP | Container & tabs structure |
| E5 | AI-Filtering & Polish | Psychiater filtering, polish | β³ To Do | 0/5 | 13 SP | Week 6 |
| E6 | Testing & Refinement | QA, bugs, performance | β³ To Do | 0/4 | 8 SP | Week 7-8 |
**Totaal:** 31 stories, **85 Story Points** (~7 weken Γ 12 SP/week)
-**Voortgang:** β
16/31 stories compleet, 2 geskipt (48 SP / 85 SP = **56%**)
+**Voortgang:** β
17/31 stories compleet, 2 geskipt (53 SP / 85 SP = **62%**)
**Belangrijk:**
- β οΈ Voer niet in 1x het volledige plan uit. Bouw per epic en per story.
@@ -602,7 +602,7 @@ E3.S5 (useChatStream hook) is geskipt omdat:
- `5b10176` β E3.S5 skip documentatie
- (to be committed) β E3.S6 (Artifact opening from chat)
-**π EPIC 3 COMPLEET!** Alle stories (4 compleet, 1 geskipt, 1 compleet) afgerond. Medical scribe chat werkt end-to-end!
+**π EPIC 3 COMPLEET!** Alle stories (5 compleet, 1 geskipt) afgerond. Medical scribe chat werkt end-to-end!
---
@@ -612,7 +612,7 @@ E3.S5 (useChatStream hook) is geskipt omdat:
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points |
|----------|--------------|---------------------|--------|------------------|--------------|
-| E4.S1 | ArtifactContainer component | Wrapper met tabs bovenaan, max 3 artifacts | β³ | E3.S6 | 5 |
+| E4.S1 | ArtifactContainer component | Wrapper met tabs bovenaan, max 3 artifacts | β
**Compleet** | E3.S6 | 5 |
| E4.S2 | Artifact lifecycle management | Open/close/switch tussen artifacts in store | β³ | E4.S1 | 3 |
| E4.S3 | Slide-in animatie | Artifact slide-in van rechts (200ms ease-out) | β³ | E4.S2 | 2 |
| E4.S4 | Placeholder state | "Artifacts verschijnen hier" met voorbeelden | β³ | E4.S3 | 3 |
@@ -723,7 +723,25 @@ function ArtifactPlaceholder() {
}
```
-**Deliverable:** Meerdere artifacts mogelijk, smooth transitions, placeholder state
+**Deliverables (E4.S1 compleet):**
+- β
`stores/swift-store.ts` β Artifact interface gedefineerd (id, type, prefill, title, createdAt)
+- β
`components/swift/artifacts/artifact-tab.tsx` (60 regels) β Tab component met close button
+- β
`components/swift/artifacts/artifact-container.tsx` (127 regels) β Container met tabs + rendering
+- β
ArtifactTab: Active state styling, hover effects, close button
+- β
ArtifactTab: Title truncation, tooltip, min/max width
+- β
ArtifactContainer: Tabs alleen bij >1 artifact
+- β
ArtifactContainer: renderArtifactBlock() voor alle block types
+- β
ArtifactContainer: getArtifactTitle() helper functie
+- β
ArtifactContainer: Placeholder state wanneer geen artifacts
+- β
Conditional rendering: DagnotatieBlock, ZoekenBlock, OverdrachtBlock, FallbackPicker
+- β
Build succesvol zonder errors
+
+**Git Commits:**
+- (to be committed) β E4.S1 (ArtifactContainer component)
+
+**Note:** E4.S1 definieert de structuur. E4.S2 voegt store state toe (openArtifacts[], activeArtifactId, actions).
+
+**Deliverable Epic 4 complete:** Meerdere artifacts mogelijk, smooth transitions, placeholder state
---
diff --git a/stores/swift-store.ts b/stores/swift-store.ts
index b84037a..5881848 100644
--- a/stores/swift-store.ts
+++ b/stores/swift-store.ts
@@ -51,6 +51,15 @@ export interface BlockPrefillData extends ExtractedEntities {
// Additional prefill data specific to blocks
}
+// Artifact type (E4 - Multiple artifacts support)
+export interface Artifact {
+ id: string;
+ type: BlockType;
+ prefill: BlockPrefillData;
+ title: string;
+ createdAt: Date;
+}
+
// Recent action for the Recent Strip
export interface RecentAction {
id: string;