Artifact First-Class Citizen Architecture
Executive Summary
This document outlines the complete architectural design for elevating artifacts from embedded content to first-class citizens in the Bike4Mind system. The design includes comprehensive type definitions, database models, CRUD APIs, versioning system, and UI components to support all current KnowledgeViewer content types with special emphasis on QuestMaster artifacts.
Core Architecture Principles
- Artifacts as Entities: Every artifact is a standalone entity with its own lifecycle
- Version Control: Full version history with branching and merging capabilities
- Type Safety: Strongly typed artifacts with discriminated unions
- Performance: Lazy loading, caching, and efficient querying
- Extensibility: Easy to add new artifact types without breaking existing code
Type System Design
Base Artifact Types
// Base artifact interface that all artifacts extend
interface BaseArtifact {
id: string;
type: ArtifactType;
title: string;
description?: string;
metadata: ArtifactMetadata;
// Versioning
version: number;
versionTag?: string; // e.g., "v1.0.0", "draft", "final"
parentVersionId?: string; // For version tree
// Timestamps
createdAt: Date;
updatedAt: Date;
publishedAt?: Date;
// Ownership & Access
userId: string;
projectId?: string;
organizationId?: string;
visibility: 'private' | 'project' | 'organization' | 'public';
permissions: ArtifactPermissions;
// Relationships
sourceQuestId?: string;
sessionId?: string;
parentArtifactId?: string; // For derived artifacts
// Status
status: ArtifactStatus;
tags: string[];
// Content
contentHash: string; // SHA-256 hash for integrity
contentSize: number; // Bytes
}
// Artifact types enum matching KnowledgeViewer
enum ArtifactType {
// Code artifacts
CODE = 'code',
REACT = 'react',
// Visual artifacts
MERMAID = 'mermaid',
SVG = 'svg',
HTML = 'html',
// Document artifacts
MARKDOWN = 'markdown',
PDF = 'pdf',
DOCX = 'docx',
// Data artifacts
CSV = 'csv',
JSON = 'json',
XLSX = 'xlsx',
// Media artifacts
IMAGE = 'image',
// AI artifacts
QUESTMASTER = 'questmaster',
// Generic
TEXT = 'text',
FILE = 'file'
}
// Status enum
enum ArtifactStatus {
DRAFT = 'draft',
REVIEW = 'review',
PUBLISHED = 'published',
ARCHIVED = 'archived',
DELETED = 'deleted'
}
// Metadata interface
interface ArtifactMetadata {
mimeType?: string;
language?: string; // For code artifacts
framework?: string; // For React/code artifacts
dependencies?: string[]; // For code artifacts
dimensions?: { width: number; height: number }; // For images/visual
duration?: number; // For future video/audio support
encoding?: string;
[key: string]: any; // Extensible metadata
}
// Permissions interface
interface ArtifactPermissions {
canRead: string[]; // User IDs
canWrite: string[]; // User IDs
canDelete: string[]; // User IDs
isPublic: boolean;
inheritFromProject: boolean;
}
Specific Artifact Types
// Code Artifact
interface CodeArtifact extends BaseArtifact {
type: ArtifactType.CODE;
content: {
code: string;
language: string;
fileName?: string;
lineCount: number;
hasTests?: boolean;
testCoverage?: number;
};
}
// React Artifact
interface ReactArtifact extends BaseArtifact {
type: ArtifactType.REACT;
content: {
code: string;
componentName: string;
props?: Record<string, any>;
dependencies: string[];
hasDefaultExport: boolean;
isTypeScript: boolean;
previewData?: any; // Mock data for preview
};
}
// QuestMaster Artifact (Special emphasis)
interface QuestMasterArtifact extends BaseArtifact {
type: ArtifactType.QUESTMASTER;
content: {
goal: string;
quests: Quest[];
totalSteps: number;
estimatedDuration?: number;
complexity: 'low' | 'medium' | 'high';
category?: string;
prerequisites?: string[];
};
}
interface Quest {
id: string;
title: string;
description: string;
complexity: string;
status: QuestStatus;
subQuests: SubQuest[];
dependencies?: string[]; // Other quest IDs
metadata?: Record<string, any>;
}
interface SubQuest {
id: string;
title: string;
description?: string;
status: QuestStatus;
questId?: string; // Reference to chat quest
completedAt?: Date;
result?: any;
}
enum QuestStatus {
NOT_STARTED = 'not_started',
IN_PROGRESS = 'in_progress',
COMPLETED = 'completed',
FAILED = 'failed',
SKIPPED = 'skipped'
}
// Mermaid Artifact
interface MermaidArtifact extends BaseArtifact {
type: ArtifactType.MERMAID;
content: {
definition: string;
diagramType: 'flowchart' | 'sequence' | 'class' | 'state' | 'er' | 'gantt' | 'pie' | 'mindmap';
theme?: string;
};
}
// HTML Artifact
interface HtmlArtifact extends BaseArtifact {
type: ArtifactType.HTML;
content: {
html: string;
css?: string;
javascript?: string;
isSanitized: boolean;
usesExternalResources: boolean;
};
}
// SVG Artifact
interface SvgArtifact extends BaseArtifact {
type: ArtifactType.SVG;
content: {
svg: string;
viewBox?: string;
width?: number;
height?: number;
isAnimated: boolean;
};
}
// File Artifact (Generic)
interface FileArtifact extends BaseArtifact {
type: ArtifactType.FILE;
content: {
fileUrl: string;
fileName: string;
mimeType: string;
size: number;
checksum: string;
};
}
// Union type for all artifacts
type Artifact =
| CodeArtifact
| ReactArtifact
| QuestMasterArtifact
| MermaidArtifact
| HtmlArtifact
| SvgArtifact
| FileArtifact;
Version Management Types
// Version history entry
interface ArtifactVersion {
id: string;
artifactId: string;
version: number;
versionTag?: string;
changes: VersionChange[];
createdAt: Date;
createdBy: string;
message?: string; // Commit message
parentVersionId?: string;
isCurrent: boolean;
}
interface VersionChange {
path: string; // JSON path to changed field
oldValue: any;
newValue: any;
operation: 'add' | 'update' | 'delete';
}
// Version comparison
interface VersionDiff {
fromVersion: number;
toVersion: number;
changes: VersionChange[];
summary: string;
}
Database Schema Design
MongoDB Collections
// artifacts collection
{
_id: ObjectId,
id: string, // UUID
type: string,
title: string,
description: string,
// Version info
version: number,
versionTag: string,
currentVersionId: ObjectId, // Reference to current version
// Timestamps
createdAt: Date,
updatedAt: Date,
publishedAt: Date,
// Ownership
userId: ObjectId,
projectId: ObjectId,
organizationId: ObjectId,
// Access control
visibility: string,
permissions: {
canRead: [ObjectId],
canWrite: [ObjectId],
canDelete: [ObjectId],
isPublic: boolean,
inheritFromProject: boolean
},
// Relationships
sourceQuestId: ObjectId,
sessionId: ObjectId,
parentArtifactId: ObjectId,
// Status and metadata
status: string,
tags: [string],
metadata: {},
// Content reference
contentId: ObjectId, // Reference to artifact_contents
contentHash: string,
contentSize: number,
// Indexing
searchText: string, // Denormalized for full-text search
// Soft delete
deletedAt: Date
}
// artifact_contents collection (separate for performance)
{
_id: ObjectId,
artifactId: ObjectId,
content: {}, // Type-specific content
compressed: boolean,
encryption: string
}
// artifact_versions collection
{
_id: ObjectId,
artifactId: ObjectId,
version: number,
versionTag: string,
// Version metadata
createdAt: Date,
createdBy: ObjectId,
message: string,
// Version tree
parentVersionId: ObjectId,
isCurrent: boolean,
// Content snapshot
contentSnapshot: {}, // Full content at this version
contentDelta: {}, // Delta from parent version
// Changes
changes: [{
path: string,
oldValue: Mixed,
newValue: Mixed,
operation: string
}]
}
// artifact_access_logs collection (for audit)
{
_id: ObjectId,
artifactId: ObjectId,
userId: ObjectId,
action: string, // 'view', 'edit', 'delete', 'share'
timestamp: Date,
ip: string,
userAgent: string,
changes: {}
}
Indexes
// artifacts collection indexes
db.artifacts.createIndex({ "userId": 1, "status": 1 })
db.artifacts.createIndex({ "projectId": 1, "type": 1 })
db.artifacts.createIndex({ "tags": 1 })
db.artifacts.createIndex({ "searchText": "text" })
db.artifacts.createIndex({ "createdAt": -1 })
db.artifacts.createIndex({ "type": 1, "status": 1, "visibility": 1 })
// artifact_versions indexes
db.artifact_versions.createIndex({ "artifactId": 1, "version": -1 })
db.artifact_versions.createIndex({ "artifactId": 1, "isCurrent": 1 })
// Compound indexes for common queries
db.artifacts.createIndex({
"userId": 1,
"type": 1,
"status": 1,
"createdAt": -1
})
API Design
RESTful API Endpoints
// Base artifact CRUD operations
GET /api/artifacts // List artifacts with filtering
POST /api/artifacts // Create new artifact
GET /api/artifacts/:id // Get specific artifact
PUT /api/artifacts/:id // Update artifact
DELETE /api/artifacts/:id // Delete artifact (soft delete)
// Version management
GET /api/artifacts/:id/versions // List all versions
GET /api/artifacts/:id/versions/:version // Get specific version
POST /api/artifacts/:id/versions // Create new version
POST /api/artifacts/:id/versions/:version/restore // Restore version
GET /api/artifacts/:id/versions/:v1/diff/:v2 // Compare versions
// Bulk operations
POST /api/artifacts/bulk // Create multiple artifacts
PUT /api/artifacts/bulk // Update multiple artifacts
DELETE /api/artifacts/bulk // Delete multiple artifacts
// Search and discovery
GET /api/artifacts/search // Full-text search
GET /api/artifacts/recent // Recently accessed
GET /api/artifacts/popular // Most accessed
GET /api/artifacts/shared // Shared with user
// QuestMaster specific
POST /api/artifacts/:id/quests/:questId/start // Start a quest
PUT /api/artifacts/:id/quests/:questId/status // Update quest status
POST /api/artifacts/:id/quests/:questId/complete // Complete quest
// Sharing and permissions
POST /api/artifacts/:id/share // Share artifact
PUT /api/artifacts/:id/permissions // Update permissions
GET /api/artifacts/:id/access-log // View access history
// Import/Export
POST /api/artifacts/import // Import from various formats
GET /api/artifacts/:id/export // Export in various formats
API Request/Response Types
// List artifacts request
interface ListArtifactsRequest {
filters?: {
type?: ArtifactType[];
status?: ArtifactStatus[];
userId?: string;
projectId?: string;
tags?: string[];
search?: string;
dateRange?: {
start: Date;
end: Date;
};
};
sort?: {
field: 'createdAt' | 'updatedAt' | 'title' | 'version';
order: 'asc' | 'desc';
};
pagination?: {
page: number;
limit: number;
};
include?: ('versions' | 'permissions' | 'content')[];
}
// Create artifact request
interface CreateArtifactRequest {
type: ArtifactType;
title: string;
description?: string;
content: any; // Type-specific content
metadata?: ArtifactMetadata;
tags?: string[];
visibility?: 'private' | 'project' | 'organization' | 'public';
projectId?: string;
sourceQuestId?: string;
sessionId?: string;
}
// Update artifact request
interface UpdateArtifactRequest {
title?: string;
description?: string;
content?: any;
metadata?: Partial<ArtifactMetadata>;
tags?: string[];
status?: ArtifactStatus;
visibility?: 'private' | 'project' | 'organization' | 'public';
createNewVersion?: boolean;
versionMessage?: string;
}
// Artifact response
interface ArtifactResponse {
artifact: Artifact;
versions?: ArtifactVersion[];
permissions?: ArtifactPermissions;
accessLog?: ArtifactAccessLog[];
}
Service Layer Architecture
// Main artifact service
export class ArtifactService {
constructor(
private db: DatabaseConnection,
private storage: StorageService,
private search: SearchService,
private cache: CacheService,
private events: EventService
) {}
// CRUD operations
async createArtifact(data: CreateArtifactRequest, userId: string): Promise<Artifact> {
// Validate input
const validated = await this.validateArtifactData(data);
// Create artifact document
const artifact = await this.db.artifacts.create({
...validated,
id: generateUUID(),
version: 1,
userId,
createdAt: new Date(),
updatedAt: new Date(),
status: ArtifactStatus.DRAFT,
contentHash: await this.hashContent(validated.content),
contentSize: this.calculateSize(validated.content)
});
// Store content separately
await this.db.artifactContents.create({
artifactId: artifact.id,
content: validated.content
});
// Create initial version
await this.createVersion(artifact, 'Initial version', userId);
// Index for search
await this.search.indexArtifact(artifact);
// Emit event
await this.events.emit('artifact.created', { artifact, userId });
// Cache
await this.cache.set(`artifact:${artifact.id}`, artifact);
return artifact;
}
async updateArtifact(
id: string,
updates: UpdateArtifactRequest,
userId: string
): Promise<Artifact> {
// Get current artifact
const current = await this.getArtifact(id);
// Check permissions
await this.checkPermission(current, userId, 'write');
// Create new version if requested
if (updates.createNewVersion) {
return this.createNewVersion(current, updates, userId);
}
// Update artifact
const updated = await this.db.artifacts.update(id, {
...updates,
updatedAt: new Date()
});
// Update content if changed
if (updates.content) {
await this.db.artifactContents.update(
{ artifactId: id },
{ content: updates.content }
);
}
// Re-index for search
await this.search.updateArtifact(updated);
// Invalidate cache
await this.cache.delete(`artifact:${id}`);
// Emit event
await this.events.emit('artifact.updated', { artifact: updated, userId });
return updated;
}
async deleteArtifact(id: string, userId: string): Promise<void> {
const artifact = await this.getArtifact(id);
// Check permissions
await this.checkPermission(artifact, userId, 'delete');
// Soft delete
await this.db.artifacts.update(id, {
status: ArtifactStatus.DELETED,
deletedAt: new Date()
});
// Remove from search index
await this.search.removeArtifact(id);
// Invalidate cache
await this.cache.delete(`artifact:${id}`);
// Emit event
await this.events.emit('artifact.deleted', { artifactId: id, userId });
}
// Version management
async createNewVersion(
artifact: Artifact,
updates: UpdateArtifactRequest,
userId: string
): Promise<Artifact> {
const newVersion = artifact.version + 1;
// Calculate changes
const changes = this.calculateChanges(artifact, updates);
// Create version record
await this.db.artifactVersions.create({
artifactId: artifact.id,
version: newVersion,
versionTag: updates.versionMessage,
createdAt: new Date(),
createdBy: userId,
message: updates.versionMessage,
parentVersionId: artifact.currentVersionId,
contentSnapshot: { ...artifact.content, ...updates.content },
changes
});
// Update artifact with new version
const updated = await this.db.artifacts.update(artifact.id, {
...updates,
version: newVersion,
currentVersionId: newVersion,
updatedAt: new Date()
});
return updated;
}
async getArtifactVersions(artifactId: string): Promise<ArtifactVersion[]> {
return this.db.artifactVersions.find({
artifactId,
$sort: { version: -1 }
});
}
async compareVersions(
artifactId: string,
fromVersion: number,
toVersion: number
): Promise<VersionDiff> {
const from = await this.db.artifactVersions.findOne({
artifactId,
version: fromVersion
});
const to = await this.db.artifactVersions.findOne({
artifactId,
version: toVersion
});
const changes = this.calculateChanges(from.contentSnapshot, to.contentSnapshot);
return {
fromVersion,
toVersion,
changes,
summary: this.generateDiffSummary(changes)
};
}
// Search and discovery
async searchArtifacts(
query: string,
filters: ListArtifactsRequest['filters']
): Promise<Artifact[]> {
return this.search.search({
query,
filters,
boost: {
title: 2.0,
description: 1.5,
tags: 1.2,
content: 1.0
}
});
}
// QuestMaster specific operations
async startQuest(
artifactId: string,
questId: string,
userId: string
): Promise<void> {
const artifact = await this.getArtifact(artifactId);
if (artifact.type !== ArtifactType.QUESTMASTER) {
throw new Error('Not a QuestMaster artifact');
}
// Update quest status
const content = artifact.content as QuestMasterArtifact['content'];
const quest = content.quests.find(q => q.id === questId);
if (!quest) {
throw new Error('Quest not found');
}
quest.status = QuestStatus.IN_PROGRESS;
await this.updateArtifact(artifactId, {
content,
createNewVersion: false
}, userId);
// Emit event for real-time updates
await this.events.emit('quest.started', { artifactId, questId, userId });
}
// Caching strategies
private async getCachedArtifact(id: string): Promise<Artifact | null> {
const cached = await this.cache.get(`artifact:${id}`);
if (cached) {
// Check if cache is still valid
const ttl = await this.cache.ttl(`artifact:${id}`);
if (ttl > 0) {
return cached;
}
}
return null;
}
// Permission checking
private async checkPermission(
artifact: Artifact,
userId: string,
action: 'read' | 'write' | 'delete'
): Promise<void> {
// Owner always has permission
if (artifact.userId === userId) {
return;
}
// Check specific permissions
const permissions = artifact.permissions;
const hasPermission =
(action === 'read' && permissions.canRead.includes(userId)) ||
(action === 'write' && permissions.canWrite.includes(userId)) ||
(action === 'delete' && permissions.canDelete.includes(userId));
if (!hasPermission) {
throw new ForbiddenError(`No ${action} permission for artifact`);
}
}
}
UI Components Architecture
Version Dropdown Component
interface VersionDropdownProps {
artifact: Artifact;
currentVersion: number;
onVersionChange: (version: number) => void;
showDiff?: boolean;
}
export const VersionDropdown: React.FC<VersionDropdownProps> = ({
artifact,
currentVersion,
onVersionChange,
showDiff = false
}) => {
const { data: versions } = useArtifactVersions(artifact.id);
const [compareMode, setCompareMode] = useState(false);
const [compareVersion, setCompareVersion] = useState<number | null>(null);
return (
<Box sx={{ display: 'flex', gap: 1, alignItems: 'center' }}>
<Select
value={currentVersion}
onChange={(_, value) => onVersionChange(value as number)}
startDecorator={<HistoryIcon />}
endDecorator={
<Chip size="sm" variant="soft">
v{currentVersion}
</Chip>
}
>
{versions?.map(version => (
<Option key={version.version} value={version.version}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', width: '100%' }}>
<Typography level="body-sm">
Version {version.version}
{version.versionTag && ` - ${version.versionTag}`}
</Typography>
<Typography level="body-xs" sx={{ color: 'text.secondary' }}>
{formatRelativeTime(version.createdAt)}
</Typography>
</Box>
{version.message && (
<Typography level="body-xs" sx={{ color: 'text.secondary' }}>
{version.message}
</Typography>
)}
</Option>
))}
</Select>
{showDiff && (
<IconButton
size="sm"
variant={compareMode ? 'solid' : 'plain'}
onClick={() => setCompareMode(!compareMode)}
>
<CompareIcon />
</IconButton>
)}
{compareMode && (
<Select
placeholder="Compare with..."
value={compareVersion}
onChange={(_, value) => setCompareVersion(value as number)}
size="sm"
>
{versions
?.filter(v => v.version !== currentVersion)
.map(version => (
<Option key={version.version} value={version.version}>
v{version.version}
</Option>
))}
</Select>
)}
</Box>
);
};
Artifact Preview Card
interface ArtifactPreviewCardProps {
artifact: Artifact;
onClick?: () => void;
showVersions?: boolean;
showActions?: boolean;
}
export const ArtifactPreviewCard: React.FC<ArtifactPreviewCardProps> = ({
artifact,
onClick,
showVersions = true,
showActions = true
}) => {
const [currentVersion, setCurrentVersion] = useState(artifact.version);
const { data: versionData } = useArtifactVersion(artifact.id, currentVersion);
const getIcon = () => {
switch (artifact.type) {
case ArtifactType.QUESTMASTER:
return <QuestIcon />;
case ArtifactType.CODE:
case ArtifactType.REACT:
return <CodeIcon />;
case ArtifactType.MERMAID:
return <DiagramIcon />;
case ArtifactType.HTML:
case ArtifactType.SVG:
return <WebIcon />;
default:
return <FileIcon />;
}
};
const getPreviewContent = () => {
switch (artifact.type) {
case ArtifactType.QUESTMASTER:
const questContent = versionData?.content as QuestMasterArtifact['content'];
return (
<Box>
<Typography level="body-sm" sx={{ mb: 1 }}>
{questContent.goal}
</Typography>
<Stack direction="row" spacing={1}>
<Chip size="sm" variant="soft">
{questContent.quests.length} Quests
</Chip>
<Chip size="sm" variant="soft">
{questContent.totalSteps} Steps
</Chip>
<Chip size="sm" variant="soft" color={getDifficultyColor(questContent.complexity)}>
{questContent.complexity}
</Chip>
</Stack>
</Box>
);
case ArtifactType.CODE:
case ArtifactType.REACT:
const codeContent = versionData?.content as CodeArtifact['content'];
return (
<Box sx={{ position: 'relative', height: 150, overflow: 'hidden' }}>
<SyntaxHighlighter
language={codeContent.language}
style={oneDark}
customStyle={{
margin: 0,
fontSize: '12px',
background: 'transparent'
}}
>
{codeContent.code.slice(0, 500)}
</SyntaxHighlighter>
<Box
sx={{
position: 'absolute',
bottom: 0,
left: 0,
right: 0,
height: 40,
background: 'linear-gradient(transparent, rgba(0,0,0,0.8))'
}}
/>
</Box>
);
case ArtifactType.MERMAID:
return (
<Box sx={{ height: 150, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<MermaidPreview definition={versionData?.content.definition} />
</Box>
);
default:
return (
<Typography level="body-sm" sx={{ color: 'text.secondary' }}>
{artifact.description || 'No preview available'}
</Typography>
);
}
};
return (
<Card
variant="outlined"
sx={{
cursor: onClick ? 'pointer' : 'default',
transition: 'all 0.2s',
'&:hover': onClick ? {
transform: 'translateY(-2px)',
boxShadow: 'md'
} : {}
}}
onClick={onClick}
>
<CardContent>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 2 }}>
<Stack direction="row" spacing={1} alignItems="center">
{getIcon()}
<Typography level="h4">{artifact.title}</Typography>
</Stack>
{showVersions && (
<VersionDropdown
artifact={artifact}
currentVersion={currentVersion}
onVersionChange={setCurrentVersion}
/>
)}
</Box>
<Box sx={{ mb: 2 }}>
{getPreviewContent()}
</Box>
<Stack direction="row" spacing={1} sx={{ mb: 2 }}>
{artifact.tags.map(tag => (
<Chip key={tag} size="sm" variant="soft">
{tag}
</Chip>
))}
</Stack>
{showActions && (
<Stack direction="row" spacing={1} justifyContent="flex-end">
<IconButton size="sm" variant="plain">
<ShareIcon />
</IconButton>
<IconButton size="sm" variant="plain">
<EditIcon />
</IconButton>
<IconButton size="sm" variant="plain">
<MoreVertIcon />
</IconButton>
</Stack>
)}
<Typography level="body-xs" sx={{ color: 'text.secondary', mt: 1 }}>
Updated {formatRelativeTime(artifact.updatedAt)} • v{currentVersion}
</Typography>
</CardContent>
</Card>
);
};
Enhanced Knowledge Viewer Integration
// Updated KnowledgeViewer to support artifact system
const KnowledgeViewer: React.FC = () => {
const { currentSession } = useSessions();
const [selectedArtifact, setSelectedArtifact] = useState<Artifact | null>(null);
const [selectedVersion, setSelectedVersion] = useState<number | null>(null);
// Fetch artifacts for current session
const { data: artifacts } = useArtifacts({
filters: {
sessionId: currentSession?.id,
status: [ArtifactStatus.PUBLISHED, ArtifactStatus.DRAFT]
},
sort: { field: 'updatedAt', order: 'desc' }
});
// Handle artifact selection
const handleArtifactSelect = (artifact: Artifact) => {
setSelectedArtifact(artifact);
setSelectedVersion(artifact.version);
};
// Handle version change
const handleVersionChange = (version: number) => {
setSelectedVersion(version);
};
// Save artifact changes
const handleSaveArtifact = async (updates: any) => {
if (!selectedArtifact) return;
try {
const updated = await updateArtifact(selectedArtifact.id, {
content: updates,
createNewVersion: true,
versionMessage: 'Updated via Knowledge Viewer'
});
setSelectedArtifact(updated);
toast.success('Artifact saved successfully');
} catch (error) {
toast.error('Failed to save artifact');
console.error(error);
}
};
return (
<Stack sx={{ height: '100%' }}>
{/* Artifact selector */}
<Box sx={{ p: 2, borderBottom: 1, borderColor: 'divider' }}>
<Select
placeholder="Select an artifact..."
value={selectedArtifact?.id}
onChange={(_, value) => {
const artifact = artifacts?.find(a => a.id === value);
if (artifact) handleArtifactSelect(artifact);
}}
>
{artifacts?.map(artifact => (
<Option key={artifact.id} value={artifact.id}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
{getArtifactIcon(artifact.type)}
<Typography>{artifact.title}</Typography>
<Chip size="sm" variant="soft">
v{artifact.version}
</Chip>
</Box>
</Option>
))}
</Select>
</Box>
{/* Version selector */}
{selectedArtifact && (
<Box sx={{ p: 2, borderBottom: 1, borderColor: 'divider' }}>
<VersionDropdown
artifact={selectedArtifact}
currentVersion={selectedVersion || selectedArtifact.version}
onVersionChange={handleVersionChange}
showDiff
/>
</Box>
)}
{/* Content viewer */}
<Box sx={{ flex: 1, overflow: 'auto', p: 2 }}>
{selectedArtifact && selectedVersion && (
<ArtifactViewer
artifact={selectedArtifact}
version={selectedVersion}
onSave={handleSaveArtifact}
editable={selectedVersion === selectedArtifact.version}
/>
)}
</Box>
</Stack>
);
};
WebSocket Real-time Updates
// WebSocket event types for artifacts
export const ArtifactWebSocketEvents = {
// Artifact lifecycle
ARTIFACT_CREATED: 'artifact.created',
ARTIFACT_UPDATED: 'artifact.updated',
ARTIFACT_DELETED: 'artifact.deleted',
// Version management
VERSION_CREATED: 'artifact.version.created',
VERSION_RESTORED: 'artifact.version.restored',
// Collaboration
ARTIFACT_LOCKED: 'artifact.locked',
ARTIFACT_UNLOCKED: 'artifact.unlocked',
ARTIFACT_SHARED: 'artifact.shared',
// QuestMaster specific
QUEST_STARTED: 'artifact.quest.started',
QUEST_UPDATED: 'artifact.quest.updated',
QUEST_COMPLETED: 'artifact.quest.completed',
SUBQUEST_STARTED: 'artifact.subquest.started',
SUBQUEST_COMPLETED: 'artifact.subquest.completed'
};
// WebSocket message types
interface ArtifactCreatedMessage {
action: typeof ArtifactWebSocketEvents.ARTIFACT_CREATED;
artifact: Artifact;
userId: string;
}
interface QuestUpdatedMessage {
action: typeof ArtifactWebSocketEvents.QUEST_UPDATED;
artifactId: string;
questId: string;
updates: Partial<Quest>;
userId: string;
}
// Hook for real-time artifact updates
export const useArtifactSubscription = (artifactId: string) => {
const queryClient = useQueryClient();
const { subscribeToAction } = useWebsocket();
useEffect(() => {
const unsubscribes = [
// Subscribe to artifact updates
subscribeToAction(ArtifactWebSocketEvents.ARTIFACT_UPDATED, async (msg) => {
if (msg.artifact.id === artifactId) {
queryClient.setQueryData(['artifact', artifactId], msg.artifact);
}
}),
// Subscribe to version updates
subscribeToAction(ArtifactWebSocketEvents.VERSION_CREATED, async (msg) => {
if (msg.artifactId === artifactId) {
queryClient.invalidateQueries(['artifact-versions', artifactId]);
}
}),
// Subscribe to quest updates for QuestMaster artifacts
subscribeToAction(ArtifactWebSocketEvents.QUEST_UPDATED, async (msg) => {
if (msg.artifactId === artifactId) {
queryClient.setQueryData(
['artifact', artifactId],
(old: Artifact) => {
if (old.type !== ArtifactType.QUESTMASTER) return old;
const content = old.content as QuestMasterArtifact['content'];
const questIndex = content.quests.findIndex(q => q.id === msg.questId);
if (questIndex !== -1) {
content.quests[questIndex] = {
...content.quests[questIndex],
...msg.updates
};
}
return { ...old, content };
}
);
}
})
];
return () => {
unsubscribes.forEach(unsubscribe => unsubscribe());
};
}, [artifactId, subscribeToAction, queryClient]);
};
Migration Strategy
Phase 1: Foundation (Weeks 1-2)
- Implement base artifact types and database schema
- Create artifact service with basic CRUD operations
- Set up artifact storage and content management
- Implement version tracking system
Phase 2: API Layer (Weeks 3-4)
- Build RESTful API endpoints
- Implement search and filtering
- Add permission system
- Create WebSocket events for real-time updates
Phase 3: UI Components (Weeks 5-6)
- Build version dropdown component
- Create artifact preview cards
- Integrate with existing Knowledge Viewer
- Add artifact creation/editing UI
Phase 4: Migration & Testing (Weeks 7-8)
- Create migration scripts for existing artifacts
- Build backward compatibility layer
- Comprehensive testing
- Performance optimization
Phase 5: Advanced Features
- Implement artifact templates
- Add collaboration features
- Build artifact marketplace
- Create artifact analytics
Performance Considerations
- Content Storage: Store large content separately from metadata
- Caching: Implement multi-level caching (Redis + CDN)
- Search: Use Elasticsearch for full-text search
- Lazy Loading: Load artifact content on demand
- Compression: Compress large artifacts before storage
- CDN: Serve static artifacts from CDN
Security Considerations
- Access Control: Row-level security for all operations
- Content Validation: Validate and sanitize all artifact content
- Audit Logging: Log all artifact operations
- Encryption: Encrypt sensitive artifact content at rest
- Rate Limiting: Implement rate limits for API endpoints
- CORS: Proper CORS configuration for artifact sharing
Conclusion
This architecture provides a robust foundation for treating artifacts as first-class citizens with full CRUD operations, versioning, and comprehensive type support. The design emphasizes extensibility, performance, and user experience while maintaining backward compatibility with existing systems.