feat(swift): implementeer artifact lifecycle management (E4.S2)
Epic 4 Story 2 compleet: Meerdere artifacts met open/close/switch werken!
E4.S2 - Artifact Lifecycle Management (3 SP)
- Store state: openArtifacts[], activeArtifactId
- Store actions met max 3 artifacts logica
- ArtifactArea refactor naar ArtifactContainer
- CommandCenter integration met nieuwe artifact system
Store Updates (stores/swift-store.ts):
- openArtifacts: Artifact[] — Array van open artifacts
- activeArtifactId: string | null — Currently active artifact
- Initial state: [], null
Store Actions:
```typescript
openArtifact(artifact: Omit<Artifact, 'id' | 'createdAt'>) {
// Auto-generate ID + timestamp
// Max 3 artifacts: remove oldest if at capacity
// Set new artifact as active
}
closeArtifact(id: string) {
// Filter out artifact
// Auto-switch to last remaining if closing active
}
switchArtifact(id: string) {
// Verify artifact exists
// Set activeArtifactId
}
closeAllArtifacts() {
// Clear all artifacts + active ID
}
```
Features:
- Max 3 artifacts enforced (oldest removed at capacity)
- Auto-switch when closing active artifact
- Console logging voor debugging
- ID generation met crypto.randomUUID()
- Timestamp tracking met createdAt
Components Updated:
- components/swift/artifacts/artifact-area.tsx (30 regels)
- Simplified: alleen ArtifactContainer rendering
- Props: openArtifacts, activeArtifactId, switchArtifact, closeArtifact
- components/swift/command-center/command-center.tsx
- openArtifact() i.p.v. openBlock()
- getArtifactTitle() voor dynamic titles
- Escape: closeAllArtifacts() i.p.v. closeBlock()
- Artifact opening met title generation
Integration Flow:
1. Chat: AI genereert action met artifact data
2. ChatPanel: setPendingAction(action)
3. CommandCenter: useEffect detecteert pendingAction
4. CommandCenter: getArtifactTitle(type, prefill)
5. CommandCenter: openArtifact({ type, prefill, title })
6. Store: Auto-generate ID, check max 3, add to array
7. ArtifactArea: Renders ArtifactContainer met artifacts
8. ArtifactContainer: Shows tabs (if >1), renders active block
Console Logging:
- "[Store] Max 3 artifacts reached, removing oldest"
- "[Store] Opening artifact: dagnotitie Dagnotitie - Jan"
- "[Store] Closing artifact: [id] New active: [id]"
- "[Store] Switching to artifact: [id]"
- "[Store] Cannot switch to artifact: [id] (not found)"
Build Status:
- ✅ pnpm build succesvol (geen type errors)
- Alleen bekende warnings (Supabase realtime, useCallback)
Voortgang: 56 SP / 85 SP (66%) - 18/31 stories compleet, 2 geskipt
Next: E4.S3 (Slide-in animatie) - 200ms ease-out transitions
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,85 +1,30 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Artifact Area (v3.0)
|
* Artifact Area (v3.0 + E4)
|
||||||
*
|
*
|
||||||
* Displays active blocks based on activeBlock state.
|
* Displays artifacts using ArtifactContainer with tab support.
|
||||||
* Shows placeholder when no block is active.
|
* Manages multiple artifacts with lifecycle actions.
|
||||||
*
|
*
|
||||||
* Epic: E1 (Foundation), E3 (Chat API & Medical Scribe)
|
* Epic: E1 (Foundation), E3 (Chat API), E4 (Artifact Area & Tabs)
|
||||||
* Stories: E1.S3 (Placeholder componenten), E3.S6 (Artifact opening from chat)
|
* Stories: E1.S3, E3.S6, E4.S1, E4.S2
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { useSwiftStore } from '@/stores/swift-store';
|
import { useSwiftStore } from '@/stores/swift-store';
|
||||||
import { DagnotatieBlock } from '../blocks/dagnotitie-block';
|
import { ArtifactContainer } from './artifact-container';
|
||||||
import { ZoekenBlock } from '../blocks/zoeken-block';
|
|
||||||
import { OverdrachtBlock } from '../blocks/overdracht-block';
|
|
||||||
import { FallbackPicker } from '../blocks/fallback-picker';
|
|
||||||
|
|
||||||
export function ArtifactArea() {
|
export function ArtifactArea() {
|
||||||
const activeBlock = useSwiftStore((s) => s.activeBlock);
|
const openArtifacts = useSwiftStore((s) => s.openArtifacts);
|
||||||
const prefillData = useSwiftStore((s) => s.prefillData);
|
const activeArtifactId = useSwiftStore((s) => s.activeArtifactId);
|
||||||
|
const switchArtifact = useSwiftStore((s) => s.switchArtifact);
|
||||||
|
const closeArtifact = useSwiftStore((s) => s.closeArtifact);
|
||||||
|
|
||||||
// Render active block
|
|
||||||
if (activeBlock) {
|
|
||||||
return (
|
|
||||||
<div className="h-full flex items-center justify-center bg-slate-50 p-6 overflow-y-auto">
|
|
||||||
{activeBlock === 'dagnotitie' && <DagnotatieBlock />}
|
|
||||||
{activeBlock === 'zoeken' && <ZoekenBlock />}
|
|
||||||
{activeBlock === 'overdracht' && <OverdrachtBlock />}
|
|
||||||
{activeBlock === 'fallback' && <FallbackPicker />}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Placeholder when no block is active
|
|
||||||
return (
|
return (
|
||||||
<div className="h-full flex items-center justify-center bg-slate-50 p-6">
|
<ArtifactContainer
|
||||||
<div className="max-w-lg text-center text-slate-500">
|
artifacts={openArtifacts}
|
||||||
<div className="text-5xl mb-4">📋</div>
|
activeArtifactId={activeArtifactId}
|
||||||
<h3 className="text-xl font-medium text-slate-700 mb-3">
|
onSelectArtifact={switchArtifact}
|
||||||
Artifacts verschijnen hier
|
onCloseArtifact={closeArtifact}
|
||||||
</h3>
|
/>
|
||||||
<p className="text-sm mb-6">
|
|
||||||
Wanneer je een actie vraagt in de chat, verschijnt het bijbehorende formulier of overzicht hier.
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div className="bg-white rounded-lg border border-slate-200 p-4 text-left space-y-3">
|
|
||||||
<div className="flex items-start gap-3">
|
|
||||||
<div className="w-8 h-8 rounded bg-blue-100 flex items-center justify-center text-sm">
|
|
||||||
📝
|
|
||||||
</div>
|
|
||||||
<div className="flex-1">
|
|
||||||
<p className="font-medium text-slate-700 text-sm">Dagnotitie</p>
|
|
||||||
<p className="text-xs text-slate-500">Voor snelle notities tijdens de dienst</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-start gap-3">
|
|
||||||
<div className="w-8 h-8 rounded bg-green-100 flex items-center justify-center text-sm">
|
|
||||||
🔍
|
|
||||||
</div>
|
|
||||||
<div className="flex-1">
|
|
||||||
<p className="font-medium text-slate-700 text-sm">Patiënt Zoeken</p>
|
|
||||||
<p className="text-xs text-slate-500">Zoek en selecteer patiënten</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="flex items-start gap-3">
|
|
||||||
<div className="w-8 h-8 rounded bg-purple-100 flex items-center justify-center text-sm">
|
|
||||||
🔄
|
|
||||||
</div>
|
|
||||||
<div className="flex-1">
|
|
||||||
<p className="font-medium text-slate-700 text-sm">Overdracht</p>
|
|
||||||
<p className="text-xs text-slate-500">Dienst overdracht met AI-samenvatting</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p className="text-xs text-slate-400 mt-6">
|
|
||||||
Vraag me iets in de chat om te beginnen!
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,18 +22,19 @@ import { ContextBar } from './context-bar';
|
|||||||
import { OfflineBanner } from './offline-banner';
|
import { OfflineBanner } from './offline-banner';
|
||||||
import { ChatPanel } from '../chat/chat-panel';
|
import { ChatPanel } from '../chat/chat-panel';
|
||||||
import { ArtifactArea } from '../artifacts/artifact-area';
|
import { ArtifactArea } from '../artifacts/artifact-area';
|
||||||
|
import { getArtifactTitle } from '../artifacts/artifact-container';
|
||||||
|
|
||||||
export function CommandCenter() {
|
export function CommandCenter() {
|
||||||
const { closeBlock, activeBlock, openBlock, pendingAction, setPendingAction } = useSwiftStore();
|
const { closeAllArtifacts, openArtifacts, openArtifact, pendingAction, setPendingAction } = useSwiftStore();
|
||||||
const inputRef = useRef<HTMLInputElement>(null);
|
const inputRef = useRef<HTMLInputElement>(null);
|
||||||
|
|
||||||
// Global keyboard shortcuts
|
// Global keyboard shortcuts
|
||||||
const handleKeyDown = useCallback(
|
const handleKeyDown = useCallback(
|
||||||
(e: KeyboardEvent) => {
|
(e: KeyboardEvent) => {
|
||||||
// Escape: close active block
|
// Escape: close all artifacts
|
||||||
if (e.key === 'Escape' && activeBlock) {
|
if (e.key === 'Escape' && openArtifacts.length > 0) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
closeBlock();
|
closeAllArtifacts();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cmd/Ctrl + K: focus input (chat input in v3.0)
|
// Cmd/Ctrl + K: focus input (chat input in v3.0)
|
||||||
@@ -42,7 +43,7 @@ export function CommandCenter() {
|
|||||||
inputRef.current?.focus();
|
inputRef.current?.focus();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
[activeBlock, closeBlock]
|
[openArtifacts, closeAllArtifacts]
|
||||||
);
|
);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -50,7 +51,7 @@ export function CommandCenter() {
|
|||||||
return () => window.removeEventListener('keydown', handleKeyDown);
|
return () => window.removeEventListener('keydown', handleKeyDown);
|
||||||
}, [handleKeyDown]);
|
}, [handleKeyDown]);
|
||||||
|
|
||||||
// E3.S6: Handle pending actions from chat (artifact opening)
|
// E3.S6 + E4.S2: Handle pending actions from chat (artifact opening)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!pendingAction) return;
|
if (!pendingAction) return;
|
||||||
|
|
||||||
@@ -60,10 +61,17 @@ export function CommandCenter() {
|
|||||||
if (pendingAction.artifact) {
|
if (pendingAction.artifact) {
|
||||||
const { type, prefill } = pendingAction.artifact;
|
const { type, prefill } = pendingAction.artifact;
|
||||||
|
|
||||||
console.log('[CommandCenter] Opening artifact:', type, 'with prefill:', prefill);
|
// Generate title for the artifact
|
||||||
|
const title = getArtifactTitle(type, prefill);
|
||||||
|
|
||||||
// Open the block with prefill data
|
console.log('[CommandCenter] Opening artifact:', type, title);
|
||||||
openBlock(type, prefill);
|
|
||||||
|
// Open the artifact (E4.S2 - new artifact system)
|
||||||
|
openArtifact({
|
||||||
|
type,
|
||||||
|
prefill,
|
||||||
|
title,
|
||||||
|
});
|
||||||
|
|
||||||
// Clear pending action after processing
|
// Clear pending action after processing
|
||||||
setPendingAction(null);
|
setPendingAction(null);
|
||||||
@@ -71,7 +79,7 @@ export function CommandCenter() {
|
|||||||
console.log('[CommandCenter] Action has no artifact, skipping');
|
console.log('[CommandCenter] Action has no artifact, skipping');
|
||||||
setPendingAction(null);
|
setPendingAction(null);
|
||||||
}
|
}
|
||||||
}, [pendingAction, openBlock, setPendingAction]);
|
}, [pendingAction, openArtifact, setPendingAction]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col h-screen overflow-hidden">
|
<div className="flex flex-col h-screen overflow-hidden">
|
||||||
|
|||||||
@@ -222,12 +222,12 @@ const useChatStore = create<ChatState>((set) => ({
|
|||||||
| E1 | Foundation - Split-screen | Layout naar 40/60 split | ✅ **Compleet** | 3/3 | 12 SP | E1.S1 geskipt (geen feature flag) |
|
| 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 |
|
| 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! |
|
| E3 | Chat API & Medical Scribe | AI conversatie werkend | ✅ **Compleet** | 6/6 | 21 SP | Artifact opening werkend! |
|
||||||
| E4 | Artifact Area & Tabs | Meerdere artifacts mogelijk | ⏳ In Progress | 1/4 | 13 SP | Container & tabs structure |
|
| E4 | Artifact Area & Tabs | Meerdere artifacts mogelijk | ⏳ In Progress | 2/4 | 13 SP | Lifecycle management werkend |
|
||||||
| E5 | AI-Filtering & Polish | Psychiater filtering, polish | ⏳ To Do | 0/5 | 13 SP | Week 6 |
|
| 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 |
|
| 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)
|
**Totaal:** 31 stories, **85 Story Points** (~7 weken à 12 SP/week)
|
||||||
**Voortgang:** ✅ 17/31 stories compleet, 2 geskipt (53 SP / 85 SP = **62%**)
|
**Voortgang:** ✅ 18/31 stories compleet, 2 geskipt (56 SP / 85 SP = **66%**)
|
||||||
|
|
||||||
**Belangrijk:**
|
**Belangrijk:**
|
||||||
- ⚠️ Voer niet in 1x het volledige plan uit. Bouw per epic en per story.
|
- ⚠️ Voer niet in 1x het volledige plan uit. Bouw per epic en per story.
|
||||||
@@ -613,7 +613,7 @@ E3.S5 (useChatStream hook) is geskipt omdat:
|
|||||||
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points |
|
| Story ID | Beschrijving | Acceptatiecriteria | Status | Afhankelijkheden | Story Points |
|
||||||
|----------|--------------|---------------------|--------|------------------|--------------|
|
|----------|--------------|---------------------|--------|------------------|--------------|
|
||||||
| E4.S1 | ArtifactContainer component | Wrapper met tabs bovenaan, max 3 artifacts | ✅ **Compleet** | 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.S2 | Artifact lifecycle management | Open/close/switch tussen artifacts in store | ✅ **Compleet** | E4.S1 | 3 |
|
||||||
| E4.S3 | Slide-in animatie | Artifact slide-in van rechts (200ms ease-out) | ⏳ | E4.S2 | 2 |
|
| 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 |
|
| E4.S4 | Placeholder state | "Artifacts verschijnen hier" met voorbeelden | ⏳ | E4.S3 | 3 |
|
||||||
|
|
||||||
@@ -739,7 +739,27 @@ function ArtifactPlaceholder() {
|
|||||||
**Git Commits:**
|
**Git Commits:**
|
||||||
- (to be committed) — E4.S1 (ArtifactContainer component)
|
- (to be committed) — E4.S1 (ArtifactContainer component)
|
||||||
|
|
||||||
**Note:** E4.S1 definieert de structuur. E4.S2 voegt store state toe (openArtifacts[], activeArtifactId, actions).
|
**Deliverables (E4.S2 compleet):**
|
||||||
|
- ✅ `stores/swift-store.ts` — openArtifacts[], activeArtifactId state
|
||||||
|
- ✅ Store actions: openArtifact(), closeArtifact(), switchArtifact(), closeAllArtifacts()
|
||||||
|
- ✅ openArtifact: Auto-generate ID + timestamp, max 3 logic (remove oldest)
|
||||||
|
- ✅ closeArtifact: Filter out artifact, auto-switch to last remaining
|
||||||
|
- ✅ switchArtifact: Verify exists, set activeArtifactId
|
||||||
|
- ✅ closeAllArtifacts: Clear all artifacts + active ID
|
||||||
|
- ✅ Console logging: "[Store] Opening artifact:", "[Store] Closing artifact:", etc.
|
||||||
|
- ✅ ArtifactArea: Refactored to use ArtifactContainer
|
||||||
|
- ✅ ArtifactArea: Pass store actions (switchArtifact, closeArtifact)
|
||||||
|
- ✅ CommandCenter: openArtifact() i.p.v. openBlock()
|
||||||
|
- ✅ CommandCenter: getArtifactTitle() voor artifact titles
|
||||||
|
- ✅ CommandCenter: Escape key → closeAllArtifacts()
|
||||||
|
- ✅ Integration: Chat → Action → openArtifact → Tabs + Container
|
||||||
|
- ✅ Build succesvol zonder errors
|
||||||
|
|
||||||
|
**Git Commits:**
|
||||||
|
- `855744c` — E4.S1 (ArtifactContainer component)
|
||||||
|
- (to be committed) — E4.S2 (Artifact lifecycle management)
|
||||||
|
|
||||||
|
**Note:** E4.S1 + E4.S2 maken het artifact systeem volledig functioneel. E4.S3 voegt animaties toe.
|
||||||
|
|
||||||
**Deliverable Epic 4 complete:** Meerdere artifacts mogelijk, smooth transitions, placeholder state
|
**Deliverable Epic 4 complete:** Meerdere artifacts mogelijk, smooth transitions, placeholder state
|
||||||
|
|
||||||
|
|||||||
@@ -92,15 +92,25 @@ interface SwiftStore {
|
|||||||
isStreaming: boolean;
|
isStreaming: boolean;
|
||||||
pendingAction: ChatAction | null;
|
pendingAction: ChatAction | null;
|
||||||
|
|
||||||
|
// Artifact state (E4 - Multiple artifacts support)
|
||||||
|
openArtifacts: Artifact[];
|
||||||
|
activeArtifactId: string | null;
|
||||||
|
|
||||||
// Context actions
|
// Context actions
|
||||||
setActivePatient: (patient: Patient | null) => void;
|
setActivePatient: (patient: Patient | null) => void;
|
||||||
setShift: (shift: ShiftType) => void;
|
setShift: (shift: ShiftType) => void;
|
||||||
|
|
||||||
// Block actions
|
// Block actions (legacy - will be replaced by artifact actions)
|
||||||
openBlock: (type: BlockType, prefill?: BlockPrefillData) => void;
|
openBlock: (type: BlockType, prefill?: BlockPrefillData) => void;
|
||||||
closeBlock: () => void;
|
closeBlock: () => void;
|
||||||
setBlockLoading: (loading: boolean) => void;
|
setBlockLoading: (loading: boolean) => void;
|
||||||
|
|
||||||
|
// Artifact actions (E4.S2)
|
||||||
|
openArtifact: (artifact: Omit<Artifact, 'id' | 'createdAt'>) => void;
|
||||||
|
closeArtifact: (id: string) => void;
|
||||||
|
switchArtifact: (id: string) => void;
|
||||||
|
closeAllArtifacts: () => void;
|
||||||
|
|
||||||
// Input actions
|
// Input actions
|
||||||
setInputValue: (value: string) => void;
|
setInputValue: (value: string) => void;
|
||||||
setVoiceActive: (active: boolean) => void;
|
setVoiceActive: (active: boolean) => void;
|
||||||
@@ -143,6 +153,9 @@ const initialState = {
|
|||||||
chatMessages: [],
|
chatMessages: [],
|
||||||
isStreaming: false,
|
isStreaming: false,
|
||||||
pendingAction: null,
|
pendingAction: null,
|
||||||
|
// Artifact state (E4)
|
||||||
|
openArtifacts: [],
|
||||||
|
activeArtifactId: null,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create the store
|
// Create the store
|
||||||
@@ -264,6 +277,89 @@ export const useSwiftStore = create<SwiftStore>()(
|
|||||||
|
|
||||||
setPendingAction: (action) => set({ pendingAction: action }, false, 'setPendingAction'),
|
setPendingAction: (action) => set({ pendingAction: action }, false, 'setPendingAction'),
|
||||||
|
|
||||||
|
// Artifact actions (E4.S2)
|
||||||
|
openArtifact: (artifactData) => {
|
||||||
|
set(
|
||||||
|
(state) => {
|
||||||
|
// Create artifact with unique ID and timestamp
|
||||||
|
const newArtifact: Artifact = {
|
||||||
|
...artifactData,
|
||||||
|
id: crypto.randomUUID(),
|
||||||
|
createdAt: new Date(),
|
||||||
|
};
|
||||||
|
|
||||||
|
let artifacts = [...state.openArtifacts];
|
||||||
|
|
||||||
|
// Max 3 artifacts - remove oldest if at capacity
|
||||||
|
if (artifacts.length >= 3) {
|
||||||
|
console.log('[Store] Max 3 artifacts reached, removing oldest');
|
||||||
|
artifacts = artifacts.slice(1); // Remove first (oldest)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[Store] Opening artifact:', newArtifact.type, newArtifact.title);
|
||||||
|
|
||||||
|
return {
|
||||||
|
openArtifacts: [...artifacts, newArtifact],
|
||||||
|
activeArtifactId: newArtifact.id,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
'openArtifact'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
closeArtifact: (id) => {
|
||||||
|
set(
|
||||||
|
(state) => {
|
||||||
|
const artifacts = state.openArtifacts.filter((a) => a.id !== id);
|
||||||
|
|
||||||
|
// If closing active artifact, switch to last remaining artifact
|
||||||
|
let newActiveId = state.activeArtifactId;
|
||||||
|
if (state.activeArtifactId === id) {
|
||||||
|
newActiveId = artifacts.length > 0 ? artifacts[artifacts.length - 1].id : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[Store] Closing artifact:', id, 'New active:', newActiveId);
|
||||||
|
|
||||||
|
return {
|
||||||
|
openArtifacts: artifacts,
|
||||||
|
activeArtifactId: newActiveId,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
'closeArtifact'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
switchArtifact: (id) => {
|
||||||
|
set(
|
||||||
|
(state) => {
|
||||||
|
// Verify artifact exists
|
||||||
|
const exists = state.openArtifacts.some((a) => a.id === id);
|
||||||
|
if (!exists) {
|
||||||
|
console.warn('[Store] Cannot switch to artifact:', id, '(not found)');
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('[Store] Switching to artifact:', id);
|
||||||
|
return { activeArtifactId: id };
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
'switchArtifact'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
|
closeAllArtifacts: () => {
|
||||||
|
set(
|
||||||
|
{
|
||||||
|
openArtifacts: [],
|
||||||
|
activeArtifactId: null,
|
||||||
|
},
|
||||||
|
false,
|
||||||
|
'closeAllArtifacts'
|
||||||
|
);
|
||||||
|
},
|
||||||
|
|
||||||
// Reset
|
// Reset
|
||||||
reset: () => set(initialState, false, 'reset'),
|
reset: () => set(initialState, false, 'reset'),
|
||||||
}),
|
}),
|
||||||
|
|||||||
Reference in New Issue
Block a user