refactor(frontend): remove unused ActivityFeedPrototype code and documentation

- Deleted `ActivityFeedPrototype` component and associated `README.md`.
- Cleaned up related assets and mock data.
- This component was no longer in use and has been deprecated.
This commit is contained in:
2026-01-01 11:44:09 +01:00
parent 5c35702caf
commit f3fb4ecbeb
13 changed files with 101 additions and 6683 deletions

View File

@@ -1,248 +0,0 @@
# Real-time Activity Feed - Design Prototype
## Overview
The Activity Feed provides a real-time view of all events happening across Syndarix projects. It displays agent status changes, messages, issue updates, sprint events, approval requests, and errors. This is the central hub for monitoring autonomous agent activity and responding to approval requests.
## User Stories
- As a user, I want to see real-time updates from my projects so I stay informed
- As a user, I want to filter events by type, agent, or project to focus on what matters
- As a user, I want to quickly identify items requiring my action
- As a user, I want to expand events to see more details
- As a user, I want to approve or reject requests directly from the feed
- As a user, I want to know if I'm connected to the real-time stream
- As a user, I want to mark events as read to track what I've seen
## Key Screens
### Main Feed View
**Header**
- Page title with description
- Action required count badge
- Mark all read button
- Real-time connection indicator
**Search and Filters**
- Full-text search input
- Filter button (toggles filter panel)
- Refresh button
**Filter Panel** (expandable)
- Event type checkboxes (with icons)
- Agent checkboxes
- "Action required only" toggle
- Clear all / Apply buttons
**Event Groups**
- Grouped by time period: New, Earlier Today, Yesterday, This Week, Older
- Group header with count badge
- Separator line
**Event Cards**
- Event type icon (colored)
- Title with New/Action Required badges
- Description text
- Metadata row (agent, project, type)
- Expand/collapse button
- Expanded details (conditional)
- Action buttons (for approval requests)
## Event Types
| Type | Icon | Color | Description |
|------|------|-------|-------------|
| Agent Status | Bot | Blue | Agent started, paused, resumed, stopped |
| Agent Message | MessageSquare | Purple | Agent communication, updates, questions |
| Issue Update | CircleDot | Green | Status change, assignment, creation |
| Sprint Event | Zap | Yellow | Standup, retrospective, planning |
| Approval Request | AlertCircle | Orange | Requires user action |
| Error | XCircle | Red | Agent or system errors |
| Milestone | CheckCircle | Emerald | Goals achieved, completions |
## User Flow
### Monitoring Activity
1. User lands on feed page
2. Feed shows real-time updates (newest first)
3. User scrolls through events
4. User clicks event to expand details
5. User navigates to related issue/PR if needed
### Filtering Events
1. User clicks Filter button
2. Filter panel expands
3. User selects event types, agents, etc.
4. User clicks Apply
5. Feed updates to show matching events
### Handling Approval Request
1. Approval request appears with orange badge
2. User reads request details
3. User clicks Approve or Request Changes
4. Event updates to show completed
5. Badge count decreases
## Design Decisions
### Card-Based Layout
- Each event is a distinct card
- Visual separation between events
- Expandable for more details
- Cards are self-contained
### Time-Based Grouping
- Events grouped by time period
- Helps users orient in the timeline
- "New" group highlights unread items
- Clear visual hierarchy
### Real-Time Indicator
- Pulsing green dot when connected
- Gray dot when disconnected
- Builds trust in data freshness
- Matches common patterns (Slack, Discord)
### Action Required Emphasis
- Orange left border on cards
- Badge in header with count
- Cannot be missed
- Inline approval buttons
### Expandable Details
- Keeps feed scannable
- Details available on demand
- Consistent expand/collapse pattern
- Metadata shown when expanded
### Search and Filters
- Combined search and filter approach
- Quick search for specific events
- Structured filters for browsing
- Filter state indicator
## States
### Loading
- Skeleton cards while loading
- Connection indicator shows connecting
### Empty
- No events: "Activity will appear here" message
- No matches: "Try adjusting filters" message
### Disconnected
- Gray indicator
- Retry button available
- Last known data shown
### Action Completed
- Card updates to remove action badge
- Success toast notification
## Responsive Breakpoints
### Desktop (lg: 1024px+)
- Centered content (max-w-3xl)
- Full filter panel inline
- Comfortable spacing
### Tablet (md: 768px)
- Full-width content
- Filter panel still inline
- Slightly tighter spacing
### Mobile (< 768px)
- Full-width cards
- Filter panel stacks vertically
- Touch-friendly buttons
- Simplified metadata row
## Accessibility Notes
- Event type icons have text labels
- Color is not only indicator (badges, icons)
- Keyboard navigation for expand/collapse
- Screen reader announces new events
- Focus management on filter panel
- Live region for real-time updates
## Components Used
- Card, CardContent
- Button (default, outline, ghost variants)
- Badge (default, secondary, outline, destructive variants)
- Input (search)
- Checkbox
- Label
- Separator
- Lucide icons
## Event Data Structure
```typescript
interface ActivityEvent {
id: string;
type: 'agent_status' | 'agent_message' | 'issue_update' |
'sprint_event' | 'approval_request' | 'error' | 'milestone';
title: string;
description: string;
timestamp: string; // ISO 8601
agent: {
name: string;
avatar: string;
};
project: string;
metadata: {
issueNumber?: number;
pullRequest?: number;
documentUrl?: string;
progress?: number;
summary?: {
agents: number;
inProgress: number;
blocked: number;
};
importance?: 'low' | 'medium' | 'high';
// ... other type-specific fields
};
requiresAction: boolean;
isNew: boolean;
}
```
## Real-Time Implementation Notes
For production, the feed should:
1. Connect via WebSocket or SSE
2. Handle reconnection gracefully
3. Queue events during disconnect
4. Merge with existing events on reconnect
5. Support pagination for history
6. Implement optimistic updates for actions
## Questions for Review
1. Should events be grouped by time or show flat chronologically?
2. How many events should load initially? (Currently showing 10)
3. Should there be sound/browser notifications for urgent items?
4. Should users be able to "star" or save important events?
5. Should there be a compact view option?
6. Should events support threading/replies?
## How to View
Navigate to: `/prototypes/activity-feed`
Try the features:
1. Scroll through the event feed
2. Click events to expand details
3. Open the filter panel
4. Try the action buttons on approval requests
5. Use the search to filter events
## Next Steps
After approval:
1. Implement WebSocket connection for real-time updates
2. Add browser notification support
3. Implement event pagination (infinite scroll)
4. Add optimistic updates for approve/dismiss
5. Implement notification sound settings
6. Add project-level filtering
7. Implement event archiving
8. Add keyboard shortcuts (j/k navigation)

View File

@@ -1,830 +0,0 @@
'use client';
import { useState, useEffect } from 'react';
import { Card } from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Checkbox } from '@/components/ui/checkbox';
import { Separator } from '@/components/ui/separator';
import {
Activity,
Bot,
MessageSquare,
CheckCircle2,
AlertCircle,
GitPullRequest,
CircleDot,
XCircle,
Zap,
Settings,
Filter,
Bell,
BellOff,
RefreshCw,
ChevronDown,
ChevronUp,
ExternalLink,
Search,
} from 'lucide-react';
// Event type configurations
const eventTypeConfig = {
agent_status: {
label: 'Agent Status',
icon: Bot,
color: 'text-blue-500',
bgColor: 'bg-blue-100 dark:bg-blue-900',
},
agent_message: {
label: 'Agent Message',
icon: MessageSquare,
color: 'text-purple-500',
bgColor: 'bg-purple-100 dark:bg-purple-900',
},
issue_update: {
label: 'Issue Update',
icon: CircleDot,
color: 'text-green-500',
bgColor: 'bg-green-100 dark:bg-green-900',
},
sprint_event: {
label: 'Sprint Event',
icon: Zap,
color: 'text-yellow-500',
bgColor: 'bg-yellow-100 dark:bg-yellow-900',
},
approval_request: {
label: 'Approval Request',
icon: AlertCircle,
color: 'text-orange-500',
bgColor: 'bg-orange-100 dark:bg-orange-900',
},
error: {
label: 'Error',
icon: XCircle,
color: 'text-red-500',
bgColor: 'bg-red-100 dark:bg-red-900',
},
milestone: {
label: 'Milestone',
icon: CheckCircle2,
color: 'text-emerald-500',
bgColor: 'bg-emerald-100 dark:bg-emerald-900',
},
};
// Filter state type
type FilterState = {
types: string[];
agents: string[];
projects: string[];
showActionRequired: boolean;
};
// Mock activity events
const mockEvents = [
{
id: 'evt-001',
type: 'approval_request',
title: 'Approval Required: Architecture Decision',
description: 'Architect is requesting approval for the API design document for the checkout flow.',
timestamp: new Date(Date.now() - 1000 * 60 * 2).toISOString(), // 2 min ago
agent: { name: 'Architect', avatar: 'AR' },
project: 'E-Commerce Platform',
metadata: {
documentUrl: '/docs/adr-015-checkout-api.md',
importance: 'high',
},
requiresAction: true,
isNew: true,
},
{
id: 'evt-002',
type: 'agent_message',
title: 'Implementation Update',
description: 'Completed JWT token generation and validation. Moving on to session management.',
timestamp: new Date(Date.now() - 1000 * 60 * 8).toISOString(), // 8 min ago
agent: { name: 'Backend Engineer', avatar: 'BE' },
project: 'E-Commerce Platform',
metadata: {
issueNumber: 42,
progress: 65,
},
requiresAction: false,
isNew: true,
},
{
id: 'evt-003',
type: 'agent_status',
title: 'Agent Started Work',
description: 'Frontend Engineer has started working on the product catalog component.',
timestamp: new Date(Date.now() - 1000 * 60 * 15).toISOString(), // 15 min ago
agent: { name: 'Frontend Engineer', avatar: 'FE' },
project: 'E-Commerce Platform',
metadata: {
previousStatus: 'idle',
newStatus: 'active',
issueNumber: 45,
},
requiresAction: false,
isNew: true,
},
{
id: 'evt-004',
type: 'issue_update',
title: 'Issue Status Changed',
description: 'Issue #38 "Implement user registration" moved from "In Progress" to "In Review".',
timestamp: new Date(Date.now() - 1000 * 60 * 25).toISOString(), // 25 min ago
agent: { name: 'Backend Engineer', avatar: 'BE' },
project: 'E-Commerce Platform',
metadata: {
issueNumber: 38,
oldStatus: 'in_progress',
newStatus: 'in_review',
},
requiresAction: false,
isNew: false,
},
{
id: 'evt-005',
type: 'sprint_event',
title: 'Daily Standup Completed',
description: 'Sprint 3 daily standup: 4 agents reported, 8 issues in progress, 1 blocked.',
timestamp: new Date(Date.now() - 1000 * 60 * 60).toISOString(), // 1 hour ago
agent: { name: 'System', avatar: 'SY' },
project: 'E-Commerce Platform',
metadata: {
sprintName: 'Sprint 3',
summary: {
agents: 4,
inProgress: 8,
blocked: 1,
},
},
requiresAction: false,
isNew: false,
},
{
id: 'evt-006',
type: 'error',
title: 'Agent Error',
description: 'QA Engineer encountered an error while setting up the test framework. Retry attempted.',
timestamp: new Date(Date.now() - 1000 * 60 * 90).toISOString(), // 1.5 hours ago
agent: { name: 'QA Engineer', avatar: 'QA' },
project: 'E-Commerce Platform',
metadata: {
errorType: 'configuration',
retryCount: 1,
resolved: false,
},
requiresAction: true,
isNew: false,
},
{
id: 'evt-007',
type: 'milestone',
title: 'Sprint Goal Achieved',
description: 'Authentication module completed! All acceptance criteria met.',
timestamp: new Date(Date.now() - 1000 * 60 * 120).toISOString(), // 2 hours ago
agent: { name: 'Product Owner', avatar: 'PO' },
project: 'E-Commerce Platform',
metadata: {
milestone: 'Auth Module',
issuesCompleted: 5,
},
requiresAction: false,
isNew: false,
},
{
id: 'evt-008',
type: 'agent_status',
title: 'Agent Paused',
description: 'DevOps Engineer paused pending infrastructure approval.',
timestamp: new Date(Date.now() - 1000 * 60 * 180).toISOString(), // 3 hours ago
agent: { name: 'DevOps Engineer', avatar: 'DO' },
project: 'E-Commerce Platform',
metadata: {
previousStatus: 'active',
newStatus: 'paused',
reason: 'Awaiting approval',
},
requiresAction: false,
isNew: false,
},
{
id: 'evt-009',
type: 'agent_message',
title: 'Code Review Comment',
description: 'Found potential security issue in password hashing. Recommending bcrypt with higher rounds.',
timestamp: new Date(Date.now() - 1000 * 60 * 240).toISOString(), // 4 hours ago
agent: { name: 'Architect', avatar: 'AR' },
project: 'E-Commerce Platform',
metadata: {
issueNumber: 42,
pullRequest: 15,
},
requiresAction: false,
isNew: false,
},
{
id: 'evt-010',
type: 'issue_update',
title: 'Issue Created',
description: 'New issue created: "Add rate limiting to API endpoints" - assigned to Backend Engineer.',
timestamp: new Date(Date.now() - 1000 * 60 * 300).toISOString(), // 5 hours ago
agent: { name: 'Product Owner', avatar: 'PO' },
project: 'E-Commerce Platform',
metadata: {
issueNumber: 50,
priority: 'medium',
},
requiresAction: false,
isNew: false,
},
];
// Format relative time
function formatRelativeTime(timestamp: string): string {
const now = new Date();
const then = new Date(timestamp);
const diffMs = now.getTime() - then.getTime();
const diffMins = Math.floor(diffMs / (1000 * 60));
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
if (diffMins < 1) return 'Just now';
if (diffMins < 60) return `${diffMins} min ago`;
if (diffHours < 24) return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`;
return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`;
}
// Group events by time period
function groupEventsByPeriod(events: typeof mockEvents) {
const now = new Date();
const groups: { label: string; events: typeof mockEvents }[] = [
{ label: 'New', events: [] },
{ label: 'Earlier Today', events: [] },
{ label: 'Yesterday', events: [] },
{ label: 'This Week', events: [] },
{ label: 'Older', events: [] },
];
events.forEach((event) => {
const eventDate = new Date(event.timestamp);
const diffHours = (now.getTime() - eventDate.getTime()) / (1000 * 60 * 60);
const diffDays = diffHours / 24;
if (event.isNew) {
groups[0].events.push(event);
} else if (diffHours < 24 && eventDate.getDate() === now.getDate()) {
groups[1].events.push(event);
} else if (diffDays < 2) {
groups[2].events.push(event);
} else if (diffDays < 7) {
groups[3].events.push(event);
} else {
groups[4].events.push(event);
}
});
return groups.filter((g) => g.events.length > 0);
}
// Event card component
function EventCard({
event,
expanded,
onToggle,
onApprove,
onDismiss,
}: {
event: (typeof mockEvents)[0];
expanded: boolean;
onToggle: () => void;
onApprove: () => void;
onDismiss: () => void;
}) {
const config = eventTypeConfig[event.type as keyof typeof eventTypeConfig];
const Icon = config.icon;
return (
<Card
className={`transition-all ${
event.isNew ? 'border-primary/50 shadow-md' : ''
} ${event.requiresAction ? 'border-l-4 border-l-orange-500' : ''}`}
>
<div className="p-4">
<div className="flex gap-4">
{/* Icon */}
<div
className={`flex h-10 w-10 shrink-0 items-center justify-center rounded-full ${config.bgColor}`}
>
<Icon className={`h-5 w-5 ${config.color}`} />
</div>
{/* Content */}
<div className="min-w-0 flex-1">
<div className="flex items-start justify-between gap-2">
<div>
<div className="flex flex-wrap items-center gap-2">
<h3 className="font-semibold">{event.title}</h3>
{event.isNew && (
<Badge variant="default" className="text-xs">
New
</Badge>
)}
{event.requiresAction && (
<Badge
variant="outline"
className="border-orange-500 text-orange-500 text-xs"
>
Action Required
</Badge>
)}
</div>
<p className="mt-1 text-sm text-muted-foreground">
{event.description}
</p>
</div>
<div className="flex items-center gap-2 text-xs text-muted-foreground shrink-0">
<span>{formatRelativeTime(event.timestamp)}</span>
<Button
variant="ghost"
size="icon"
className="h-6 w-6"
onClick={onToggle}
>
{expanded ? (
<ChevronUp className="h-4 w-4" />
) : (
<ChevronDown className="h-4 w-4" />
)}
</Button>
</div>
</div>
{/* Metadata row */}
<div className="mt-2 flex flex-wrap items-center gap-3 text-xs text-muted-foreground">
<div className="flex items-center gap-1">
<Bot className="h-3 w-3" />
<span>{event.agent.name}</span>
</div>
<div className="flex items-center gap-1">
<Activity className="h-3 w-3" />
<span>{event.project}</span>
</div>
<Badge variant="secondary" className="text-xs">
{config.label}
</Badge>
</div>
{/* Expanded details */}
{expanded && (
<div className="mt-4 rounded-lg bg-muted/50 p-3 space-y-3">
{event.metadata.issueNumber && (
<div className="flex items-center gap-2 text-sm">
<CircleDot className="h-4 w-4" />
<span>Issue #{event.metadata.issueNumber}</span>
<Button variant="link" size="sm" className="h-auto p-0">
View Issue
</Button>
</div>
)}
{event.metadata.pullRequest && (
<div className="flex items-center gap-2 text-sm">
<GitPullRequest className="h-4 w-4" />
<span>PR #{event.metadata.pullRequest}</span>
<Button variant="link" size="sm" className="h-auto p-0">
View PR
</Button>
</div>
)}
{event.metadata.documentUrl && (
<div className="flex items-center gap-2 text-sm">
<ExternalLink className="h-4 w-4" />
<a
href={event.metadata.documentUrl}
className="text-primary hover:underline"
>
{event.metadata.documentUrl}
</a>
</div>
)}
{event.metadata.summary && (
<div className="grid grid-cols-3 gap-4 text-sm">
<div className="text-center">
<p className="text-2xl font-bold text-primary">
{event.metadata.summary.agents}
</p>
<p className="text-muted-foreground">Agents</p>
</div>
<div className="text-center">
<p className="text-2xl font-bold text-blue-500">
{event.metadata.summary.inProgress}
</p>
<p className="text-muted-foreground">In Progress</p>
</div>
<div className="text-center">
<p className="text-2xl font-bold text-red-500">
{event.metadata.summary.blocked}
</p>
<p className="text-muted-foreground">Blocked</p>
</div>
</div>
)}
{event.metadata.progress !== undefined && (
<div className="space-y-1">
<div className="flex justify-between text-sm">
<span>Progress</span>
<span>{event.metadata.progress}%</span>
</div>
<div className="h-2 rounded-full bg-muted">
<div
className="h-full rounded-full bg-primary"
style={{ width: `${event.metadata.progress}%` }}
/>
</div>
</div>
)}
<p className="text-xs text-muted-foreground">
{new Date(event.timestamp).toLocaleString()}
</p>
</div>
)}
{/* Action buttons for approval requests */}
{event.requiresAction && (
<div className="mt-3 flex gap-2">
<Button size="sm" onClick={onApprove}>
<CheckCircle2 className="mr-2 h-4 w-4" />
Approve
</Button>
<Button variant="outline" size="sm" onClick={onDismiss}>
Request Changes
</Button>
<Button variant="ghost" size="sm">
View Details
</Button>
</div>
)}
</div>
</div>
</div>
</Card>
);
}
// Filter panel component
function FilterPanel({
filters,
onFiltersChange,
onClose,
}: {
filters: FilterState;
onFiltersChange: (filters: FilterState) => void;
onClose: () => void;
}) {
const eventTypes = Object.entries(eventTypeConfig);
const agents = ['Backend Engineer', 'Frontend Engineer', 'Architect', 'Product Owner', 'QA Engineer', 'DevOps Engineer'];
const _projects = ['E-Commerce Platform', 'Mobile App', 'API Gateway'];
const toggleType = (type: string) => {
const newTypes = filters.types.includes(type)
? filters.types.filter((t) => t !== type)
: [...filters.types, type];
onFiltersChange({ ...filters, types: newTypes });
};
const toggleAgent = (agent: string) => {
const newAgents = filters.agents.includes(agent)
? filters.agents.filter((a) => a !== agent)
: [...filters.agents, agent];
onFiltersChange({ ...filters, agents: newAgents });
};
return (
<Card className="p-4">
<div className="space-y-6">
{/* Event Types */}
<div>
<Label className="text-sm font-medium">Event Types</Label>
<div className="mt-2 grid grid-cols-2 gap-2">
{eventTypes.map(([key, config]) => {
const Icon = config.icon;
return (
<div key={key} className="flex items-center gap-2">
<Checkbox
id={`type-${key}`}
checked={filters.types.length === 0 || filters.types.includes(key)}
onCheckedChange={() => toggleType(key)}
/>
<Label
htmlFor={`type-${key}`}
className="flex items-center gap-1 text-sm font-normal cursor-pointer"
>
<Icon className={`h-3 w-3 ${config.color}`} />
{config.label}
</Label>
</div>
);
})}
</div>
</div>
{/* Agents */}
<div>
<Label className="text-sm font-medium">Agents</Label>
<div className="mt-2 grid grid-cols-2 gap-2">
{agents.map((agent) => (
<div key={agent} className="flex items-center gap-2">
<Checkbox
id={`agent-${agent}`}
checked={filters.agents.length === 0 || filters.agents.includes(agent)}
onCheckedChange={() => toggleAgent(agent)}
/>
<Label
htmlFor={`agent-${agent}`}
className="text-sm font-normal cursor-pointer"
>
{agent}
</Label>
</div>
))}
</div>
</div>
{/* Show action required only */}
<div className="flex items-center gap-2">
<Checkbox
id="action-required"
checked={filters.showActionRequired}
onCheckedChange={(checked) =>
onFiltersChange({ ...filters, showActionRequired: checked as boolean })
}
/>
<Label htmlFor="action-required" className="text-sm font-normal cursor-pointer">
Show only items requiring action
</Label>
</div>
{/* Actions */}
<div className="flex justify-between">
<Button
variant="ghost"
size="sm"
onClick={() =>
onFiltersChange({
types: [],
agents: [],
projects: [],
showActionRequired: false,
})
}
>
Clear All
</Button>
<Button size="sm" onClick={onClose}>
Apply Filters
</Button>
</div>
</div>
</Card>
);
}
// Real-time indicator
function RealTimeIndicator({ connected }: { connected: boolean }) {
return (
<div className="flex items-center gap-2">
<span
className={`h-2 w-2 rounded-full ${
connected ? 'bg-green-500 animate-pulse' : 'bg-gray-400'
}`}
/>
<span className="text-sm text-muted-foreground">
{connected ? 'Live' : 'Disconnected'}
</span>
</div>
);
}
export default function ActivityFeedPrototype() {
const [events, setEvents] = useState(mockEvents);
const [expandedEvents, setExpandedEvents] = useState<string[]>([]);
const [showFilters, setShowFilters] = useState(false);
const [filters, setFilters] = useState({
types: [] as string[],
agents: [] as string[],
projects: [] as string[],
showActionRequired: false,
});
const [searchQuery, setSearchQuery] = useState('');
const [isConnected, setIsConnected] = useState(true);
const [notificationsEnabled, setNotificationsEnabled] = useState(true);
// Simulate real-time updates
useEffect(() => {
const interval = setInterval(() => {
// Randomly toggle connection status for demo
if (Math.random() > 0.95) {
setIsConnected((prev) => !prev);
}
}, 5000);
return () => clearInterval(interval);
}, []);
// Filter events
const filteredEvents = events.filter((event) => {
const matchesSearch =
searchQuery === '' ||
event.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
event.description.toLowerCase().includes(searchQuery.toLowerCase());
const matchesType =
filters.types.length === 0 || filters.types.includes(event.type);
const matchesAgent =
filters.agents.length === 0 || filters.agents.includes(event.agent.name);
const matchesActionRequired =
!filters.showActionRequired || event.requiresAction;
return matchesSearch && matchesType && matchesAgent && matchesActionRequired;
});
const groupedEvents = groupEventsByPeriod(filteredEvents);
const toggleExpanded = (eventId: string) => {
setExpandedEvents((prev) =>
prev.includes(eventId)
? prev.filter((id) => id !== eventId)
: [...prev, eventId]
);
};
const handleApprove = (eventId: string) => {
setEvents((prev) =>
prev.map((e) =>
e.id === eventId ? { ...e, requiresAction: false, isNew: false } : e
)
);
};
const handleDismiss = (eventId: string) => {
// In real implementation, this would open a modal for feedback
console.log('Dismiss:', eventId);
};
const markAllRead = () => {
setEvents((prev) => prev.map((e) => ({ ...e, isNew: false })));
};
const actionRequiredCount = events.filter((e) => e.requiresAction).length;
const newCount = events.filter((e) => e.isNew).length;
return (
<div className="min-h-screen bg-background">
{/* Navigation Bar */}
<nav className="border-b bg-card">
<div className="container mx-auto flex h-14 items-center justify-between px-4">
<div className="flex items-center gap-4">
<span className="font-semibold text-primary">Syndarix</span>
<Separator orientation="vertical" className="h-6" />
<span className="text-sm font-medium">Activity Feed</span>
</div>
<div className="flex items-center gap-2">
<RealTimeIndicator connected={isConnected} />
<Button
variant="ghost"
size="icon"
onClick={() => setNotificationsEnabled(!notificationsEnabled)}
>
{notificationsEnabled ? (
<Bell className="h-4 w-4" />
) : (
<BellOff className="h-4 w-4" />
)}
</Button>
<Button variant="ghost" size="icon">
<Settings className="h-4 w-4" />
</Button>
</div>
</div>
</nav>
<div className="container mx-auto px-4 py-6">
<div className="mx-auto max-w-3xl space-y-6">
{/* Header */}
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div>
<h1 className="text-3xl font-bold">Activity Feed</h1>
<p className="text-muted-foreground">
Real-time updates from your projects
</p>
</div>
<div className="flex gap-2">
{actionRequiredCount > 0 && (
<Badge variant="destructive" className="gap-1">
<AlertCircle className="h-3 w-3" />
{actionRequiredCount} pending
</Badge>
)}
{newCount > 0 && (
<Button variant="outline" size="sm" onClick={markAllRead}>
Mark all read ({newCount})
</Button>
)}
</div>
</div>
{/* Search and Filters */}
<div className="flex flex-col gap-4 sm:flex-row">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<Input
placeholder="Search activity..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-9"
/>
</div>
<Button
variant="outline"
onClick={() => setShowFilters(!showFilters)}
className={showFilters ? 'bg-muted' : ''}
>
<Filter className="mr-2 h-4 w-4" />
Filters
{(filters.types.length > 0 ||
filters.agents.length > 0 ||
filters.showActionRequired) && (
<Badge variant="secondary" className="ml-2">
Active
</Badge>
)}
</Button>
<Button variant="ghost" size="icon" onClick={() => setIsConnected(true)}>
<RefreshCw className={`h-4 w-4 ${!isConnected ? 'animate-spin' : ''}`} />
</Button>
</div>
{/* Filter Panel */}
{showFilters && (
<FilterPanel
filters={filters}
onFiltersChange={setFilters}
onClose={() => setShowFilters(false)}
/>
)}
{/* Event Groups */}
<div className="space-y-6">
{groupedEvents.map((group) => (
<div key={group.label}>
<div className="mb-3 flex items-center gap-2">
<h2 className="text-sm font-medium text-muted-foreground">
{group.label}
</h2>
<Badge variant="secondary" className="text-xs">
{group.events.length}
</Badge>
<Separator className="flex-1" />
</div>
<div className="space-y-3">
{group.events.map((event) => (
<EventCard
key={event.id}
event={event}
expanded={expandedEvents.includes(event.id)}
onToggle={() => toggleExpanded(event.id)}
onApprove={() => handleApprove(event.id)}
onDismiss={() => handleDismiss(event.id)}
/>
))}
</div>
</div>
))}
</div>
{filteredEvents.length === 0 && (
<Card className="py-12 text-center">
<Activity className="mx-auto h-12 w-12 text-muted-foreground" />
<h3 className="mt-4 font-semibold">No activity found</h3>
<p className="text-muted-foreground">
{searchQuery || filters.types.length > 0
? 'Try adjusting your search or filters'
: 'Activity will appear here as agents work on your projects'}
</p>
</Card>
)}
{/* Load More */}
{filteredEvents.length > 0 && (
<div className="text-center">
<Button variant="outline">Load More Activity</Button>
</div>
)}
</div>
</div>
</div>
);
}

View File

@@ -1,213 +0,0 @@
# Agent Configuration UI - Design Prototype
## Overview
The Agent Configuration UI allows users to create, view, and manage Agent Types - the templates from which agent instances are spawned. This is a critical administrative interface for defining how AI agents behave, what models they use, and what permissions they have.
## User Stories
- As an admin, I want to create new agent types so I can define specialized AI agents for my projects
- As an admin, I want to configure model selection and parameters to optimize agent performance
- As an admin, I want to define MCP permissions to control what tools agents can access
- As an admin, I want to craft personality prompts to shape agent behavior and communication style
- As an admin, I want to view all agent types and their status at a glance
- As an admin, I want to duplicate existing agent types as starting points for new ones
## Key Screens
### 1. Agent Type List View
- Grid layout of agent type cards
- Search functionality
- Status filter (Active, Draft, Inactive)
- Create new agent type button
- Card shows: name, description, expertise tags, model, instance count, status
### 2. Agent Type Detail View
- Full configuration display (read-only)
- Description and expertise areas
- Model configuration (primary, failover, parameters)
- MCP permissions with scopes
- Personality prompt display
- Instance count and quick actions
- Duplicate, Edit, Delete buttons
- Danger zone for destructive actions
### 3. Agent Type Editor View
- Tabbed interface for organized editing:
- **Basic Info**: Name, description, status, expertise areas
- **Model**: Primary/failover model selection, temperature, max tokens, top P
- **Permissions**: MCP server toggles with granular scope checkboxes
- **Personality**: Large textarea for personality prompt with character count
## User Flow
### Creating a New Agent Type
1. User clicks "Create Agent Type" from list view
2. Editor opens with blank form
3. User fills in basic info (name, description, expertise)
4. User selects model configuration
5. User enables MCP permissions and selects scopes
6. User writes personality prompt
7. User saves (creates as Draft or Active)
### Editing an Existing Agent Type
1. User clicks agent type card in list
2. Detail view shows full configuration
3. User clicks "Edit" button
4. Editor opens with pre-filled data
5. User modifies settings across tabs
6. User saves changes
### Duplicating an Agent Type
1. User opens detail view
2. User clicks "Duplicate"
3. Editor opens with copied data and new name
4. User modifies as needed
5. User saves as new agent type
## Design Decisions
### Three-View Architecture
- **List**: Overview and discovery
- **Detail**: Read-only inspection
- **Editor**: Focused editing experience
- Clear navigation between views with back button
### Tabbed Editor
- Logical grouping of related settings
- Reduces cognitive load
- Allows focused work on one aspect at a time
- Icons on tabs for quick recognition
### Card-Based List
- Visual grid layout for quick scanning
- Status badges for instant status recognition
- Expertise tags show capabilities at a glance
- Instance count indicates usage
### MCP Permissions UI
- Checkbox for enabling/disabling each server
- Nested scopes only shown when server is enabled
- Clear visual distinction between enabled/disabled
- Granular control over what agents can access
### Model Configuration
- Primary + failover model selection
- Common parameters exposed (temperature, max tokens, top P)
- Helpful descriptions for each parameter
- Sensible defaults pre-filled
## States
### Loading
- Skeleton cards in list view
- Skeleton placeholders in detail/editor views
### Empty
- No agent types: "Create your first agent type" CTA
- No search results: "No agent types found" with suggestion
### Error
- Failed to load: Error card with retry button
- Failed to save: Toast notification with error details
### Validation
- Required fields highlighted
- Character limits enforced
- Model parameter ranges validated
## Responsive Breakpoints
### Desktop (lg: 1024px+)
- 3-column card grid in list view
- 2/3 + 1/3 split in detail view
- Full tab navigation visible
### Tablet (md: 768px)
- 2-column card grid
- Stacked layout in detail view
- Compact tab labels
### Mobile (< 768px)
- Single column card list
- Full-width cards
- Scrollable tab navigation
- Stacked form fields
## Accessibility Notes
- Form labels properly associated with inputs
- Tab panel navigation is keyboard accessible
- Status badges have text (not just color)
- Focus management when navigating between views
- Screen reader announces view changes
- Textarea has proper aria-labels
## Components Used
- Card, CardHeader, CardTitle, CardContent, CardDescription
- Button (default, outline, ghost, destructive variants)
- Badge (default, secondary, outline variants)
- Input, Textarea, Label
- Select, SelectContent, SelectItem, SelectTrigger, SelectValue
- Tabs, TabsList, TabsTrigger, TabsContent
- Checkbox
- Separator
- Lucide icons
## Form Fields Reference
### Basic Info
| Field | Type | Required | Validation |
|-------|------|----------|------------|
| Name | Text | Yes | 3-50 characters |
| Status | Select | Yes | active/draft/inactive |
| Description | Textarea | Yes | 10-500 characters |
| Expertise | Tags | No | Comma-separated |
### Model Configuration
| Field | Type | Required | Validation |
|-------|------|----------|------------|
| Primary Model | Select | Yes | From available models |
| Failover Model | Select | No | From available models |
| Temperature | Number | Yes | 0.0 - 2.0 |
| Max Tokens | Number | Yes | 1024 - 32768 |
| Top P | Number | Yes | 0.0 - 1.0 |
### MCP Permissions
| Server | Scopes |
|--------|--------|
| Gitea | read, write, issues, branches, prs |
| Knowledge Base | read, write |
| Filesystem | read, write |
| Slack | read, write |
| Jira | read, write, issues |
### Personality Prompt
- Large textarea (15 rows)
- Character count display
- Template insertion button
- Preview functionality
## Questions for Review
1. Should we add a "Test Agent" feature to try the personality prompt?
2. Is the tabbed editor the right approach or should all fields be on one page?
3. Should expertise be free-form tags or a predefined list?
4. Should model parameters have "presets" (conservative, balanced, creative)?
5. How should we handle versioning of agent types?
6. Should there be an approval workflow for activating agent types?
## How to View
Navigate to: `/prototypes/agent-configuration`
Click through the views:
1. Start on list view
2. Click any card to see detail view
3. Click "Edit" to see editor view
4. Click "Create Agent Type" to see editor in create mode
## Next Steps
After approval:
1. Implement form validation with react-hook-form and zod
2. Connect to agent type CRUD API
3. Add real model list from API
4. Implement tag input component for expertise
5. Add personality prompt templates
6. Implement MCP permission verification
7. Add loading skeletons and error states

View File

@@ -1,939 +0,0 @@
'use client';
import { useState } from 'react';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Textarea } from '@/components/ui/textarea';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Separator } from '@/components/ui/separator';
import { Checkbox } from '@/components/ui/checkbox';
import {
Bot,
Plus,
Search,
Settings,
ChevronRight,
ArrowLeft,
Copy,
Trash2,
Save,
Eye,
Edit,
Sliders,
Shield,
MessageSquare,
Cpu,
Zap,
Code,
FileText,
CheckCircle2,
AlertTriangle,
} from 'lucide-react';
// Mock data for agent types
const mockAgentTypes = [
{
id: 'type-001',
name: 'Product Owner',
description: 'Manages product backlog, defines user stories, and prioritizes features',
expertise: ['Requirements', 'User Stories', 'Prioritization', 'Stakeholder Management'],
model: 'claude-opus-4-5-20251101',
status: 'active',
instanceCount: 3,
lastModified: '2025-01-20',
},
{
id: 'type-002',
name: 'Software Architect',
description: 'Designs system architecture, makes technology decisions, and ensures scalability',
expertise: ['System Design', 'API Design', 'Database Architecture', 'Security'],
model: 'claude-opus-4-5-20251101',
status: 'active',
instanceCount: 2,
lastModified: '2025-01-18',
},
{
id: 'type-003',
name: 'Backend Engineer',
description: 'Implements server-side logic, APIs, and database interactions',
expertise: ['Python', 'FastAPI', 'PostgreSQL', 'Redis', 'Testing'],
model: 'claude-sonnet-4-20250514',
status: 'active',
instanceCount: 5,
lastModified: '2025-01-22',
},
{
id: 'type-004',
name: 'Frontend Engineer',
description: 'Builds user interfaces using modern React and Next.js',
expertise: ['React', 'Next.js', 'TypeScript', 'Tailwind CSS', 'Testing'],
model: 'claude-sonnet-4-20250514',
status: 'active',
instanceCount: 4,
lastModified: '2025-01-21',
},
{
id: 'type-005',
name: 'QA Engineer',
description: 'Creates test plans, writes automated tests, and ensures quality',
expertise: ['Test Planning', 'E2E Testing', 'Unit Testing', 'Bug Reporting'],
model: 'claude-sonnet-4-20250514',
status: 'draft',
instanceCount: 0,
lastModified: '2025-01-19',
},
{
id: 'type-006',
name: 'DevOps Engineer',
description: 'Manages infrastructure, CI/CD pipelines, and deployment processes',
expertise: ['Docker', 'Kubernetes', 'CI/CD', 'Monitoring', 'Security'],
model: 'claude-sonnet-4-20250514',
status: 'inactive',
instanceCount: 1,
lastModified: '2025-01-15',
},
];
// Full agent type detail for editor
const mockAgentTypeDetail = {
id: 'type-002',
name: 'Software Architect',
description: 'Designs system architecture, makes technology decisions, and ensures scalability. Works closely with the Product Owner to understand requirements and with Engineers to implement solutions.',
expertise: ['System Design', 'API Design', 'Database Architecture', 'Security', 'Scalability'],
model: {
primary: 'claude-opus-4-5-20251101',
failover: 'claude-sonnet-4-20250514',
},
parameters: {
temperature: 0.7,
maxTokens: 8192,
topP: 0.95,
},
mcpPermissions: [
{ id: 'gitea', name: 'Gitea', enabled: true, scopes: ['read', 'write', 'issues'] },
{ id: 'knowledge', name: 'Knowledge Base', enabled: true, scopes: ['read', 'write'] },
{ id: 'filesystem', name: 'Filesystem', enabled: true, scopes: ['read', 'write'] },
{ id: 'slack', name: 'Slack', enabled: false, scopes: [] },
{ id: 'jira', name: 'Jira', enabled: false, scopes: [] },
],
personalityPrompt: `You are a Senior Software Architect with 15+ years of experience designing scalable, maintainable systems. Your approach is:
1. **Pragmatic**: You favor proven solutions over cutting-edge unless there's a clear benefit
2. **Security-minded**: Security is a first-class concern, not an afterthought
3. **Documentation-focused**: You believe in architecture decision records (ADRs)
4. **Collaborative**: You seek input from engineers and stakeholders before making major decisions
When designing systems:
- Start with requirements and constraints
- Consider scalability, maintainability, and operational concerns
- Document decisions and their rationale
- Provide clear guidance for implementation teams
Your communication style is clear, structured, and respectful. You explain complex concepts simply and always justify your recommendations.`,
status: 'active',
instanceCount: 2,
createdAt: '2025-01-10',
lastModified: '2025-01-18',
};
// View states
type ViewState = 'list' | 'detail' | 'editor';
// Status badge for agent types
function AgentTypeStatusBadge({ status }: { status: string }) {
const variants: Record<string, { label: string; className: string }> = {
active: {
label: 'Active',
className: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
},
draft: {
label: 'Draft',
className: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200',
},
inactive: {
label: 'Inactive',
className: 'bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200',
},
};
const variant = variants[status] || variants.inactive;
return (
<Badge className={variant.className} variant="outline">
{variant.label}
</Badge>
);
}
// Agent Type List View
function AgentTypeListView({
onSelect,
onCreate,
}: {
onSelect: (id: string) => void;
onCreate: () => void;
}) {
const [searchQuery, setSearchQuery] = useState('');
const [statusFilter, setStatusFilter] = useState('all');
const filteredTypes = mockAgentTypes.filter((type) => {
const matchesSearch =
type.name.toLowerCase().includes(searchQuery.toLowerCase()) ||
type.description.toLowerCase().includes(searchQuery.toLowerCase());
const matchesStatus = statusFilter === 'all' || type.status === statusFilter;
return matchesSearch && matchesStatus;
});
return (
<div className="space-y-6">
{/* Header */}
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div>
<h1 className="text-3xl font-bold">Agent Types</h1>
<p className="text-muted-foreground">
Configure templates for spawning AI agent instances
</p>
</div>
<Button onClick={onCreate}>
<Plus className="mr-2 h-4 w-4" />
Create Agent Type
</Button>
</div>
{/* Filters */}
<div className="flex flex-col gap-4 sm:flex-row">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<Input
placeholder="Search agent types..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-9"
/>
</div>
<Select value={statusFilter} onValueChange={setStatusFilter}>
<SelectTrigger className="w-full sm:w-40">
<SelectValue placeholder="Status" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Status</SelectItem>
<SelectItem value="active">Active</SelectItem>
<SelectItem value="draft">Draft</SelectItem>
<SelectItem value="inactive">Inactive</SelectItem>
</SelectContent>
</Select>
</div>
{/* Agent Type Grid */}
<div className="grid gap-4 md:grid-cols-2 lg:grid-cols-3">
{filteredTypes.map((type) => (
<Card
key={type.id}
className="cursor-pointer transition-all hover:border-primary hover:shadow-md"
onClick={() => onSelect(type.id)}
>
<CardHeader className="pb-3">
<div className="flex items-start justify-between">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10">
<Bot className="h-5 w-5 text-primary" />
</div>
<AgentTypeStatusBadge status={type.status} />
</div>
<CardTitle className="mt-3">{type.name}</CardTitle>
<CardDescription className="line-clamp-2">{type.description}</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-3">
{/* Expertise tags */}
<div className="flex flex-wrap gap-1">
{type.expertise.slice(0, 3).map((skill) => (
<Badge key={skill} variant="secondary" className="text-xs">
{skill}
</Badge>
))}
{type.expertise.length > 3 && (
<Badge variant="outline" className="text-xs">
+{type.expertise.length - 3}
</Badge>
)}
</div>
<Separator />
{/* Metadata */}
<div className="flex items-center justify-between text-sm text-muted-foreground">
<div className="flex items-center gap-1">
<Cpu className="h-3.5 w-3.5" />
<span className="text-xs">{type.model.split('-').slice(1, 2).join(' ')}</span>
</div>
<div className="flex items-center gap-1">
<Bot className="h-3.5 w-3.5" />
<span className="text-xs">{type.instanceCount} instances</span>
</div>
</div>
</div>
</CardContent>
</Card>
))}
</div>
{filteredTypes.length === 0 && (
<div className="py-12 text-center">
<Bot className="mx-auto h-12 w-12 text-muted-foreground" />
<h3 className="mt-4 font-semibold">No agent types found</h3>
<p className="text-muted-foreground">Try adjusting your search or filters</p>
</div>
)}
</div>
);
}
// Agent Type Detail View
function AgentTypeDetailView({
onBack,
onEdit,
}: {
onBack: () => void;
onEdit: () => void;
}) {
const type = mockAgentTypeDetail;
return (
<div className="space-y-6">
{/* Header */}
<div className="flex items-center gap-4">
<Button variant="ghost" size="icon" onClick={onBack}>
<ArrowLeft className="h-4 w-4" />
</Button>
<div className="flex-1">
<div className="flex items-center gap-3">
<h1 className="text-3xl font-bold">{type.name}</h1>
<AgentTypeStatusBadge status={type.status} />
</div>
<p className="text-muted-foreground">Last modified: {type.lastModified}</p>
</div>
<div className="flex gap-2">
<Button variant="outline" size="sm">
<Copy className="mr-2 h-4 w-4" />
Duplicate
</Button>
<Button size="sm" onClick={onEdit}>
<Edit className="mr-2 h-4 w-4" />
Edit
</Button>
</div>
</div>
<div className="grid gap-6 lg:grid-cols-3">
{/* Main Content */}
<div className="space-y-6 lg:col-span-2">
{/* Description Card */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<FileText className="h-5 w-5" />
Description
</CardTitle>
</CardHeader>
<CardContent>
<p className="text-muted-foreground">{type.description}</p>
</CardContent>
</Card>
{/* Expertise Card */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Zap className="h-5 w-5" />
Expertise Areas
</CardTitle>
</CardHeader>
<CardContent>
<div className="flex flex-wrap gap-2">
{type.expertise.map((skill) => (
<Badge key={skill} variant="secondary">
{skill}
</Badge>
))}
</div>
</CardContent>
</Card>
{/* Personality Prompt Card */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<MessageSquare className="h-5 w-5" />
Personality Prompt
</CardTitle>
</CardHeader>
<CardContent>
<pre className="whitespace-pre-wrap rounded-lg bg-muted p-4 text-sm">
{type.personalityPrompt}
</pre>
</CardContent>
</Card>
{/* MCP Permissions Card */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Shield className="h-5 w-5" />
MCP Permissions
</CardTitle>
<CardDescription>
Model Context Protocol servers this agent can access
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-3">
{type.mcpPermissions.map((perm) => (
<div
key={perm.id}
className={`flex items-center justify-between rounded-lg border p-3 ${
perm.enabled ? 'border-primary/20 bg-primary/5' : 'border-muted bg-muted/50'
}`}
>
<div className="flex items-center gap-3">
<div
className={`flex h-8 w-8 items-center justify-center rounded-full ${
perm.enabled ? 'bg-primary/10' : 'bg-muted'
}`}
>
{perm.enabled ? (
<CheckCircle2 className="h-4 w-4 text-primary" />
) : (
<AlertTriangle className="h-4 w-4 text-muted-foreground" />
)}
</div>
<div>
<p className="font-medium">{perm.name}</p>
<p className="text-xs text-muted-foreground">
{perm.enabled ? perm.scopes.join(', ') : 'Not enabled'}
</p>
</div>
</div>
<Badge variant={perm.enabled ? 'default' : 'secondary'}>
{perm.enabled ? 'Enabled' : 'Disabled'}
</Badge>
</div>
))}
</div>
</CardContent>
</Card>
</div>
{/* Sidebar */}
<div className="space-y-6">
{/* Model Configuration */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2 text-lg">
<Cpu className="h-5 w-5" />
Model Configuration
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<p className="text-sm text-muted-foreground">Primary Model</p>
<p className="font-medium">{type.model.primary}</p>
</div>
<div>
<p className="text-sm text-muted-foreground">Failover Model</p>
<p className="font-medium">{type.model.failover}</p>
</div>
<Separator />
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Temperature</span>
<span className="font-medium">{type.parameters.temperature}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Max Tokens</span>
<span className="font-medium">{type.parameters.maxTokens.toLocaleString()}</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Top P</span>
<span className="font-medium">{type.parameters.topP}</span>
</div>
</div>
</CardContent>
</Card>
{/* Instance Stats */}
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2 text-lg">
<Bot className="h-5 w-5" />
Instances
</CardTitle>
</CardHeader>
<CardContent>
<div className="text-center">
<p className="text-4xl font-bold text-primary">{type.instanceCount}</p>
<p className="text-sm text-muted-foreground">Active instances</p>
</div>
<Button variant="outline" className="mt-4 w-full" size="sm">
View Instances
</Button>
</CardContent>
</Card>
{/* Danger Zone */}
<Card className="border-destructive/50">
<CardHeader>
<CardTitle className="flex items-center gap-2 text-lg text-destructive">
<AlertTriangle className="h-5 w-5" />
Danger Zone
</CardTitle>
</CardHeader>
<CardContent className="space-y-3">
<Button variant="outline" className="w-full" size="sm">
Deactivate Type
</Button>
<Button variant="destructive" className="w-full" size="sm">
<Trash2 className="mr-2 h-4 w-4" />
Delete Type
</Button>
</CardContent>
</Card>
</div>
</div>
</div>
);
}
// Agent Type Editor View
function AgentTypeEditorView({
onBack,
onSave,
isNew = false,
}: {
onBack: () => void;
onSave: () => void;
isNew?: boolean;
}) {
const [activeTab, setActiveTab] = useState('basic');
return (
<div className="space-y-6">
{/* Header */}
<div className="flex items-center gap-4">
<Button variant="ghost" size="icon" onClick={onBack}>
<ArrowLeft className="h-4 w-4" />
</Button>
<div className="flex-1">
<h1 className="text-3xl font-bold">
{isNew ? 'Create Agent Type' : 'Edit Agent Type'}
</h1>
<p className="text-muted-foreground">
{isNew
? 'Define a new agent type template'
: 'Modify agent type configuration'}
</p>
</div>
<div className="flex gap-2">
<Button variant="outline" onClick={onBack}>
Cancel
</Button>
<Button onClick={onSave}>
<Save className="mr-2 h-4 w-4" />
{isNew ? 'Create' : 'Save Changes'}
</Button>
</div>
</div>
{/* Editor Tabs */}
<Tabs value={activeTab} onValueChange={setActiveTab}>
<TabsList className="grid w-full grid-cols-4">
<TabsTrigger value="basic">
<FileText className="mr-2 h-4 w-4" />
Basic Info
</TabsTrigger>
<TabsTrigger value="model">
<Cpu className="mr-2 h-4 w-4" />
Model
</TabsTrigger>
<TabsTrigger value="permissions">
<Shield className="mr-2 h-4 w-4" />
Permissions
</TabsTrigger>
<TabsTrigger value="personality">
<MessageSquare className="mr-2 h-4 w-4" />
Personality
</TabsTrigger>
</TabsList>
{/* Basic Info Tab */}
<TabsContent value="basic" className="space-y-6">
<Card>
<CardHeader>
<CardTitle>Basic Information</CardTitle>
<CardDescription>
Define the agent type name, description, and expertise areas
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="grid gap-4 md:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="name">Name</Label>
<Input
id="name"
placeholder="e.g., Software Architect"
defaultValue={!isNew ? mockAgentTypeDetail.name : ''}
/>
</div>
<div className="space-y-2">
<Label htmlFor="status">Status</Label>
<Select defaultValue={!isNew ? mockAgentTypeDetail.status : 'draft'}>
<SelectTrigger id="status">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="active">Active</SelectItem>
<SelectItem value="draft">Draft</SelectItem>
<SelectItem value="inactive">Inactive</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="space-y-2">
<Label htmlFor="description">Description</Label>
<Textarea
id="description"
placeholder="Describe what this agent type does..."
rows={3}
defaultValue={!isNew ? mockAgentTypeDetail.description : ''}
/>
</div>
<div className="space-y-2">
<Label>Expertise Areas</Label>
<p className="text-sm text-muted-foreground">
Add skills and areas of expertise (comma-separated)
</p>
<Input
placeholder="e.g., System Design, API Design, Security"
defaultValue={!isNew ? mockAgentTypeDetail.expertise.join(', ') : ''}
/>
<div className="flex flex-wrap gap-2 pt-2">
{(!isNew ? mockAgentTypeDetail.expertise : []).map((skill) => (
<Badge key={skill} variant="secondary" className="gap-1">
{skill}
<button className="ml-1 rounded-full hover:bg-muted">
<span className="sr-only">Remove {skill}</span>
&times;
</button>
</Badge>
))}
</div>
</div>
</CardContent>
</Card>
</TabsContent>
{/* Model Configuration Tab */}
<TabsContent value="model" className="space-y-6">
<Card>
<CardHeader>
<CardTitle>Model Selection</CardTitle>
<CardDescription>
Choose the AI models that power this agent type
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="grid gap-4 md:grid-cols-2">
<div className="space-y-2">
<Label htmlFor="primary-model">Primary Model</Label>
<Select
defaultValue={!isNew ? mockAgentTypeDetail.model.primary : ''}
>
<SelectTrigger id="primary-model">
<SelectValue placeholder="Select model" />
</SelectTrigger>
<SelectContent>
<SelectItem value="claude-opus-4-5-20251101">
Claude Opus 4.5
</SelectItem>
<SelectItem value="claude-sonnet-4-20250514">
Claude Sonnet 4
</SelectItem>
<SelectItem value="claude-3-5-sonnet-20241022">
Claude 3.5 Sonnet
</SelectItem>
</SelectContent>
</Select>
<p className="text-xs text-muted-foreground">
Main model used for this agent
</p>
</div>
<div className="space-y-2">
<Label htmlFor="failover-model">Failover Model</Label>
<Select
defaultValue={!isNew ? mockAgentTypeDetail.model.failover : ''}
>
<SelectTrigger id="failover-model">
<SelectValue placeholder="Select model" />
</SelectTrigger>
<SelectContent>
<SelectItem value="claude-opus-4-5-20251101">
Claude Opus 4.5
</SelectItem>
<SelectItem value="claude-sonnet-4-20250514">
Claude Sonnet 4
</SelectItem>
<SelectItem value="claude-3-5-sonnet-20241022">
Claude 3.5 Sonnet
</SelectItem>
</SelectContent>
</Select>
<p className="text-xs text-muted-foreground">
Backup model if primary is unavailable
</p>
</div>
</div>
</CardContent>
</Card>
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2">
<Sliders className="h-5 w-5" />
Model Parameters
</CardTitle>
<CardDescription>
Fine-tune the model behavior
</CardDescription>
</CardHeader>
<CardContent className="space-y-6">
<div className="grid gap-6 md:grid-cols-3">
<div className="space-y-2">
<Label htmlFor="temperature">Temperature</Label>
<Input
id="temperature"
type="number"
step="0.1"
min="0"
max="2"
defaultValue={!isNew ? mockAgentTypeDetail.parameters.temperature : 0.7}
/>
<p className="text-xs text-muted-foreground">
0 = deterministic, 2 = creative
</p>
</div>
<div className="space-y-2">
<Label htmlFor="max-tokens">Max Tokens</Label>
<Input
id="max-tokens"
type="number"
step="1024"
min="1024"
max="32768"
defaultValue={!isNew ? mockAgentTypeDetail.parameters.maxTokens : 8192}
/>
<p className="text-xs text-muted-foreground">
Maximum response length
</p>
</div>
<div className="space-y-2">
<Label htmlFor="top-p">Top P</Label>
<Input
id="top-p"
type="number"
step="0.05"
min="0"
max="1"
defaultValue={!isNew ? mockAgentTypeDetail.parameters.topP : 0.95}
/>
<p className="text-xs text-muted-foreground">
Nucleus sampling threshold
</p>
</div>
</div>
</CardContent>
</Card>
</TabsContent>
{/* MCP Permissions Tab */}
<TabsContent value="permissions" className="space-y-6">
<Card>
<CardHeader>
<CardTitle>MCP Server Permissions</CardTitle>
<CardDescription>
Configure which MCP servers this agent can access and with what permissions
</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{mockAgentTypeDetail.mcpPermissions.map((perm) => (
<div
key={perm.id}
className="rounded-lg border p-4 space-y-3"
>
<div className="flex items-center justify-between">
<div className="flex items-center gap-3">
<Checkbox
id={`perm-${perm.id}`}
defaultChecked={perm.enabled}
/>
<Label htmlFor={`perm-${perm.id}`} className="font-medium">
{perm.name}
</Label>
</div>
</div>
{perm.enabled && (
<div className="ml-7 space-y-2">
<p className="text-sm text-muted-foreground">Scopes:</p>
<div className="flex flex-wrap gap-2">
{['read', 'write', 'issues', 'branches', 'prs'].map((scope) => (
<div key={scope} className="flex items-center gap-1">
<Checkbox
id={`scope-${perm.id}-${scope}`}
defaultChecked={perm.scopes.includes(scope)}
/>
<Label
htmlFor={`scope-${perm.id}-${scope}`}
className="text-sm font-normal"
>
{scope}
</Label>
</div>
))}
</div>
</div>
)}
</div>
))}
</CardContent>
</Card>
</TabsContent>
{/* Personality Prompt Tab */}
<TabsContent value="personality" className="space-y-6">
<Card>
<CardHeader>
<CardTitle>Personality Prompt</CardTitle>
<CardDescription>
Define the agent&apos;s personality, behavior, and communication style.
This prompt shapes how the agent approaches tasks and interacts.
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<Textarea
placeholder="You are a..."
rows={15}
className="font-mono text-sm"
defaultValue={!isNew ? mockAgentTypeDetail.personalityPrompt : ''}
/>
<div className="flex items-center gap-4 text-sm text-muted-foreground">
<span>Character count: {mockAgentTypeDetail.personalityPrompt.length}</span>
<Separator orientation="vertical" className="h-4" />
<Button variant="ghost" size="sm">
<Eye className="mr-2 h-4 w-4" />
Preview
</Button>
<Button variant="ghost" size="sm">
<Code className="mr-2 h-4 w-4" />
Use Template
</Button>
</div>
</div>
</CardContent>
</Card>
</TabsContent>
</Tabs>
</div>
);
}
export default function AgentConfigurationPrototype() {
const [view, setView] = useState<ViewState>('list');
const [_selectedId, setSelectedId] = useState<string | null>(null);
const [isCreating, setIsCreating] = useState(false);
const handleSelectType = (id: string) => {
setSelectedId(id);
setView('detail');
};
const handleCreate = () => {
setIsCreating(true);
setView('editor');
};
const handleEdit = () => {
setIsCreating(false);
setView('editor');
};
const handleBack = () => {
if (view === 'editor') {
setView(isCreating ? 'list' : 'detail');
} else {
setView('list');
setSelectedId(null);
}
};
const handleSave = () => {
// In real implementation, this would save to API
setView('list');
setSelectedId(null);
setIsCreating(false);
};
return (
<div className="min-h-screen bg-background">
{/* Navigation Bar */}
<nav className="border-b bg-card">
<div className="container mx-auto flex h-14 items-center justify-between px-4">
<div className="flex items-center gap-4">
<span className="font-semibold text-primary">Syndarix</span>
<Separator orientation="vertical" className="h-6" />
<span className="text-sm text-muted-foreground">Configuration</span>
<ChevronRight className="h-4 w-4 text-muted-foreground" />
<span className="text-sm font-medium">Agent Types</span>
</div>
<div className="flex items-center gap-2">
<Button variant="ghost" size="icon">
<Settings className="h-4 w-4" />
</Button>
</div>
</div>
</nav>
<div className="container mx-auto px-4 py-6">
{view === 'list' && (
<AgentTypeListView onSelect={handleSelectType} onCreate={handleCreate} />
)}
{view === 'detail' && (
<AgentTypeDetailView onBack={handleBack} onEdit={handleEdit} />
)}
{view === 'editor' && (
<AgentTypeEditorView
onBack={handleBack}
onSave={handleSave}
isNew={isCreating}
/>
)}
</div>
</div>
);
}

View File

@@ -1,246 +0,0 @@
# Issue List and Detail Views - Design Prototype
## Overview
The Issue Management interface provides a comprehensive view of project issues synced from Gitea. Users can filter, sort, search, and manage issues. The detail view shows full issue content, status workflow, and activity timeline. Issues are the primary unit of work that agents execute.
## User Stories
- As a user, I want to see all issues in a filterable list so I can find specific work items
- As a user, I want to filter by status, priority, sprint, and assignee to focus on relevant issues
- As a user, I want to sort issues by different criteria to organize my view
- As a user, I want to perform bulk actions on multiple issues efficiently
- As a user, I want to see the sync status to know if issues are current with Gitea
- As a user, I want to view full issue details including description (markdown)
- As a user, I want to change issue status through a workflow UI
- As a user, I want to see the activity timeline to understand issue history
## Key Screens
### 1. Issue List View
**Header**
- Page title with issue count
- Sync button to trigger Gitea sync
- Create new issue button
**Search and Filters**
- Full-text search input
- Quick status filter dropdown
- Expandable filter panel with:
- Priority filter
- Sprint filter
- Assignee filter
- Labels filter
- Clear filters button
**Bulk Actions Bar** (appears when issues selected)
- Selection count
- Change Status button
- Assign button
- Add Labels button
- Delete button
**Issue Table**
- Checkbox column for selection
- Issue number (sortable)
- Title with label badges
- Status with icon
- Priority badge (sortable)
- Assignee with agent/user indicator
- Sprint badge or "Backlog"
- Sync status indicator
- Actions menu
### 2. Issue Detail View
**Header**
- Back button
- Issue number
- Status and priority badges
- Sync status
- Title
- Metadata (created, updated dates)
- External link to Gitea
- Edit and workflow action buttons
**Main Content (2/3)**
- Description card with markdown content
- Activity timeline with:
- Status changes
- Comments
- Assignment changes
- Label changes
- Creation event
- Agent/user avatars
**Sidebar (1/3)**
- Status workflow panel (clickable status list)
- Details panel:
- Assignee with avatar
- Reporter
- Sprint
- Story points
- Due date
- Labels
- Development panel:
- Branch name
- Pull request link
## User Flow
### Finding and Filtering Issues
1. User lands on issue list
2. User types in search box to filter
3. User selects status from dropdown
4. User expands filter panel for more options
5. User clears filters to reset
### Bulk Operations
1. User selects multiple issues via checkboxes
2. Bulk action bar appears
3. User clicks action (e.g., "Change Status")
4. Modal appears for status selection
5. Action applied to all selected issues
### Viewing and Updating Issue
1. User clicks issue row
2. Detail view shows full issue
3. User reviews description and activity
4. User clicks status in sidebar to change
5. Or clicks workflow button in header
6. Status updates and syncs to Gitea
## Design Decisions
### Table vs Card Layout
- Table chosen for density and scannability
- Columns prioritize most important info
- Sortable columns for user control
- Compact row height with key data visible
### Status Workflow
- Visual status list in sidebar
- Current status highlighted
- Click any status to change
- Workflow button in header for common transitions
- Matches Gitea/common issue tracker patterns
### Sync Status Indicator
- Small icon per issue showing sync state
- Synced (green check)
- Syncing (yellow spinner)
- Error (red alert)
- Critical for understanding data freshness
### Markdown Description
- Full markdown support planned
- Currently shows pre-formatted text
- Checkboxes for acceptance criteria
- Code blocks for technical details
### Activity Timeline
- Chronological (newest first option available)
- Avatar indicates agent vs human
- Grouped by type (status, comment, etc.)
- Vertical line connects events
## States
### Loading
- Skeleton rows in table
- Loading spinner in header
### Empty
- No issues: "Create your first issue" CTA
- No matches: "No issues found" with filter reset
### Error
- Failed to load: Error card with retry
- Sync error: Warning banner with retry
### Selected
- Row highlight on hover
- Checkbox checked state
- Bulk action bar visible
## Responsive Breakpoints
### Desktop (lg: 1024px+)
- Full table with all columns
- 2/3 + 1/3 split in detail view
- Extended filter panel inline
### Tablet (md: 768px)
- Table with key columns only
- Stacked detail view
- Collapsible filter panel
### Mobile (< 768px)
- Card-based list instead of table
- Single column detail view
- Bottom sheet for filters
- Touch-friendly checkboxes
## Accessibility Notes
- Table has proper header associations
- Checkbox has aria-label
- Status icons have text labels
- Sort direction announced
- Focus management on view changes
- Keyboard navigation for table rows
## Components Used
- Card, CardHeader, CardTitle, CardContent
- Button (default, outline, ghost variants)
- Badge (default, secondary, outline variants)
- Input (search)
- Select, SelectContent, SelectItem
- Checkbox
- Table, TableHeader, TableBody, TableRow, TableHead, TableCell
- Separator
- Lucide icons
## Filter Fields Reference
| Filter | Type | Options |
|--------|------|---------|
| Status | Select | All, Open, In Progress, In Review, Blocked, Done |
| Priority | Select | All, High, Medium, Low |
| Sprint | Select | All, Current sprints, Backlog |
| Assignee | Select | All, Unassigned, Agent list |
| Labels | Multi-select | Available labels |
## Status Workflow
```
Open --> In Progress --> In Review --> Done
| | |
v v v
Blocked <-- Blocked <---- Blocked
```
## Questions for Review
1. Should the table support drag-and-drop for status changes (Kanban-style)?
2. Is the sync status indicator clear enough?
3. Should we add inline editing for priority and assignee?
4. Should the activity timeline be collapsible for long histories?
5. Should we add keyboard shortcuts for common actions?
6. Should we show more columns or keep it minimal?
## How to View
Navigate to: `/prototypes/issue-management`
Click through the views:
1. Start on list view with sample issues
2. Use search and filters
3. Select checkboxes to see bulk actions
4. Click a row to see detail view
5. Click status buttons to see workflow
## Next Steps
After approval:
1. Implement real-time sync with Gitea via MCP
2. Add markdown rendering for descriptions
3. Implement comment posting
4. Add keyboard shortcuts
5. Implement bulk action modals
6. Add pagination for large issue lists
7. Implement inline editing
8. Add Kanban view as alternative

View File

@@ -1,958 +0,0 @@
'use client';
import { useState } from 'react';
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Checkbox } from '@/components/ui/checkbox';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Separator } from '@/components/ui/separator';
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from '@/components/ui/table';
import {
Search,
Filter,
ChevronRight,
ArrowLeft,
ChevronUp,
ChevronDown,
MoreVertical,
CircleDot,
PlayCircle,
Clock,
CheckCircle2,
AlertCircle,
XCircle,
RefreshCw,
ExternalLink,
MessageSquare,
GitBranch,
GitPullRequest,
Bot,
User,
Calendar,
Tag,
Settings,
Upload,
Trash2,
Edit,
Plus,
} from 'lucide-react';
// Mock data for issues
const mockIssues = [
{
id: 'ISS-001',
number: 42,
title: 'Implement user authentication flow',
description: 'Create complete authentication flow with login, register, and password reset.',
status: 'in_progress',
priority: 'high',
labels: ['feature', 'auth', 'backend'],
sprint: 'Sprint 3',
assignee: { name: 'Backend Engineer', type: 'agent' },
createdAt: '2025-01-15',
updatedAt: '2 hours ago',
syncStatus: 'synced',
},
{
id: 'ISS-002',
number: 43,
title: 'Design product catalog component',
description: 'Create reusable product card and catalog grid components.',
status: 'in_review',
priority: 'medium',
labels: ['feature', 'frontend', 'ui'],
sprint: 'Sprint 3',
assignee: { name: 'Frontend Engineer', type: 'agent' },
createdAt: '2025-01-16',
updatedAt: '30 min ago',
syncStatus: 'synced',
},
{
id: 'ISS-003',
number: 44,
title: 'Fix cart total calculation bug',
description: 'Cart total shows incorrect amount when discount is applied.',
status: 'blocked',
priority: 'high',
labels: ['bug', 'critical', 'backend'],
sprint: 'Sprint 3',
assignee: { name: 'Backend Engineer', type: 'agent' },
createdAt: '2025-01-17',
updatedAt: '1 hour ago',
syncStatus: 'pending',
blockedBy: 'Waiting for discount API specification',
},
{
id: 'ISS-004',
number: 45,
title: 'Add product search functionality',
description: 'Implement full-text search with filters for the product catalog.',
status: 'open',
priority: 'medium',
labels: ['feature', 'search', 'backend'],
sprint: 'Sprint 3',
assignee: null,
createdAt: '2025-01-18',
updatedAt: '3 hours ago',
syncStatus: 'synced',
},
{
id: 'ISS-005',
number: 46,
title: 'Optimize database queries for product listing',
description: 'Performance optimization for product queries with pagination.',
status: 'done',
priority: 'low',
labels: ['performance', 'backend', 'database'],
sprint: 'Sprint 2',
assignee: { name: 'Backend Engineer', type: 'agent' },
createdAt: '2025-01-10',
updatedAt: '2 days ago',
syncStatus: 'synced',
},
{
id: 'ISS-006',
number: 47,
title: 'Create checkout page wireframes',
description: 'Design wireframes for the checkout flow including payment selection.',
status: 'done',
priority: 'high',
labels: ['design', 'checkout', 'ui'],
sprint: 'Sprint 2',
assignee: { name: 'Product Owner', type: 'agent' },
createdAt: '2025-01-08',
updatedAt: '5 days ago',
syncStatus: 'synced',
},
{
id: 'ISS-007',
number: 48,
title: 'Implement responsive navigation',
description: 'Create mobile-friendly navigation with hamburger menu.',
status: 'open',
priority: 'medium',
labels: ['feature', 'frontend', 'responsive'],
sprint: null,
assignee: null,
createdAt: '2025-01-19',
updatedAt: '1 day ago',
syncStatus: 'synced',
},
{
id: 'ISS-008',
number: 49,
title: 'Set up E2E test framework',
description: 'Configure Playwright for end-to-end testing.',
status: 'in_progress',
priority: 'medium',
labels: ['testing', 'infrastructure'],
sprint: 'Sprint 3',
assignee: { name: 'QA Engineer', type: 'agent' },
createdAt: '2025-01-20',
updatedAt: '4 hours ago',
syncStatus: 'synced',
},
];
// Detailed issue for detail view
const mockIssueDetail = {
id: 'ISS-001',
number: 42,
title: 'Implement user authentication flow',
description: `## Overview
Create a complete authentication flow for the e-commerce platform.
## Requirements
- Login with email/password
- Registration with email verification
- Password reset functionality
- OAuth support (Google, GitHub)
- JWT token management
- Session handling
## Acceptance Criteria
- [ ] Users can register with email and password
- [ ] Users receive email verification link
- [ ] Users can log in with verified email
- [ ] Password reset email is sent within 30 seconds
- [ ] OAuth buttons redirect properly
- [x] JWT tokens are stored securely
- [x] Tokens refresh automatically
## Technical Notes
- Use FastAPI security utilities
- Store sessions in Redis
- Follow OWASP guidelines
`,
status: 'in_progress',
priority: 'high',
labels: ['feature', 'auth', 'backend', 'security'],
sprint: 'Sprint 3',
milestone: 'MVP Launch',
storyPoints: 8,
assignee: { name: 'Backend Engineer', type: 'agent', avatar: 'BE' },
reporter: { name: 'Product Owner', type: 'agent', avatar: 'PO' },
createdAt: '2025-01-15T10:30:00Z',
updatedAt: '2025-01-20T14:22:00Z',
dueDate: '2025-02-01',
syncStatus: 'synced',
externalUrl: 'https://gitea.example.com/project/issues/42',
branch: 'feature/42-auth-flow',
pullRequest: 'PR #15',
activity: [
{
id: 'act-001',
type: 'status_change',
actor: { name: 'Backend Engineer', type: 'agent' },
message: 'moved issue from "Open" to "In Progress"',
timestamp: '2 hours ago',
},
{
id: 'act-002',
type: 'comment',
actor: { name: 'Backend Engineer', type: 'agent' },
message: 'Started implementing JWT token generation. Using HS256 algorithm as discussed in architecture meeting.',
timestamp: '3 hours ago',
},
{
id: 'act-003',
type: 'assignment',
actor: { name: 'Product Owner', type: 'agent' },
message: 'assigned this issue to Backend Engineer',
timestamp: '1 day ago',
},
{
id: 'act-004',
type: 'label',
actor: { name: 'Product Owner', type: 'agent' },
message: 'added labels: security, backend',
timestamp: '1 day ago',
},
{
id: 'act-005',
type: 'created',
actor: { name: 'Product Owner', type: 'agent' },
message: 'created this issue',
timestamp: '5 days ago',
},
],
};
// Status config
const statusConfig: Record<string, { label: string; icon: typeof CircleDot; color: string }> = {
open: { label: 'Open', icon: CircleDot, color: 'text-blue-500' },
in_progress: { label: 'In Progress', icon: PlayCircle, color: 'text-yellow-500' },
in_review: { label: 'In Review', icon: Clock, color: 'text-purple-500' },
blocked: { label: 'Blocked', icon: AlertCircle, color: 'text-red-500' },
done: { label: 'Done', icon: CheckCircle2, color: 'text-green-500' },
closed: { label: 'Closed', icon: XCircle, color: 'text-gray-500' },
};
// Priority config
const priorityConfig: Record<string, { label: string; color: string }> = {
high: { label: 'High', color: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200' },
medium: { label: 'Medium', color: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200' },
low: { label: 'Low', color: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200' },
};
// Sync status indicator
function SyncStatusIndicator({ status }: { status: string }) {
const configs: Record<string, { icon: typeof RefreshCw; color: string; label: string }> = {
synced: { icon: CheckCircle2, color: 'text-green-500', label: 'Synced' },
pending: { icon: RefreshCw, color: 'text-yellow-500 animate-spin', label: 'Syncing' },
error: { icon: AlertCircle, color: 'text-red-500', label: 'Sync Error' },
};
const config = configs[status] || configs.synced;
const Icon = config.icon;
return (
<div className="flex items-center gap-1" title={config.label}>
<Icon className={`h-3.5 w-3.5 ${config.color}`} />
</div>
);
}
// Issue status badge
function StatusBadge({ status }: { status: string }) {
const config = statusConfig[status] || statusConfig.open;
const Icon = config.icon;
return (
<div className={`flex items-center gap-1.5 ${config.color}`}>
<Icon className="h-4 w-4" />
<span className="text-sm font-medium">{config.label}</span>
</div>
);
}
// Priority badge
function PriorityBadge({ priority }: { priority: string }) {
const config = priorityConfig[priority] || priorityConfig.medium;
return (
<Badge className={config.color} variant="outline">
{config.label}
</Badge>
);
}
// Issue List View
function IssueListView({ onSelectIssue }: { onSelectIssue: (id: string) => void }) {
const [searchQuery, setSearchQuery] = useState('');
const [statusFilter, setStatusFilter] = useState('all');
const [priorityFilter, setPriorityFilter] = useState('all');
const [sprintFilter, setSprintFilter] = useState('all');
const [selectedIssues, setSelectedIssues] = useState<string[]>([]);
const [sortField, setSortField] = useState<'number' | 'priority' | 'updatedAt'>('updatedAt');
const [sortDirection, setSortDirection] = useState<'asc' | 'desc'>('desc');
const [showFilters, setShowFilters] = useState(false);
const filteredIssues = mockIssues.filter((issue) => {
const matchesSearch =
issue.title.toLowerCase().includes(searchQuery.toLowerCase()) ||
issue.description.toLowerCase().includes(searchQuery.toLowerCase()) ||
issue.number.toString().includes(searchQuery);
const matchesStatus = statusFilter === 'all' || issue.status === statusFilter;
const matchesPriority = priorityFilter === 'all' || issue.priority === priorityFilter;
const matchesSprint =
sprintFilter === 'all' ||
(sprintFilter === 'backlog' && !issue.sprint) ||
issue.sprint === sprintFilter;
return matchesSearch && matchesStatus && matchesPriority && matchesSprint;
});
const sortedIssues = [...filteredIssues].sort((a, b) => {
const direction = sortDirection === 'asc' ? 1 : -1;
if (sortField === 'number') return (a.number - b.number) * direction;
if (sortField === 'priority') {
const priorityOrder = { high: 3, medium: 2, low: 1 };
return (priorityOrder[a.priority as keyof typeof priorityOrder] -
priorityOrder[b.priority as keyof typeof priorityOrder]) * direction;
}
return 0; // Default to original order for updatedAt
});
const handleSort = (field: 'number' | 'priority' | 'updatedAt') => {
if (sortField === field) {
setSortDirection(sortDirection === 'asc' ? 'desc' : 'asc');
} else {
setSortField(field);
setSortDirection('desc');
}
};
const handleSelectAll = () => {
if (selectedIssues.length === sortedIssues.length) {
setSelectedIssues([]);
} else {
setSelectedIssues(sortedIssues.map((i) => i.id));
}
};
const handleSelectIssue = (id: string) => {
if (selectedIssues.includes(id)) {
setSelectedIssues(selectedIssues.filter((i) => i !== id));
} else {
setSelectedIssues([...selectedIssues, id]);
}
};
const SortIcon = ({ field }: { field: string }) => {
if (sortField !== field) return null;
return sortDirection === 'asc' ? (
<ChevronUp className="ml-1 inline h-4 w-4" />
) : (
<ChevronDown className="ml-1 inline h-4 w-4" />
);
};
return (
<div className="space-y-4">
{/* Header */}
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div>
<h1 className="text-3xl font-bold">Issues</h1>
<p className="text-muted-foreground">
{filteredIssues.length} issues found
</p>
</div>
<div className="flex gap-2">
<Button variant="outline" size="sm">
<Upload className="mr-2 h-4 w-4" />
Sync
</Button>
<Button size="sm">
<Plus className="mr-2 h-4 w-4" />
New Issue
</Button>
</div>
</div>
{/* Search and Quick Filters */}
<div className="flex flex-col gap-4 sm:flex-row">
<div className="relative flex-1">
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<Input
placeholder="Search issues..."
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
className="pl-9"
/>
</div>
<div className="flex gap-2">
<Select value={statusFilter} onValueChange={setStatusFilter}>
<SelectTrigger className="w-32">
<SelectValue placeholder="Status" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Status</SelectItem>
<SelectItem value="open">Open</SelectItem>
<SelectItem value="in_progress">In Progress</SelectItem>
<SelectItem value="in_review">In Review</SelectItem>
<SelectItem value="blocked">Blocked</SelectItem>
<SelectItem value="done">Done</SelectItem>
</SelectContent>
</Select>
<Button
variant="outline"
size="icon"
onClick={() => setShowFilters(!showFilters)}
className={showFilters ? 'bg-muted' : ''}
>
<Filter className="h-4 w-4" />
</Button>
</div>
</div>
{/* Extended Filters */}
{showFilters && (
<Card className="p-4">
<div className="grid gap-4 sm:grid-cols-4">
<div className="space-y-2">
<Label>Priority</Label>
<Select value={priorityFilter} onValueChange={setPriorityFilter}>
<SelectTrigger>
<SelectValue placeholder="All" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All</SelectItem>
<SelectItem value="high">High</SelectItem>
<SelectItem value="medium">Medium</SelectItem>
<SelectItem value="low">Low</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label>Sprint</Label>
<Select value={sprintFilter} onValueChange={setSprintFilter}>
<SelectTrigger>
<SelectValue placeholder="All" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Sprints</SelectItem>
<SelectItem value="Sprint 3">Sprint 3</SelectItem>
<SelectItem value="Sprint 2">Sprint 2</SelectItem>
<SelectItem value="backlog">Backlog</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label>Assignee</Label>
<Select defaultValue="all">
<SelectTrigger>
<SelectValue placeholder="All" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All</SelectItem>
<SelectItem value="unassigned">Unassigned</SelectItem>
<SelectItem value="backend">Backend Engineer</SelectItem>
<SelectItem value="frontend">Frontend Engineer</SelectItem>
<SelectItem value="qa">QA Engineer</SelectItem>
</SelectContent>
</Select>
</div>
<div className="space-y-2">
<Label>Labels</Label>
<Select defaultValue="all">
<SelectTrigger>
<SelectValue placeholder="All" />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All</SelectItem>
<SelectItem value="feature">Feature</SelectItem>
<SelectItem value="bug">Bug</SelectItem>
<SelectItem value="backend">Backend</SelectItem>
<SelectItem value="frontend">Frontend</SelectItem>
</SelectContent>
</Select>
</div>
</div>
<div className="mt-4 flex justify-end gap-2">
<Button
variant="ghost"
size="sm"
onClick={() => {
setStatusFilter('all');
setPriorityFilter('all');
setSprintFilter('all');
}}
>
Clear Filters
</Button>
</div>
</Card>
)}
{/* Bulk Actions */}
{selectedIssues.length > 0 && (
<div className="flex items-center gap-4 rounded-lg border bg-muted/50 p-3">
<span className="text-sm font-medium">
{selectedIssues.length} selected
</span>
<Separator orientation="vertical" className="h-6" />
<div className="flex gap-2">
<Button variant="outline" size="sm">
Change Status
</Button>
<Button variant="outline" size="sm">
Assign
</Button>
<Button variant="outline" size="sm">
Add Labels
</Button>
<Button variant="outline" size="sm" className="text-destructive">
<Trash2 className="mr-2 h-4 w-4" />
Delete
</Button>
</div>
</div>
)}
{/* Issue Table */}
<Card>
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-12">
<Checkbox
checked={
selectedIssues.length === sortedIssues.length && sortedIssues.length > 0
}
onCheckedChange={handleSelectAll}
/>
</TableHead>
<TableHead
className="w-20 cursor-pointer"
onClick={() => handleSort('number')}
>
# <SortIcon field="number" />
</TableHead>
<TableHead>Title</TableHead>
<TableHead className="w-32">Status</TableHead>
<TableHead
className="w-24 cursor-pointer"
onClick={() => handleSort('priority')}
>
Priority <SortIcon field="priority" />
</TableHead>
<TableHead className="w-32">Assignee</TableHead>
<TableHead className="w-28">Sprint</TableHead>
<TableHead className="w-10">Sync</TableHead>
<TableHead className="w-10"></TableHead>
</TableRow>
</TableHeader>
<TableBody>
{sortedIssues.map((issue) => (
<TableRow
key={issue.id}
className="cursor-pointer"
onClick={() => onSelectIssue(issue.id)}
>
<TableCell onClick={(e) => e.stopPropagation()}>
<Checkbox
checked={selectedIssues.includes(issue.id)}
onCheckedChange={() => handleSelectIssue(issue.id)}
/>
</TableCell>
<TableCell className="font-mono text-sm text-muted-foreground">
{issue.number}
</TableCell>
<TableCell>
<div className="space-y-1">
<p className="font-medium">{issue.title}</p>
<div className="flex flex-wrap gap-1">
{issue.labels.slice(0, 3).map((label) => (
<Badge key={label} variant="secondary" className="text-xs">
{label}
</Badge>
))}
{issue.labels.length > 3 && (
<Badge variant="outline" className="text-xs">
+{issue.labels.length - 3}
</Badge>
)}
</div>
</div>
</TableCell>
<TableCell>
<StatusBadge status={issue.status} />
</TableCell>
<TableCell>
<PriorityBadge priority={issue.priority} />
</TableCell>
<TableCell>
{issue.assignee ? (
<div className="flex items-center gap-2">
<div className="flex h-6 w-6 items-center justify-center rounded-full bg-primary/10 text-xs font-medium text-primary">
{issue.assignee.type === 'agent' ? (
<Bot className="h-3 w-3" />
) : (
<User className="h-3 w-3" />
)}
</div>
<span className="text-sm">{issue.assignee.name}</span>
</div>
) : (
<span className="text-sm text-muted-foreground">Unassigned</span>
)}
</TableCell>
<TableCell>
{issue.sprint ? (
<Badge variant="outline" className="text-xs">
{issue.sprint}
</Badge>
) : (
<span className="text-xs text-muted-foreground">Backlog</span>
)}
</TableCell>
<TableCell>
<SyncStatusIndicator status={issue.syncStatus} />
</TableCell>
<TableCell onClick={(e) => e.stopPropagation()}>
<Button variant="ghost" size="icon" className="h-8 w-8">
<MoreVertical className="h-4 w-4" />
</Button>
</TableCell>
</TableRow>
))}
</TableBody>
</Table>
{sortedIssues.length === 0 && (
<div className="py-12 text-center">
<CircleDot className="mx-auto h-12 w-12 text-muted-foreground" />
<h3 className="mt-4 font-semibold">No issues found</h3>
<p className="text-muted-foreground">Try adjusting your search or filters</p>
</div>
)}
</Card>
</div>
);
}
// Issue Detail View
function IssueDetailView({ onBack }: { onBack: () => void }) {
const issue = mockIssueDetail;
const [currentStatus, setCurrentStatus] = useState(issue.status);
const workflowButtons = [
{ from: 'open', to: 'in_progress', label: 'Start Work' },
{ from: 'in_progress', to: 'in_review', label: 'Submit for Review' },
{ from: 'in_review', to: 'done', label: 'Mark Done' },
{ from: 'blocked', to: 'in_progress', label: 'Unblock' },
];
const currentWorkflow = workflowButtons.find((w) => w.from === currentStatus);
return (
<div className="space-y-6">
{/* Header */}
<div className="flex items-start gap-4">
<Button variant="ghost" size="icon" onClick={onBack}>
<ArrowLeft className="h-4 w-4" />
</Button>
<div className="flex-1 space-y-2">
<div className="flex flex-wrap items-center gap-2">
<span className="font-mono text-muted-foreground">#{issue.number}</span>
<StatusBadge status={currentStatus} />
<PriorityBadge priority={issue.priority} />
<SyncStatusIndicator status={issue.syncStatus} />
</div>
<h1 className="text-2xl font-bold">{issue.title}</h1>
<div className="flex flex-wrap items-center gap-4 text-sm text-muted-foreground">
<div className="flex items-center gap-1">
<Calendar className="h-4 w-4" />
Created {new Date(issue.createdAt).toLocaleDateString()}
</div>
<div className="flex items-center gap-1">
<Clock className="h-4 w-4" />
Updated {new Date(issue.updatedAt).toLocaleDateString()}
</div>
{issue.externalUrl && (
<a
href={issue.externalUrl}
target="_blank"
rel="noopener noreferrer"
className="flex items-center gap-1 text-primary hover:underline"
>
<ExternalLink className="h-4 w-4" />
View in Gitea
</a>
)}
</div>
</div>
<div className="flex gap-2">
<Button variant="outline" size="sm">
<Edit className="mr-2 h-4 w-4" />
Edit
</Button>
{currentWorkflow && (
<Button size="sm" onClick={() => setCurrentStatus(currentWorkflow.to)}>
{currentWorkflow.label}
</Button>
)}
</div>
</div>
<div className="grid gap-6 lg:grid-cols-3">
{/* Main Content */}
<div className="space-y-6 lg:col-span-2">
{/* Description Card */}
<Card>
<CardHeader>
<CardTitle>Description</CardTitle>
</CardHeader>
<CardContent>
<div className="prose prose-sm max-w-none dark:prose-invert">
<pre className="whitespace-pre-wrap font-sans text-sm">
{issue.description}
</pre>
</div>
</CardContent>
</Card>
{/* Activity Timeline */}
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<CardTitle className="flex items-center gap-2">
<MessageSquare className="h-5 w-5" />
Activity
</CardTitle>
<Button variant="outline" size="sm">
Add Comment
</Button>
</div>
</CardHeader>
<CardContent>
<div className="space-y-6">
{issue.activity.map((item, index) => (
<div key={item.id} className="flex gap-4">
<div className="relative flex flex-col items-center">
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-muted">
{item.actor.type === 'agent' ? (
<Bot className="h-4 w-4" />
) : (
<User className="h-4 w-4" />
)}
</div>
{index < issue.activity.length - 1 && (
<div className="absolute top-8 h-full w-px bg-border" />
)}
</div>
<div className="flex-1 pb-6">
<div className="flex items-baseline gap-2">
<span className="font-medium">{item.actor.name}</span>
<span className="text-sm text-muted-foreground">
{item.message}
</span>
</div>
<p className="text-xs text-muted-foreground">{item.timestamp}</p>
</div>
</div>
))}
</div>
</CardContent>
</Card>
</div>
{/* Sidebar */}
<div className="space-y-6">
{/* Status Actions */}
<Card>
<CardHeader>
<CardTitle className="text-lg">Status Workflow</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-2">
{Object.entries(statusConfig).map(([key, config]) => {
const Icon = config.icon;
const isActive = currentStatus === key;
return (
<button
key={key}
className={`flex w-full items-center gap-2 rounded-lg p-2 text-left transition-colors ${
isActive
? 'bg-primary/10 text-primary'
: 'hover:bg-muted'
}`}
onClick={() => setCurrentStatus(key)}
>
<Icon className={`h-4 w-4 ${config.color}`} />
<span className="text-sm">{config.label}</span>
{isActive && <CheckCircle2 className="ml-auto h-4 w-4" />}
</button>
);
})}
</div>
</CardContent>
</Card>
{/* Assignment Panel */}
<Card>
<CardHeader>
<CardTitle className="text-lg">Details</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<p className="text-sm text-muted-foreground">Assignee</p>
<div className="mt-1 flex items-center gap-2">
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-primary/10 text-sm font-medium text-primary">
{issue.assignee.avatar}
</div>
<div>
<p className="font-medium">{issue.assignee.name}</p>
<p className="text-xs text-muted-foreground">Agent</p>
</div>
</div>
</div>
<Separator />
<div>
<p className="text-sm text-muted-foreground">Reporter</p>
<div className="mt-1 flex items-center gap-2">
<div className="flex h-8 w-8 items-center justify-center rounded-full bg-muted text-sm font-medium">
{issue.reporter.avatar}
</div>
<p className="font-medium">{issue.reporter.name}</p>
</div>
</div>
<Separator />
<div>
<p className="text-sm text-muted-foreground">Sprint</p>
<p className="font-medium">{issue.sprint}</p>
</div>
<div>
<p className="text-sm text-muted-foreground">Story Points</p>
<p className="font-medium">{issue.storyPoints}</p>
</div>
<div>
<p className="text-sm text-muted-foreground">Due Date</p>
<p className="font-medium">{new Date(issue.dueDate).toLocaleDateString()}</p>
</div>
<Separator />
<div>
<p className="text-sm text-muted-foreground">Labels</p>
<div className="mt-2 flex flex-wrap gap-1">
{issue.labels.map((label) => (
<Badge key={label} variant="secondary" className="text-xs">
<Tag className="mr-1 h-3 w-3" />
{label}
</Badge>
))}
</div>
</div>
</CardContent>
</Card>
{/* Development */}
<Card>
<CardHeader>
<CardTitle className="text-lg">Development</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="flex items-center gap-2">
<GitBranch className="h-4 w-4 text-muted-foreground" />
<span className="text-sm font-mono">{issue.branch}</span>
</div>
<div className="flex items-center gap-2">
<GitPullRequest className="h-4 w-4 text-muted-foreground" />
<span className="text-sm">{issue.pullRequest}</span>
<Badge variant="outline" className="text-xs">Open</Badge>
</div>
</CardContent>
</Card>
</div>
</div>
</div>
);
}
export default function IssueManagementPrototype() {
const [view, setView] = useState<'list' | 'detail'>('list');
const [_selectedIssueId, setSelectedIssueId] = useState<string | null>(null);
const handleSelectIssue = (id: string) => {
setSelectedIssueId(id);
setView('detail');
};
const handleBack = () => {
setView('list');
setSelectedIssueId(null);
};
return (
<div className="min-h-screen bg-background">
{/* Navigation Bar */}
<nav className="border-b bg-card">
<div className="container mx-auto flex h-14 items-center justify-between px-4">
<div className="flex items-center gap-4">
<span className="font-semibold text-primary">Syndarix</span>
<Separator orientation="vertical" className="h-6" />
<span className="text-sm text-muted-foreground">E-Commerce Platform</span>
<ChevronRight className="h-4 w-4 text-muted-foreground" />
<span className="text-sm font-medium">Issues</span>
</div>
<div className="flex items-center gap-2">
<Button variant="ghost" size="icon">
<Settings className="h-4 w-4" />
</Button>
</div>
</div>
</nav>
<div className="container mx-auto px-4 py-6">
{view === 'list' && <IssueListView onSelectIssue={handleSelectIssue} />}
{view === 'detail' && <IssueDetailView onBack={handleBack} />}
</div>
</div>
);
}

View File

@@ -1,225 +0,0 @@
# Main Dashboard / Projects List - Design Prototype
## Overview
The Main Dashboard is the landing page users see after logging in to Syndarix. It provides a comprehensive overview of all their projects, real-time activity across the platform, and quick access to create new projects. This is the central hub from which users navigate to individual project dashboards.
## User Stories
- As a user, I want to see all my projects at a glance so I can quickly assess overall status
- As a user, I want to see real-time activity from my agents so I stay informed of progress
- As a user, I want to quickly create a new project without leaving the dashboard
- As a user, I want to filter and sort my projects to find specific ones easily
- As a user, I want to switch between grid and list views based on my preference
- As a new user, I want a clear call-to-action to create my first project
## Key Screens
### 1. Navigation Header
- Syndarix logo with link to prototypes index
- Global search bar (placeholder) for searching projects, issues, and agents
- Notifications bell with badge count for pending approvals
- User menu dropdown with profile, settings, and logout options
### 2. Welcome Section
- Personalized welcome message with user name
- Quick summary stats (active projects, working agents)
- Prominent "New Project" button
### 3. Quick Stats Cards
- Active Projects count with icon
- Agents Active count
- Open Issues total across all projects
- Pending Approvals requiring user action
### 4. Projects Section
- **Grid View**: Card-based layout showing project details
- Project icon with status-based coloring
- Status badge (Active, Paused, Completed, Archived)
- Complexity indicator (3-dot system)
- Quick stats grid (issues, agents, current sprint)
- Progress bar with percentage
- Last activity timestamp
- **List View**: Compact row-based layout for data-dense viewing
- All key information in a single scannable row
- Responsive - hides some columns on smaller screens
- **Controls**: Filter by status, sort options, view toggle
- **Empty State**: For new users with no projects (toggle available for demo)
### 5. Activity Feed (Right Sidebar)
- Real-time connection indicator (Live/Reconnecting)
- Chronological list of events across all projects
- Event types: agent messages, issue updates, sprint events, approvals
- Project attribution for each event
- Action buttons for pending approvals
- "View All Activity" link
### 6. Performance Summary
- Average sprint velocity
- Issues resolved (7-day window)
- Agent uptime percentage
- Average approval response time
## User Flow
1. User logs in and lands on the main dashboard
2. User scans quick stats to get an overview of platform status
3. User reviews the projects list to see current work
4. User optionally switches between grid/list views or applies filters
5. User monitors activity feed for real-time updates
6. If notifications badge shows pending items, user reviews approvals
7. User clicks a project card to navigate to the project dashboard
8. Alternatively, user clicks "New Project" to start a new project
## Design Decisions
### Layout
- Responsive 4-column grid on desktop: 3 columns for main content, 1 for sidebar
- Sidebar collapses below main content on mobile
- Sticky navigation header for easy access to search and notifications
### Project Cards (Grid View)
- Fixed height cards for visual consistency in grid
- Three-column quick stats (issues, agents, sprint) for rapid scanning
- Progress bar as the main visual indicator of project health
- Complexity indicator uses a 3-dot system (low/medium/high)
### Project Rows (List View)
- All critical information visible without scrolling
- Progressive disclosure: less info on smaller screens
- Clear click affordance with hover states and chevron
### Status Colors
- Active: Green (positive, working)
- Paused: Yellow (attention, on hold)
- Completed: Blue (success, finished)
- Archived: Gray (inactive, historical)
### Activity Feed
- Live connection indicator gives confidence in real-time updates
- Events grouped by time (relative timestamps)
- Action items (approvals) highlighted with yellow background
- Limited to 8 items with "View All" for full history
### Empty State
- Friendly message for new users
- Clear call-to-action to create first project
- Dashed border to indicate "fill me" visually
## States
### Loading
- Skeleton cards in grid view
- Skeleton rows in list view
- Activity feed shows loading placeholders
- Stats cards show placeholder numbers
### Empty
- "No projects yet" message with illustration
- Encouragement text explaining the value proposition
- Prominent "Create Your First Project" button
- Toggle available in prototype to switch between empty/populated
### Error
- Toast notification for failed operations
- Retry buttons where applicable
- Activity feed shows "Connection lost" with reconnection status
### Real-time Updates
- Activity feed updates every 30 seconds (simulated in prototype)
- Connection status indicator shows Live/Reconnecting
- New events animate into the feed at the top
## Responsive Breakpoints
### Desktop (lg: 1024px+)
- 4-column layout (3 main + 1 sidebar)
- Grid view shows 3 project cards per row
- Full activity feed visible
- All stats and filters inline
### Tablet (md: 768px)
- 3-column layout (main takes full width, sidebar below)
- Grid view shows 2 project cards per row
- Search visible in header
- Some list view columns hidden
### Mobile (< 768px)
- Single column, vertical stack
- Grid view shows 1 project card per row
- Search hidden (accessible via icon)
- Compact quick stats (2x2 grid)
- Activity feed collapsible
## Accessibility Notes
- All interactive elements are keyboard navigable
- Status badges use both color AND text labels
- Complexity indicator has title attribute for screen readers
- Activity feed connection status announced to screen readers
- Focus states visible on all buttons and cards
- Color contrast meets WCAG AA standards
- View toggle buttons have aria-labels
## Components Used
- Card, CardHeader, CardTitle, CardContent, CardDescription
- Button (default, outline, ghost, secondary variants)
- Badge (default, secondary, outline variants)
- Input (for search)
- Avatar, AvatarFallback
- DropdownMenu components
- Select, SelectContent, SelectItem, SelectTrigger, SelectValue
- Separator
- Lucide icons for visual indicators
## Interactive Features (Prototype)
1. **View Toggle**: Switch between grid and list views
2. **Filter Dropdown**: Filter projects by status (all, active, paused, completed, archived)
3. **Sort Dropdown**: Sort by recent, name, progress, or issues
4. **Empty State Toggle**: Button to switch between empty and populated states for demo
5. **Simulated Activity Feed**: New events appear every 30 seconds
6. **Connection Status**: Randomly disconnects/reconnects to show the indicator
7. **Notifications Dropdown**: Shows pending approval items
8. **User Menu**: Dropdown with profile options
## Questions for Review
1. Is the 4-column layout optimal or should the activity feed take more/less space?
2. Should project cards show more or fewer quick stats?
3. Is the complexity indicator (3 dots) intuitive enough?
4. Should the activity feed show events from ALL projects or have a filter?
5. Is the empty state message compelling enough for new users?
6. Should there be a "pinned projects" feature for frequent access?
7. Is the search bar placement optimal or should it be more prominent?
## How to View
Navigate to: `/prototypes/main-dashboard`
## Mock Data Summary
### Projects (5 total)
- E-Commerce Platform Redesign (Active, High complexity)
- Mobile Banking App (Active, High complexity)
- Internal HR Portal (Paused, Medium complexity)
- API Gateway Modernization (Completed, High complexity)
- Customer Support Chatbot (Archived, Low complexity)
### Activity Events (10 initial)
- Mix of agent messages, issue updates, sprint events, and approval requests
- Timestamps range from 2 minutes ago to 5 hours ago
- One pending approval request highlighted
## Next Steps
After approval:
1. Connect to real user authentication for personalized welcome
2. Implement actual project list API integration
3. Connect to SSE/WebSocket for real-time activity feed
4. Add project creation modal/wizard
5. Implement global search functionality
6. Add loading skeletons for all data sections
7. Implement persistent view preference (grid/list)
8. Add project quick actions (pause, archive) from dashboard

View File

@@ -1,927 +0,0 @@
'use client';
import { useState, useEffect } from 'react';
import Link from 'next/link';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Input } from '@/components/ui/input';
import { Separator } from '@/components/ui/separator';
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import {
Plus,
Search,
Bell,
LayoutGrid,
List,
ChevronRight,
Bot,
CircleDot,
Clock,
Activity,
Settings,
User,
LogOut,
GitBranch,
MessageSquare,
PlayCircle,
CheckCircle2,
AlertTriangle,
Folder,
FolderOpen,
Zap,
Gauge,
Archive,
Pause,
Filter,
ArrowUpDown,
} from 'lucide-react';
// Mock data for projects
const mockProjects = [
{
id: 'proj-001',
name: 'E-Commerce Platform Redesign',
description: 'Complete redesign of the e-commerce platform with modern UI/UX',
status: 'active',
complexity: 'high',
openIssues: 12,
activeAgents: 4,
currentSprint: 'Sprint 3',
lastActivity: '2 min ago',
progress: 67,
},
{
id: 'proj-002',
name: 'Mobile Banking App',
description: 'Native mobile app for banking services with biometric authentication',
status: 'active',
complexity: 'high',
openIssues: 8,
activeAgents: 5,
currentSprint: 'Sprint 2',
lastActivity: '15 min ago',
progress: 45,
},
{
id: 'proj-003',
name: 'Internal HR Portal',
description: 'Employee self-service portal for HR operations',
status: 'paused',
complexity: 'medium',
openIssues: 5,
activeAgents: 0,
currentSprint: 'Sprint 1',
lastActivity: '2 days ago',
progress: 23,
},
{
id: 'proj-004',
name: 'API Gateway Modernization',
description: 'Migrate legacy API gateway to modern microservices architecture',
status: 'completed',
complexity: 'high',
openIssues: 0,
activeAgents: 0,
currentSprint: 'Sprint 5',
lastActivity: '1 week ago',
progress: 100,
},
{
id: 'proj-005',
name: 'Customer Support Chatbot',
description: 'AI-powered chatbot for customer support automation',
status: 'archived',
complexity: 'low',
openIssues: 0,
activeAgents: 0,
currentSprint: 'Sprint 3',
lastActivity: '1 month ago',
progress: 100,
},
];
// Mock activity events (simulated real-time)
const mockActivityEvents = [
{
id: 'evt-001',
type: 'agent_message',
project: 'E-Commerce Platform Redesign',
projectId: 'proj-001',
agent: 'Product Owner',
message: 'Approved user story #42: Cart checkout flow',
timestamp: new Date(Date.now() - 2 * 60 * 1000),
icon: MessageSquare,
},
{
id: 'evt-002',
type: 'issue_update',
project: 'Mobile Banking App',
projectId: 'proj-002',
agent: 'Backend Engineer',
message: 'Moved issue #38 to "In Review"',
timestamp: new Date(Date.now() - 8 * 60 * 1000),
icon: GitBranch,
},
{
id: 'evt-003',
type: 'agent_status',
project: 'E-Commerce Platform Redesign',
projectId: 'proj-001',
agent: 'Frontend Engineer',
message: 'Started working on issue #45',
timestamp: new Date(Date.now() - 15 * 60 * 1000),
icon: PlayCircle,
},
{
id: 'evt-004',
type: 'sprint_event',
project: 'Mobile Banking App',
projectId: 'proj-002',
agent: 'System',
message: 'Sprint 2 daily standup completed',
timestamp: new Date(Date.now() - 45 * 60 * 1000),
icon: CheckCircle2,
},
{
id: 'evt-005',
type: 'approval_request',
project: 'E-Commerce Platform Redesign',
projectId: 'proj-001',
agent: 'Architect',
message: 'Requesting approval for API design document',
timestamp: new Date(Date.now() - 60 * 60 * 1000),
icon: AlertTriangle,
requiresAction: true,
},
{
id: 'evt-006',
type: 'agent_message',
project: 'Mobile Banking App',
projectId: 'proj-002',
agent: 'QA Engineer',
message: 'Test suite passed: 142 tests, 0 failures',
timestamp: new Date(Date.now() - 90 * 60 * 1000),
icon: CheckCircle2,
},
{
id: 'evt-007',
type: 'issue_update',
project: 'E-Commerce Platform Redesign',
projectId: 'proj-001',
agent: 'Backend Engineer',
message: 'Created PR #78 for payment integration',
timestamp: new Date(Date.now() - 120 * 60 * 1000),
icon: GitBranch,
},
{
id: 'evt-008',
type: 'sprint_event',
project: 'E-Commerce Platform Redesign',
projectId: 'proj-001',
agent: 'System',
message: 'Sprint 3 started with 15 issues',
timestamp: new Date(Date.now() - 180 * 60 * 1000),
icon: PlayCircle,
},
{
id: 'evt-009',
type: 'agent_status',
project: 'Mobile Banking App',
projectId: 'proj-002',
agent: 'Architect',
message: 'Completed security audit review',
timestamp: new Date(Date.now() - 240 * 60 * 1000),
icon: CheckCircle2,
},
{
id: 'evt-010',
type: 'issue_update',
project: 'Mobile Banking App',
projectId: 'proj-002',
agent: 'Frontend Engineer',
message: 'Resolved issue #32: Login screen layout',
timestamp: new Date(Date.now() - 300 * 60 * 1000),
icon: CheckCircle2,
},
];
// Helper function to format relative time
function formatRelativeTime(date: Date): string {
const now = new Date();
const diffMs = now.getTime() - date.getTime();
const diffMins = Math.floor(diffMs / (1000 * 60));
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));
if (diffMins < 1) return 'Just now';
if (diffMins < 60) return `${diffMins} min ago`;
if (diffHours < 24) return `${diffHours} hour${diffHours > 1 ? 's' : ''} ago`;
return `${diffDays} day${diffDays > 1 ? 's' : ''} ago`;
}
// Project status badge component
function ProjectStatusBadge({ status }: { status: string }) {
const variants: Record<string, { label: string; className: string; icon: React.ElementType }> = {
active: {
label: 'Active',
className: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
icon: PlayCircle,
},
paused: {
label: 'Paused',
className: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200',
icon: Pause,
},
completed: {
label: 'Completed',
className: 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200',
icon: CheckCircle2,
},
archived: {
label: 'Archived',
className: 'bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-300',
icon: Archive,
},
};
const variant = variants[status] || variants.active;
const Icon = variant.icon;
return (
<Badge className={`gap-1 ${variant.className}`} variant="outline">
<Icon className="h-3 w-3" />
{variant.label}
</Badge>
);
}
// Complexity indicator component
function ComplexityIndicator({ complexity }: { complexity: string }) {
const variants: Record<string, { label: string; dots: number; color: string }> = {
low: { label: 'Low', dots: 1, color: 'bg-green-500' },
medium: { label: 'Medium', dots: 2, color: 'bg-yellow-500' },
high: { label: 'High', dots: 3, color: 'bg-red-500' },
};
const variant = variants[complexity] || variants.medium;
return (
<div className="flex items-center gap-1" title={`Complexity: ${variant.label}`}>
{[1, 2, 3].map((i) => (
<div
key={i}
className={`h-2 w-2 rounded-full ${i <= variant.dots ? variant.color : 'bg-muted'}`}
/>
))}
</div>
);
}
// Progress bar component
function ProgressBar({ value, size = 'default' }: { value: number; size?: 'default' | 'sm' }) {
const height = size === 'sm' ? 'h-1.5' : 'h-2';
return (
<div className={`w-full rounded-full bg-muted ${height}`}>
<div
className={`rounded-full bg-primary transition-all ${height}`}
style={{ width: `${Math.min(100, Math.max(0, value))}%` }}
/>
</div>
);
}
// Project card component (Grid view)
function ProjectCardGrid({
project,
}: {
project: (typeof mockProjects)[0];
}) {
return (
<Link href={`/prototypes/project-dashboard`}>
<Card className="h-full cursor-pointer transition-all hover:border-primary hover:shadow-lg">
<CardHeader className="pb-3">
<div className="flex items-start justify-between">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-primary/10">
{project.status === 'archived' ? (
<Archive className="h-5 w-5 text-muted-foreground" />
) : project.status === 'completed' ? (
<FolderOpen className="h-5 w-5 text-primary" />
) : (
<Folder className="h-5 w-5 text-primary" />
)}
</div>
<div className="flex flex-col items-end gap-1">
<ProjectStatusBadge status={project.status} />
<ComplexityIndicator complexity={project.complexity} />
</div>
</div>
<CardTitle className="mt-3 line-clamp-1">{project.name}</CardTitle>
<CardDescription className="line-clamp-2">{project.description}</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
{/* Quick stats */}
<div className="grid grid-cols-3 gap-2 text-center">
<div className="rounded-md bg-muted/50 p-2">
<div className="flex items-center justify-center gap-1 text-lg font-semibold">
<CircleDot className="h-4 w-4 text-blue-500" />
{project.openIssues}
</div>
<div className="text-xs text-muted-foreground">Issues</div>
</div>
<div className="rounded-md bg-muted/50 p-2">
<div className="flex items-center justify-center gap-1 text-lg font-semibold">
<Bot className="h-4 w-4 text-green-500" />
{project.activeAgents}
</div>
<div className="text-xs text-muted-foreground">Agents</div>
</div>
<div className="rounded-md bg-muted/50 p-2">
<div className="text-xs font-medium text-primary">{project.currentSprint}</div>
<div className="text-xs text-muted-foreground">Current</div>
</div>
</div>
{/* Progress */}
<div className="space-y-1">
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Progress</span>
<span className="font-medium">{project.progress}%</span>
</div>
<ProgressBar value={project.progress} />
</div>
{/* Last activity */}
<div className="flex items-center justify-between text-xs text-muted-foreground">
<div className="flex items-center gap-1">
<Clock className="h-3 w-3" />
Last activity
</div>
<span>{project.lastActivity}</span>
</div>
</div>
</CardContent>
</Card>
</Link>
);
}
// Project row component (List view)
function ProjectRowList({
project,
}: {
project: (typeof mockProjects)[0];
}) {
return (
<Link href={`/prototypes/project-dashboard`}>
<div className="flex items-center gap-4 rounded-lg border p-4 transition-all hover:border-primary hover:bg-muted/50">
<div className="flex h-10 w-10 shrink-0 items-center justify-center rounded-lg bg-primary/10">
{project.status === 'archived' ? (
<Archive className="h-5 w-5 text-muted-foreground" />
) : project.status === 'completed' ? (
<FolderOpen className="h-5 w-5 text-primary" />
) : (
<Folder className="h-5 w-5 text-primary" />
)}
</div>
<div className="min-w-0 flex-1">
<div className="flex items-center gap-2">
<h3 className="font-medium">{project.name}</h3>
<ProjectStatusBadge status={project.status} />
</div>
<p className="mt-0.5 line-clamp-1 text-sm text-muted-foreground">
{project.description}
</p>
</div>
<div className="hidden items-center gap-6 md:flex">
<div className="flex items-center gap-1 text-sm">
<CircleDot className="h-4 w-4 text-blue-500" />
<span className="font-medium">{project.openIssues}</span>
<span className="text-muted-foreground">issues</span>
</div>
<div className="flex items-center gap-1 text-sm">
<Bot className="h-4 w-4 text-green-500" />
<span className="font-medium">{project.activeAgents}</span>
<span className="text-muted-foreground">agents</span>
</div>
<div className="w-24">
<div className="text-right text-xs text-muted-foreground">{project.progress}%</div>
<ProgressBar value={project.progress} size="sm" />
</div>
</div>
<div className="hidden flex-col items-end gap-1 lg:flex">
<ComplexityIndicator complexity={project.complexity} />
<span className="text-xs text-muted-foreground">{project.lastActivity}</span>
</div>
<ChevronRight className="h-5 w-5 shrink-0 text-muted-foreground" />
</div>
</Link>
);
}
// Empty state component
function EmptyState() {
return (
<Card className="border-dashed">
<CardContent className="flex flex-col items-center justify-center py-16">
<div className="flex h-16 w-16 items-center justify-center rounded-full bg-muted">
<Folder className="h-8 w-8 text-muted-foreground" />
</div>
<h3 className="mt-4 text-lg font-semibold">No projects yet</h3>
<p className="mt-2 max-w-sm text-center text-sm text-muted-foreground">
Get started by creating your first project. Syndarix will help you assemble a team of AI
agents to build your software.
</p>
<Button className="mt-6">
<Plus className="mr-2 h-4 w-4" />
Create Your First Project
</Button>
</CardContent>
</Card>
);
}
// Activity event component
function ActivityEvent({
event,
}: {
event: (typeof mockActivityEvents)[0];
}) {
const Icon = event.icon;
return (
<div className="flex gap-3 py-2">
<div
className={`flex h-8 w-8 shrink-0 items-center justify-center rounded-full ${
event.requiresAction
? 'bg-yellow-100 text-yellow-600 dark:bg-yellow-900 dark:text-yellow-400'
: 'bg-muted text-muted-foreground'
}`}
>
<Icon className="h-4 w-4" />
</div>
<div className="min-w-0 flex-1">
<p className="text-sm">
<span className="font-medium">{event.agent}</span>{' '}
<span className="text-muted-foreground">{event.message}</span>
</p>
<div className="mt-0.5 flex items-center gap-2 text-xs text-muted-foreground">
<span>{event.project}</span>
<span>-</span>
<span>{formatRelativeTime(event.timestamp)}</span>
</div>
{event.requiresAction && (
<Button variant="outline" size="sm" className="mt-2 h-7 text-xs">
Review Request
</Button>
)}
</div>
</div>
);
}
export default function MainDashboardPrototype() {
const [viewMode, setViewMode] = useState<'grid' | 'list'>('grid');
const [showEmptyState, setShowEmptyState] = useState(false);
const [statusFilter, setStatusFilter] = useState<string>('all');
const [sortBy, setSortBy] = useState<string>('recent');
const [activityEvents, setActivityEvents] = useState(mockActivityEvents);
const [isConnected, setIsConnected] = useState(true);
// Simulate real-time activity feed updates
useEffect(() => {
const interval = setInterval(() => {
// Randomly add a new event every 30 seconds
const eventTypes = ['agent_message', 'issue_update', 'agent_status'];
const agents = ['Product Owner', 'Backend Engineer', 'Frontend Engineer', 'QA Engineer'];
const messages = [
'Updated documentation for module #12',
'Completed code review for PR #89',
'Started implementing feature #34',
'Resolved merge conflict in main branch',
'Added unit tests for auth module',
];
const projects = mockProjects.filter((p) => p.status === 'active');
if (projects.length > 0 && Math.random() > 0.7) {
const newEvent = {
id: `evt-${Date.now()}`,
type: eventTypes[Math.floor(Math.random() * eventTypes.length)],
project: projects[Math.floor(Math.random() * projects.length)].name,
projectId: projects[Math.floor(Math.random() * projects.length)].id,
agent: agents[Math.floor(Math.random() * agents.length)],
message: messages[Math.floor(Math.random() * messages.length)],
timestamp: new Date(),
icon: MessageSquare,
requiresAction: false,
};
setActivityEvents((prev) => [newEvent, ...prev.slice(0, 9)]);
}
}, 30000);
return () => clearInterval(interval);
}, []);
// Simulate connection status
useEffect(() => {
const interval = setInterval(() => {
// Randomly disconnect for demo purposes (rare)
if (Math.random() > 0.95) {
setIsConnected(false);
setTimeout(() => setIsConnected(true), 3000);
}
}, 10000);
return () => clearInterval(interval);
}, []);
// Filter and sort projects
const filteredProjects = mockProjects
.filter((project) => {
if (statusFilter === 'all') return true;
return project.status === statusFilter;
})
.sort((a, b) => {
switch (sortBy) {
case 'name':
return a.name.localeCompare(b.name);
case 'progress':
return b.progress - a.progress;
case 'issues':
return b.openIssues - a.openIssues;
default: // recent
return 0; // Keep original order (already sorted by recent)
}
});
const activeProjectsCount = mockProjects.filter((p) => p.status === 'active').length;
const totalAgentsActive = mockProjects.reduce((sum, p) => sum + p.activeAgents, 0);
const pendingApprovals = activityEvents.filter((e) => e.requiresAction).length;
return (
<div className="min-h-screen bg-background">
{/* Navigation Header */}
<nav className="sticky top-0 z-50 border-b bg-card">
<div className="container mx-auto flex h-14 items-center justify-between px-4">
<div className="flex items-center gap-4">
<Link href="/prototypes" className="flex items-center gap-2">
<div className="flex h-8 w-8 items-center justify-center rounded-lg bg-primary">
<Zap className="h-5 w-5 text-primary-foreground" />
</div>
<span className="font-semibold text-primary">Syndarix</span>
</Link>
</div>
{/* Global Search */}
<div className="hidden max-w-md flex-1 px-8 md:block">
<div className="relative">
<Search className="absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground" />
<Input
placeholder="Search projects, issues, agents..."
className="w-full pl-9"
/>
</div>
</div>
{/* Right side actions */}
<div className="flex items-center gap-2">
{/* Notifications */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" size="icon" className="relative">
<Bell className="h-5 w-5" />
{pendingApprovals > 0 && (
<span className="absolute -right-0.5 -top-0.5 flex h-4 w-4 items-center justify-center rounded-full bg-destructive text-[10px] font-medium text-destructive-foreground">
{pendingApprovals}
</span>
)}
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end" className="w-80">
<DropdownMenuLabel>Notifications</DropdownMenuLabel>
<DropdownMenuSeparator />
{activityEvents
.filter((e) => e.requiresAction)
.slice(0, 3)
.map((event) => (
<DropdownMenuItem key={event.id} className="flex-col items-start p-3">
<div className="font-medium">{event.agent}</div>
<div className="text-sm text-muted-foreground">{event.message}</div>
<div className="mt-1 text-xs text-muted-foreground">
{formatRelativeTime(event.timestamp)}
</div>
</DropdownMenuItem>
))}
{pendingApprovals === 0 && (
<div className="p-4 text-center text-sm text-muted-foreground">
No pending notifications
</div>
)}
</DropdownMenuContent>
</DropdownMenu>
<Separator orientation="vertical" className="mx-2 h-6" />
{/* User Menu */}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="ghost" className="gap-2">
<Avatar className="h-8 w-8">
<AvatarFallback>JD</AvatarFallback>
</Avatar>
<span className="hidden md:inline">John Doe</span>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>My Account</DropdownMenuLabel>
<DropdownMenuSeparator />
<DropdownMenuItem>
<User className="mr-2 h-4 w-4" />
Profile
</DropdownMenuItem>
<DropdownMenuItem>
<Settings className="mr-2 h-4 w-4" />
Settings
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem className="text-destructive">
<LogOut className="mr-2 h-4 w-4" />
Log out
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</nav>
<div className="container mx-auto px-4 py-6">
<div className="grid gap-6 lg:grid-cols-4">
{/* Main Content */}
<div className="space-y-6 lg:col-span-3">
{/* Welcome Header */}
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div>
<h1 className="text-2xl font-bold">Welcome back, John</h1>
<p className="text-muted-foreground">
{activeProjectsCount} active project{activeProjectsCount !== 1 ? 's' : ''},{' '}
{totalAgentsActive} agent{totalAgentsActive !== 1 ? 's' : ''} working
</p>
</div>
<Button>
<Plus className="mr-2 h-4 w-4" />
New Project
</Button>
</div>
{/* Quick Stats */}
<div className="grid grid-cols-2 gap-4 sm:grid-cols-4">
<Card>
<CardContent className="flex items-center gap-3 p-4">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-green-100 dark:bg-green-900">
<PlayCircle className="h-5 w-5 text-green-600 dark:text-green-400" />
</div>
<div>
<div className="text-2xl font-bold">{activeProjectsCount}</div>
<div className="text-xs text-muted-foreground">Active Projects</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="flex items-center gap-3 p-4">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-blue-100 dark:bg-blue-900">
<Bot className="h-5 w-5 text-blue-600 dark:text-blue-400" />
</div>
<div>
<div className="text-2xl font-bold">{totalAgentsActive}</div>
<div className="text-xs text-muted-foreground">Agents Active</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="flex items-center gap-3 p-4">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-purple-100 dark:bg-purple-900">
<CircleDot className="h-5 w-5 text-purple-600 dark:text-purple-400" />
</div>
<div>
<div className="text-2xl font-bold">
{mockProjects.reduce((sum, p) => sum + p.openIssues, 0)}
</div>
<div className="text-xs text-muted-foreground">Open Issues</div>
</div>
</CardContent>
</Card>
<Card>
<CardContent className="flex items-center gap-3 p-4">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-yellow-100 dark:bg-yellow-900">
<AlertTriangle className="h-5 w-5 text-yellow-600 dark:text-yellow-400" />
</div>
<div>
<div className="text-2xl font-bold">{pendingApprovals}</div>
<div className="text-xs text-muted-foreground">Pending Approvals</div>
</div>
</CardContent>
</Card>
</div>
{/* Projects Section */}
<Card>
<CardHeader>
<div className="flex flex-col gap-4 sm:flex-row sm:items-center sm:justify-between">
<div>
<CardTitle>Projects</CardTitle>
<CardDescription>
Manage your software projects and track AI agent progress
</CardDescription>
</div>
<div className="flex flex-wrap items-center gap-2">
{/* Demo toggle for empty state */}
<Button
variant="outline"
size="sm"
onClick={() => setShowEmptyState(!showEmptyState)}
className="text-xs"
>
{showEmptyState ? 'Show Projects' : 'Show Empty State'}
</Button>
<Separator orientation="vertical" className="hidden h-6 sm:block" />
{/* Filter */}
<Select value={statusFilter} onValueChange={setStatusFilter}>
<SelectTrigger className="w-32">
<Filter className="mr-2 h-4 w-4" />
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="all">All Status</SelectItem>
<SelectItem value="active">Active</SelectItem>
<SelectItem value="paused">Paused</SelectItem>
<SelectItem value="completed">Completed</SelectItem>
<SelectItem value="archived">Archived</SelectItem>
</SelectContent>
</Select>
{/* Sort */}
<Select value={sortBy} onValueChange={setSortBy}>
<SelectTrigger className="w-32">
<ArrowUpDown className="mr-2 h-4 w-4" />
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="recent">Recent</SelectItem>
<SelectItem value="name">Name</SelectItem>
<SelectItem value="progress">Progress</SelectItem>
<SelectItem value="issues">Issues</SelectItem>
</SelectContent>
</Select>
{/* View Toggle */}
<div className="flex rounded-md border">
<Button
variant={viewMode === 'grid' ? 'secondary' : 'ghost'}
size="sm"
className="rounded-r-none"
onClick={() => setViewMode('grid')}
aria-label="Grid view"
>
<LayoutGrid className="h-4 w-4" />
</Button>
<Button
variant={viewMode === 'list' ? 'secondary' : 'ghost'}
size="sm"
className="rounded-l-none"
onClick={() => setViewMode('list')}
aria-label="List view"
>
<List className="h-4 w-4" />
</Button>
</div>
</div>
</div>
</CardHeader>
<CardContent>
{showEmptyState ? (
<EmptyState />
) : viewMode === 'grid' ? (
<div className="grid gap-4 sm:grid-cols-2 xl:grid-cols-3">
{filteredProjects.map((project) => (
<ProjectCardGrid key={project.id} project={project} />
))}
</div>
) : (
<div className="space-y-2">
{filteredProjects.map((project) => (
<ProjectRowList key={project.id} project={project} />
))}
</div>
)}
{filteredProjects.length === 0 && !showEmptyState && (
<div className="py-8 text-center text-muted-foreground">
No projects match the selected filters
</div>
)}
</CardContent>
</Card>
</div>
{/* Right Sidebar - Activity Feed */}
<div className="space-y-6">
<Card>
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<CardTitle className="flex items-center gap-2 text-lg">
<Activity className="h-5 w-5" />
Activity Feed
</CardTitle>
<div className="flex items-center gap-2">
{isConnected ? (
<Badge variant="outline" className="gap-1 text-xs">
<span className="h-2 w-2 animate-pulse rounded-full bg-green-500" />
Live
</Badge>
) : (
<Badge variant="outline" className="gap-1 text-xs text-muted-foreground">
<span className="h-2 w-2 rounded-full bg-gray-400" />
Reconnecting...
</Badge>
)}
</div>
</div>
<CardDescription>Real-time updates from all projects</CardDescription>
</CardHeader>
<CardContent>
<div className="divide-y">
{activityEvents.slice(0, 8).map((event) => (
<ActivityEvent key={event.id} event={event} />
))}
</div>
<Button variant="ghost" className="mt-4 w-full text-sm">
View All Activity
<ChevronRight className="ml-1 h-4 w-4" />
</Button>
</CardContent>
</Card>
{/* Quick Performance Stats */}
<Card>
<CardHeader className="pb-3">
<CardTitle className="flex items-center gap-2 text-lg">
<Gauge className="h-5 w-5" />
Performance
</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Avg. Sprint Velocity</span>
<span className="font-medium">24 pts</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Issues Resolved (7d)</span>
<span className="font-medium">42</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Agent Uptime</span>
<span className="font-medium text-green-600">99.8%</span>
</div>
<div className="flex justify-between text-sm">
<span className="text-muted-foreground">Approval Response</span>
<span className="font-medium">2.3 hrs avg</span>
</div>
</div>
</CardContent>
</Card>
</div>
</div>
</div>
</div>
);
}

View File

@@ -9,6 +9,7 @@ import {
CardTitle,
} from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import {
LayoutDashboard,
Bot,
@@ -17,24 +18,18 @@ import {
ChevronRight,
Wand2,
Home,
CheckCircle2,
ArrowRight,
} from 'lucide-react';
const prototypes = [
const implementedPrototypes = [
{
id: 'main-dashboard',
title: 'Main Dashboard',
description: 'Landing page with projects list, activity feed, and quick stats overview',
icon: Home,
issue: '#47',
status: 'ready',
features: [
'Projects grid/list view with status badges',
'Real-time activity feed',
'Quick stats overview',
'Filter and sort controls',
'Empty state for new users',
'Responsive layout',
],
implementedAt: '/projects',
},
{
id: 'project-dashboard',
@@ -42,14 +37,7 @@ const prototypes = [
description: 'Comprehensive view of project status, agents, sprints, and activity',
icon: LayoutDashboard,
issue: '#36',
status: 'ready',
features: [
'Project header with status badges',
'Agent panel with status indicators',
'Sprint progress and burndown chart',
'Issue summary sidebar',
'Recent activity feed',
],
implementedAt: '/projects/[id]',
},
{
id: 'agent-configuration',
@@ -57,15 +45,7 @@ const prototypes = [
description: 'Create and manage agent type templates with model and permission settings',
icon: Bot,
issue: '#37',
status: 'ready',
features: [
'Agent type list view',
'Agent type detail view',
'Tabbed editor interface',
'Model and parameter configuration',
'MCP permission management',
'Personality prompt editor',
],
implementedAt: '/agents',
},
{
id: 'issue-management',
@@ -73,15 +53,7 @@ const prototypes = [
description: 'List and detail views for project issues with filtering and workflow actions',
icon: CircleDot,
issue: '#38',
status: 'ready',
features: [
'Filterable issue table',
'Sortable columns',
'Bulk action support',
'Sync status indicators',
'Status workflow buttons',
'Activity timeline',
],
implementedAt: '/projects/[id]/issues',
},
{
id: 'activity-feed',
@@ -89,15 +61,7 @@ const prototypes = [
description: 'Real-time event feed with filtering, grouping, and action handling',
icon: Activity,
issue: '#39',
status: 'ready',
features: [
'Real-time connection indicator',
'Time-based event grouping',
'Event type filtering',
'Expandable event details',
'Approval request handling',
'Search functionality',
],
implementedAt: '/activity',
},
{
id: 'project-wizard',
@@ -105,15 +69,7 @@ const prototypes = [
description: 'Guided onboarding flow for creating new projects with AI agent configuration',
icon: Wand2,
issue: '#49',
status: 'ready',
features: [
'6-step guided wizard flow',
'Complexity assessment selector',
'Client mode selection (Technical/Auto)',
'Autonomy level with approval matrix',
'Agent chat preview placeholder',
'Review and confirmation step',
],
implementedAt: '/projects/new',
},
];
@@ -126,91 +82,119 @@ export default function PrototypesIndex() {
<div>
<h1 className="text-3xl font-bold">Syndarix UI Prototypes</h1>
<p className="mt-2 text-muted-foreground">
Interactive design prototypes for Phase 1 features. Click a prototype to view and interact with it.
</p>
<p className="mt-1 text-sm text-muted-foreground">
These prototypes are navigable demos - not production code. They demonstrate UI/UX concepts for approval before implementation.
This page serves as a staging area for new UI prototypes before implementation.
</p>
<div className="mt-4 flex items-center gap-2">
<CheckCircle2 className="h-5 w-5 text-green-500" />
<span className="text-sm font-medium text-green-600">
All Phase 1 prototypes have been implemented!
</span>
</div>
</div>
{/* Prototype Cards */}
<div className="grid gap-6 md:grid-cols-2">
{prototypes.map((proto) => {
const Icon = proto.icon;
return (
<Link key={proto.id} href={`/prototypes/${proto.id}`}>
<Card className="h-full cursor-pointer transition-all hover:border-primary hover:shadow-lg">
<CardHeader>
<div className="flex items-start justify-between">
<div className="flex h-12 w-12 items-center justify-center rounded-lg bg-primary/10">
<Icon className="h-6 w-6 text-primary" />
{/* Implemented Prototypes */}
<Card>
<CardHeader>
<CardTitle>Implemented Features</CardTitle>
<CardDescription>
These prototypes have been approved and implemented in the main application.
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-3">
{implementedPrototypes.map((proto) => {
const Icon = proto.icon;
return (
<div
key={proto.id}
className="flex items-center justify-between rounded-lg border p-3"
>
<div className="flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-lg bg-green-100 dark:bg-green-900/20">
<Icon className="h-5 w-5 text-green-600 dark:text-green-400" />
</div>
<div className="flex gap-2">
<Badge variant="outline">{proto.issue}</Badge>
<Badge
variant={proto.status === 'ready' ? 'default' : 'secondary'}
>
{proto.status === 'ready' ? 'Ready for Review' : proto.status}
</Badge>
<div>
<p className="font-medium">{proto.title}</p>
<p className="text-sm text-muted-foreground">{proto.description}</p>
</div>
</div>
<CardTitle className="mt-4">{proto.title}</CardTitle>
<CardDescription>{proto.description}</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-2">
<p className="text-sm font-medium">Key Features:</p>
<ul className="space-y-1">
{proto.features.slice(0, 4).map((feature) => (
<li
key={feature}
className="flex items-center gap-2 text-sm text-muted-foreground"
>
<ChevronRight className="h-3 w-3" />
{feature}
</li>
))}
{proto.features.length > 4 && (
<li className="text-sm text-muted-foreground">
+{proto.features.length - 4} more...
</li>
)}
</ul>
<div className="flex items-center gap-2">
<Badge variant="outline" className="text-green-600">
{proto.issue}
</Badge>
<Badge variant="secondary">Implemented</Badge>
</div>
</CardContent>
</Card>
</Link>
);
})}
</div>
</div>
);
})}
</div>
</CardContent>
</Card>
{/* Instructions */}
{/* Instructions for New Prototypes */}
<Card className="bg-muted/50">
<CardHeader>
<CardTitle className="text-lg">Reviewing Prototypes</CardTitle>
<CardTitle className="text-lg">Creating New Prototypes</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
<div>
<h4 className="font-medium">What to Look For:</h4>
<h4 className="font-medium">Workflow:</h4>
<ul className="mt-2 space-y-1 text-sm text-muted-foreground">
<li>- Layout and visual hierarchy</li>
<li>- Information density and readability</li>
<li>- User flow and navigation patterns</li>
<li>- Interactive elements and feedback</li>
<li>- Responsive behavior (resize browser)</li>
<li className="flex items-start gap-2">
<ChevronRight className="mt-0.5 h-4 w-4 flex-shrink-0" />
<span>Create a Gitea issue with the &quot;design&quot; label</span>
</li>
<li className="flex items-start gap-2">
<ChevronRight className="mt-0.5 h-4 w-4 flex-shrink-0" />
<span>Build interactive prototype in this directory</span>
</li>
<li className="flex items-start gap-2">
<ChevronRight className="mt-0.5 h-4 w-4 flex-shrink-0" />
<span>Get user approval on the design</span>
</li>
<li className="flex items-start gap-2">
<ChevronRight className="mt-0.5 h-4 w-4 flex-shrink-0" />
<span>Implement in the main app following design system</span>
</li>
<li className="flex items-start gap-2">
<ChevronRight className="mt-0.5 h-4 w-4 flex-shrink-0" />
<span>Remove prototype after implementation</span>
</li>
</ul>
</div>
<div>
<h4 className="font-medium">Providing Feedback:</h4>
<h4 className="font-medium">Prototype Guidelines:</h4>
<ul className="mt-2 space-y-1 text-sm text-muted-foreground">
<li>- Comment on the related Gitea issue</li>
<li>- Specify what works and what needs changes</li>
<li>- Suggest alternatives where applicable</li>
<li>- Approve when ready for implementation</li>
<li>- Use mock data, no backend integration required</li>
<li>- Focus on UX flows and visual hierarchy</li>
<li>- Make it interactive and navigable</li>
<li>- Document decisions in issue comments</li>
</ul>
</div>
</CardContent>
</Card>
{/* Quick Links */}
<div className="flex flex-wrap gap-4">
<Button asChild variant="outline">
<Link href="/" className="gap-2">
<Home className="h-4 w-4" />
Home
</Link>
</Button>
<Button asChild variant="outline">
<Link href="/dev" className="gap-2">
<LayoutDashboard className="h-4 w-4" />
Design System
</Link>
</Button>
<Button asChild>
<Link href="/projects" className="gap-2">
View Implemented Features
<ArrowRight className="h-4 w-4" />
</Link>
</Button>
</div>
</div>
</div>
</div>

View File

@@ -1,151 +0,0 @@
# Project Dashboard - Design Prototype
## Overview
The Project Dashboard provides a comprehensive view of a Syndarix project, allowing users to monitor agent activity, sprint progress, and issue status at a glance. This is the primary interface users will see when managing an AI-driven software project.
## User Stories
- As a project owner, I want to see the current status of my project at a glance so I can quickly understand progress
- As a project owner, I want to monitor which agents are active and what they're working on
- As a project owner, I want to track sprint progress and identify blockers
- As a project owner, I want to see recent activity to stay informed of project developments
- As a project owner, I want to take quick actions (pause, run sprint) without navigating away
## Key Screens
### 1. Header Section
- Project name with status badge (In Progress, Completed, Paused, Blocked)
- Autonomy level indicator (Full Control, Milestone, Autonomous)
- Quick action buttons (Pause Project, Run Sprint)
- Breadcrumb navigation
### 2. Agent Panel
- List of all project agents with avatars
- Real-time status indicators (active = green, idle = yellow, pending = gray)
- Current task description for each agent
- Last activity timestamp
- Quick access to agent management
### 3. Sprint Overview
- Current sprint name and date range
- Sprint progress bar with percentage
- Issue statistics grid (Completed, In Progress, Blocked, To Do)
- Visual burndown chart with ideal vs actual lines
- Sprint selector dropdown for historical data
### 4. Issue Summary (Right Sidebar)
- Count of issues by status with color-coded icons
- Quick links to view all issues
- Compact vertical layout for easy scanning
### 5. Recent Activity Feed
- Chronological list of recent project events
- Event type icons (messages, issue updates, status changes, approval requests)
- Agent attribution for each event
- Highlighted items requiring user action
- Quick action buttons for approval requests
## User Flow
1. User navigates to project from project list
2. Dashboard loads with current project state
3. User can scan agent panel to see what's happening
4. User reviews sprint progress and issue counts
5. User checks activity feed for recent updates
6. If approval request exists, user can review and respond
7. User can pause/run sprint or navigate to detailed views
## Design Decisions
### Layout
- Three-column layout on desktop: 2/3 for main content, 1/3 for sidebar
- Main content prioritizes agent panel and sprint overview
- Sidebar contains issue summary and activity feed
- Responsive: stacks vertically on mobile
### Visual Hierarchy
- Project name and status badges are most prominent
- Agent status indicators use traffic light colors (green/yellow/red)
- Action buttons are clearly visible but not overwhelming
- Activity feed uses subtle icons to indicate event types
### Color System
- Status colors follow intuitive patterns:
- Green: Completed, Active, Success
- Blue: In Progress, Primary actions
- Yellow/Orange: Idle, Warning, Pending approval
- Red: Blocked, Error, Critical
- Uses design system semantic colors (primary, destructive, muted)
### Spacing and Cards
- Each major section is contained in a Card component
- Consistent 24px (gap-6) spacing between cards
- 16px (p-4) internal padding for compact elements
- 24px (p-6) padding for card headers and content
## States
### Loading
- Skeleton placeholders for agent list
- Skeleton for sprint stats grid
- Animated placeholder for burndown chart
- Activity feed shows skeleton items
### Empty
- "No agents assigned" message in agent panel
- "No sprint active" in sprint overview
- "No recent activity" in activity feed
### Error
- Error alert with retry button for failed data loads
- Graceful degradation showing last known state
## Responsive Breakpoints
### Desktop (lg: 1024px+)
- Full three-column layout
- Side-by-side agent cards
- Full burndown chart
### Tablet (md: 768px)
- Two-column layout (main + sidebar stack)
- Agent list in single column
- Compact sprint stats
### Mobile (< 768px)
- Single column, vertical stack
- Collapsible sections for agents
- Simplified burndown (or hidden)
- Touch-friendly action buttons
## Accessibility Notes
- All status indicators have aria-labels
- Color is not the only indicator of status (also uses icons and text)
- Keyboard navigation for all interactive elements
- Focus visible indicators on all buttons
- Screen reader announces activity feed updates
## Components Used
- Card, CardHeader, CardTitle, CardContent, CardDescription
- Button (default, outline, ghost variants)
- Badge (default, secondary, outline variants)
- Tabs, TabsList, TabsTrigger, TabsContent
- Select, SelectContent, SelectItem, SelectTrigger, SelectValue
- Separator
- Lucide icons for visual indicators
## Questions for Review
1. Should the burndown chart be more detailed or is the simplified version sufficient?
2. Is the activity feed length appropriate (5 items) or should more be shown?
3. Should agent cards be expandable to show more details?
4. Is the autonomy level badge prominent enough?
5. Should there be a global "stop all agents" emergency button?
## How to View
Navigate to: `/prototypes/project-dashboard`
## Next Steps
After approval:
1. Implement real-time WebSocket updates for activity feed
2. Connect to actual project and agent APIs
3. Add loading skeletons
4. Implement sprint selector functionality
5. Add agent action dropdown menus

View File

@@ -1,586 +0,0 @@
'use client';
import { useState } from 'react';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { Separator } from '@/components/ui/separator';
import {
Bot,
Activity,
AlertCircle,
CheckCircle2,
Clock,
PlayCircle,
PauseCircle,
Settings,
MessageSquare,
GitPullRequest,
GitBranch,
MoreVertical,
TrendingUp,
Users,
CircleDot,
ChevronRight,
} from 'lucide-react';
// Mock data for the prototype
const mockProject = {
id: 'proj-001',
name: 'E-Commerce Platform Redesign',
description: 'Complete redesign of the e-commerce platform with modern UI/UX',
status: 'in_progress',
autonomyLevel: 'milestone',
currentSprint: 'Sprint 3',
sprintProgress: 67,
startDate: '2025-01-15',
estimatedEnd: '2025-03-15',
};
const mockAgents = [
{
id: 'agent-001',
name: 'Product Owner',
type: 'product_owner',
status: 'active',
currentTask: 'Reviewing user story acceptance criteria',
lastActivity: '2 min ago',
avatar: 'PO',
},
{
id: 'agent-002',
name: 'Architect',
type: 'architect',
status: 'active',
currentTask: 'Designing API contract for checkout flow',
lastActivity: '5 min ago',
avatar: 'AR',
},
{
id: 'agent-003',
name: 'Backend Engineer',
type: 'engineer',
status: 'idle',
currentTask: 'Waiting for architecture review',
lastActivity: '15 min ago',
avatar: 'BE',
},
{
id: 'agent-004',
name: 'Frontend Engineer',
type: 'engineer',
status: 'active',
currentTask: 'Implementing product catalog component',
lastActivity: '1 min ago',
avatar: 'FE',
},
{
id: 'agent-005',
name: 'QA Engineer',
type: 'qa',
status: 'pending',
currentTask: 'Preparing test cases for Sprint 3',
lastActivity: '30 min ago',
avatar: 'QA',
},
];
const mockSprintData = {
name: 'Sprint 3',
startDate: '2025-01-27',
endDate: '2025-02-10',
totalIssues: 15,
completed: 8,
inProgress: 4,
blocked: 1,
todo: 2,
burndown: [
{ day: 1, remaining: 45, ideal: 45 },
{ day: 2, remaining: 42, ideal: 42 },
{ day: 3, remaining: 38, ideal: 39 },
{ day: 4, remaining: 35, ideal: 36 },
{ day: 5, remaining: 30, ideal: 33 },
{ day: 6, remaining: 28, ideal: 30 },
{ day: 7, remaining: 25, ideal: 27 },
{ day: 8, remaining: 20, ideal: 24 },
],
};
const mockActivity = [
{
id: 'act-001',
type: 'agent_message',
agent: 'Product Owner',
message: 'Approved user story #42: Cart checkout flow',
timestamp: '2 min ago',
icon: MessageSquare,
},
{
id: 'act-002',
type: 'issue_update',
agent: 'Backend Engineer',
message: 'Moved issue #38 to "In Review"',
timestamp: '8 min ago',
icon: GitPullRequest,
},
{
id: 'act-003',
type: 'agent_status',
agent: 'Frontend Engineer',
message: 'Started working on issue #45',
timestamp: '15 min ago',
icon: PlayCircle,
},
{
id: 'act-004',
type: 'approval_request',
agent: 'Architect',
message: 'Requesting approval for API design document',
timestamp: '25 min ago',
icon: AlertCircle,
requiresAction: true,
},
{
id: 'act-005',
type: 'sprint_event',
agent: 'System',
message: 'Sprint 3 daily standup completed',
timestamp: '1 hour ago',
icon: Users,
},
];
const mockIssueSummary = {
open: 12,
inProgress: 8,
inReview: 3,
blocked: 2,
done: 45,
total: 70,
};
// Status badge component
function StatusBadge({ status }: { status: string }) {
const variants: Record<string, { label: string; className: string }> = {
in_progress: {
label: 'In Progress',
className: 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-200',
},
completed: {
label: 'Completed',
className: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200',
},
paused: {
label: 'Paused',
className: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900 dark:text-yellow-200',
},
blocked: {
label: 'Blocked',
className: 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-200',
},
};
const variant = variants[status] || variants.in_progress;
return (
<Badge className={variant.className} variant="outline">
{variant.label}
</Badge>
);
}
// Autonomy level badge
function AutonomyBadge({ level }: { level: string }) {
const variants: Record<string, { label: string; description: string }> = {
full_control: { label: 'Full Control', description: 'Approve every action' },
milestone: { label: 'Milestone', description: 'Approve at sprint boundaries' },
autonomous: { label: 'Autonomous', description: 'Only major decisions' },
};
const variant = variants[level] || variants.milestone;
return (
<Badge variant="secondary" className="gap-1">
<CircleDot className="h-3 w-3" />
{variant.label}
</Badge>
);
}
// Agent status indicator
function AgentStatusIndicator({ status }: { status: string }) {
const colors: Record<string, string> = {
active: 'bg-green-500',
idle: 'bg-yellow-500',
pending: 'bg-gray-400',
error: 'bg-red-500',
};
return (
<span
className={`inline-block h-2 w-2 rounded-full ${colors[status] || colors.pending}`}
aria-label={`Status: ${status}`}
/>
);
}
// Simple burndown chart visualization
function BurndownChart({ data }: { data: typeof mockSprintData.burndown }) {
const maxPoints = Math.max(...data.map((d) => Math.max(d.remaining, d.ideal)));
const chartHeight = 120;
const chartWidth = 100;
return (
<div className="relative h-32 w-full">
<svg
viewBox={`0 0 ${chartWidth} ${chartHeight}`}
className="h-full w-full"
preserveAspectRatio="none"
>
{/* Ideal line */}
<polyline
fill="none"
stroke="currentColor"
strokeOpacity="0.3"
strokeWidth="1"
strokeDasharray="4,2"
points={data
.map(
(d, i) =>
`${(i / (data.length - 1)) * chartWidth},${chartHeight - (d.ideal / maxPoints) * chartHeight}`
)
.join(' ')}
/>
{/* Actual line */}
<polyline
fill="none"
stroke="currentColor"
className="text-primary"
strokeWidth="2"
points={data
.map(
(d, i) =>
`${(i / (data.length - 1)) * chartWidth},${chartHeight - (d.remaining / maxPoints) * chartHeight}`
)
.join(' ')}
/>
</svg>
<div className="absolute bottom-0 left-0 right-0 flex justify-between text-xs text-muted-foreground">
<span>Day 1</span>
<span>Day {data.length}</span>
</div>
</div>
);
}
// Progress bar component
function ProgressBar({ value, className }: { value: number; className?: string }) {
return (
<div className={`h-2 w-full rounded-full bg-muted ${className}`}>
<div
className="h-full rounded-full bg-primary transition-all"
style={{ width: `${Math.min(100, Math.max(0, value))}%` }}
/>
</div>
);
}
export default function ProjectDashboardPrototype() {
const [_selectedView, _setSelectedView] = useState('overview');
return (
<div className="min-h-screen bg-background">
{/* Navigation Bar - Prototype placeholder */}
<nav className="border-b bg-card">
<div className="container mx-auto flex h-14 items-center justify-between px-4">
<div className="flex items-center gap-4">
<span className="font-semibold text-primary">Syndarix</span>
<Separator orientation="vertical" className="h-6" />
<span className="text-sm text-muted-foreground">Projects</span>
<ChevronRight className="h-4 w-4 text-muted-foreground" />
<span className="text-sm font-medium">{mockProject.name}</span>
</div>
<div className="flex items-center gap-2">
<Button variant="ghost" size="icon">
<Settings className="h-4 w-4" />
</Button>
</div>
</div>
</nav>
<div className="container mx-auto px-4 py-6">
<div className="space-y-6">
{/* Header Section */}
<div className="flex flex-col gap-4 md:flex-row md:items-start md:justify-between">
<div className="space-y-2">
<div className="flex flex-wrap items-center gap-3">
<h1 className="text-3xl font-bold">{mockProject.name}</h1>
<StatusBadge status={mockProject.status} />
<AutonomyBadge level={mockProject.autonomyLevel} />
</div>
<p className="text-muted-foreground">{mockProject.description}</p>
</div>
<div className="flex gap-2">
<Button variant="outline" size="sm">
<PauseCircle className="mr-2 h-4 w-4" />
Pause Project
</Button>
<Button size="sm">
<PlayCircle className="mr-2 h-4 w-4" />
Run Sprint
</Button>
</div>
</div>
{/* Main Content Grid */}
<div className="grid gap-6 lg:grid-cols-3">
{/* Left Column - Agent Panel & Sprint Overview */}
<div className="space-y-6 lg:col-span-2">
{/* Agent Panel */}
<Card>
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<div>
<CardTitle className="flex items-center gap-2">
<Bot className="h-5 w-5" />
Active Agents
</CardTitle>
<CardDescription>
{mockAgents.filter((a) => a.status === 'active').length} of{' '}
{mockAgents.length} agents working
</CardDescription>
</div>
<Button variant="outline" size="sm">
Manage Agents
</Button>
</div>
</CardHeader>
<CardContent>
<div className="space-y-3">
{mockAgents.map((agent) => (
<div
key={agent.id}
className="flex items-center justify-between rounded-lg border p-3 transition-colors hover:bg-muted/50"
>
<div className="flex items-center gap-3">
<div className="flex h-10 w-10 items-center justify-center rounded-full bg-primary/10 text-sm font-medium text-primary">
{agent.avatar}
</div>
<div>
<div className="flex items-center gap-2">
<span className="font-medium">{agent.name}</span>
<AgentStatusIndicator status={agent.status} />
</div>
<p className="text-sm text-muted-foreground">{agent.currentTask}</p>
</div>
</div>
<div className="flex items-center gap-2">
<span className="text-xs text-muted-foreground">{agent.lastActivity}</span>
<Button variant="ghost" size="icon" className="h-8 w-8">
<MoreVertical className="h-4 w-4" />
</Button>
</div>
</div>
))}
</div>
</CardContent>
</Card>
{/* Sprint Overview */}
<Card>
<CardHeader>
<div className="flex items-center justify-between">
<div>
<CardTitle className="flex items-center gap-2">
<TrendingUp className="h-5 w-5" />
Sprint Overview
</CardTitle>
<CardDescription>
{mockSprintData.name} ({mockSprintData.startDate} - {mockSprintData.endDate})
</CardDescription>
</div>
<Select defaultValue="current">
<SelectTrigger className="w-32">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="current">Current</SelectItem>
<SelectItem value="sprint-2">Sprint 2</SelectItem>
<SelectItem value="sprint-1">Sprint 1</SelectItem>
</SelectContent>
</Select>
</div>
</CardHeader>
<CardContent>
<div className="space-y-6">
{/* Sprint Progress */}
<div className="space-y-2">
<div className="flex justify-between text-sm">
<span>Sprint Progress</span>
<span className="font-medium">{mockProject.sprintProgress}%</span>
</div>
<ProgressBar value={mockProject.sprintProgress} />
</div>
{/* Issue Stats Grid */}
<div className="grid grid-cols-2 gap-4 sm:grid-cols-4">
<div className="rounded-lg border p-3 text-center">
<div className="text-2xl font-bold text-green-600">{mockSprintData.completed}</div>
<div className="text-xs text-muted-foreground">Completed</div>
</div>
<div className="rounded-lg border p-3 text-center">
<div className="text-2xl font-bold text-blue-600">{mockSprintData.inProgress}</div>
<div className="text-xs text-muted-foreground">In Progress</div>
</div>
<div className="rounded-lg border p-3 text-center">
<div className="text-2xl font-bold text-red-600">{mockSprintData.blocked}</div>
<div className="text-xs text-muted-foreground">Blocked</div>
</div>
<div className="rounded-lg border p-3 text-center">
<div className="text-2xl font-bold text-gray-600">{mockSprintData.todo}</div>
<div className="text-xs text-muted-foreground">To Do</div>
</div>
</div>
{/* Burndown Chart */}
<div className="space-y-2">
<h4 className="text-sm font-medium">Burndown Chart</h4>
<BurndownChart data={mockSprintData.burndown} />
<div className="flex items-center justify-center gap-6 text-xs text-muted-foreground">
<span className="flex items-center gap-1">
<span className="h-0.5 w-4 bg-primary" /> Actual
</span>
<span className="flex items-center gap-1">
<span className="h-0.5 w-4 border-t border-dashed border-muted-foreground" />{' '}
Ideal
</span>
</div>
</div>
</div>
</CardContent>
</Card>
</div>
{/* Right Column - Activity & Issue Summary */}
<div className="space-y-6">
{/* Issue Summary */}
<Card>
<CardHeader className="pb-3">
<CardTitle className="flex items-center gap-2 text-lg">
<GitBranch className="h-5 w-5" />
Issue Summary
</CardTitle>
</CardHeader>
<CardContent>
<div className="space-y-3">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<CircleDot className="h-4 w-4 text-blue-500" />
<span className="text-sm">Open</span>
</div>
<span className="font-medium">{mockIssueSummary.open}</span>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<PlayCircle className="h-4 w-4 text-yellow-500" />
<span className="text-sm">In Progress</span>
</div>
<span className="font-medium">{mockIssueSummary.inProgress}</span>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<Clock className="h-4 w-4 text-purple-500" />
<span className="text-sm">In Review</span>
</div>
<span className="font-medium">{mockIssueSummary.inReview}</span>
</div>
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<AlertCircle className="h-4 w-4 text-red-500" />
<span className="text-sm">Blocked</span>
</div>
<span className="font-medium">{mockIssueSummary.blocked}</span>
</div>
<Separator />
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<CheckCircle2 className="h-4 w-4 text-green-500" />
<span className="text-sm">Completed</span>
</div>
<span className="font-medium">{mockIssueSummary.done}</span>
</div>
<div className="pt-2">
<Button variant="outline" className="w-full" size="sm">
View All Issues
</Button>
</div>
</div>
</CardContent>
</Card>
{/* Recent Activity */}
<Card>
<CardHeader className="pb-3">
<div className="flex items-center justify-between">
<CardTitle className="flex items-center gap-2 text-lg">
<Activity className="h-5 w-5" />
Recent Activity
</CardTitle>
<Button variant="ghost" size="sm" className="text-xs">
View All
</Button>
</div>
</CardHeader>
<CardContent>
<div className="space-y-4">
{mockActivity.map((activity) => {
const Icon = activity.icon;
return (
<div key={activity.id} className="flex gap-3">
<div
className={`flex h-8 w-8 shrink-0 items-center justify-center rounded-full ${
activity.requiresAction
? 'bg-yellow-100 text-yellow-600 dark:bg-yellow-900 dark:text-yellow-400'
: 'bg-muted text-muted-foreground'
}`}
>
<Icon className="h-4 w-4" />
</div>
<div className="min-w-0 flex-1">
<p className="text-sm">
<span className="font-medium">{activity.agent}</span>{' '}
<span className="text-muted-foreground">{activity.message}</span>
</p>
<p className="text-xs text-muted-foreground">{activity.timestamp}</p>
{activity.requiresAction && (
<Button variant="outline" size="sm" className="mt-2 h-7 text-xs">
Review Request
</Button>
)}
</div>
</div>
);
})}
</div>
</CardContent>
</Card>
</div>
</div>
</div>
</div>
</div>
);
}

View File

@@ -1,180 +0,0 @@
# Project Creation Wizard - Design Prototype
## Overview
The Project Creation Wizard provides a guided onboarding flow for creating new projects in Syndarix. It walks users through essential configuration steps including naming, complexity assessment, interaction mode selection, and autonomy level settings. This wizard ensures users make informed decisions about how they want to work with AI agents.
## User Stories
- As a new user, I want a guided process to create my first project so I understand all the options available
- As a user, I want to specify my project's complexity so the right resources are allocated
- As a user, I want to choose how I interact with agents (technical vs auto mode) based on my expertise
- As a user, I want to control how much autonomy agents have so I feel comfortable with the workflow
- As a user, I want to review all my selections before creating the project to ensure accuracy
- As a user, I want to see a preview of the agent chat experience to understand what's coming
## Key Screens
### 1. Basic Info (Step 1)
- Project name input (required, minimum 3 characters)
- Description textarea (optional)
- Repository URL input (optional, with git icon)
- Inline validation with error messages
- Helpful descriptions for each field
### 2. Complexity Assessment (Step 2)
- Visual grid of complexity options (Script, Simple, Medium, Complex)
- Each option shows:
- Icon representing complexity level
- Label and description
- Typical scope/timeline
- Real-world examples
- Selectable card pattern with checkmark indicator
### 3. Client Mode Selection (Step 3)
- Two large cards: Technical Mode vs Auto Mode
- Each mode shows:
- Icon and title
- Description
- List of features/capabilities
- Clear differentiation between approaches
### 4. Autonomy Level (Step 4)
- Three options: Full Control, Milestone, Autonomous
- Each option shows:
- Icon, label, description
- Best-for recommendation
- Approval badges for quick scanning
- Detailed approval matrix table below
- Clear indication of what requires approval in each mode
### 5. Agent Chat Placeholder (Step 5)
- Static mockup of future chat interface
- Sample conversation showing Product Owner agent interaction
- "Coming in Phase 4" badge
- Disabled input field with explanation
- Card explaining what to expect in full version
### 6. Review & Create (Step 6)
- Summary cards for all selections:
- Basic Information
- Project Complexity
- Interaction Mode
- Autonomy Level
- Ready-to-create confirmation card
- Create Project button with loading state
## User Flow
1. User clicks "Create Project" or starts wizard
2. Enters project name and optional details
3. Selects complexity level based on project scope
4. Chooses interaction mode (technical or assisted)
5. Sets autonomy level for agent actions
6. Views agent chat preview (informational)
7. Reviews all selections on summary screen
8. Clicks "Create Project" to finalize
9. Sees success screen with navigation options
## Design Decisions
### Wizard Pattern
- Linear 6-step flow with clear progress indication
- Each step is self-contained and focused
- Back/Next navigation with disabled states
- Step indicator shows current position and labels
### Visual Feedback
- Selectable cards highlight on hover and selection
- Checkmark icons confirm selected options
- Primary color treatment for selected states
- Disabled button states prevent premature progression
### Information Architecture
- Progressive disclosure: show details only when relevant
- Complexity options ordered from simple to complex
- Autonomy matrix provides at-a-glance comparison
- Review step consolidates all information
### Form Validation
- Real-time validation with immediate feedback
- Error messages appear below problematic fields
- Required fields clearly marked with asterisk
- Prevents progression until valid
## States
### Loading
- Create button shows spinner during submission
- Text changes to "Creating..." for clarity
### Empty
- Initial state with no selections made
- Next button disabled until requirements met
### Error
- Inline error messages for validation failures
- Red border on invalid inputs
- Descriptive error text guides correction
### Success
- Full-screen success message
- Project name displayed in confirmation
- Clear navigation options (dashboard or create another)
## Responsive Breakpoints
### Desktop (lg: 1024px+)
- 2-column grid for complexity options
- Side-by-side client mode cards
- Full approval matrix table
- Comfortable spacing
### Tablet (md: 768px)
- 2-column grid maintained for complexity
- Review cards in 2-column grid
- Adequate touch targets
### Mobile (< 768px)
- Single column layout throughout
- Stacked complexity cards
- Stacked client mode cards
- Scrollable approval matrix
- Full-width buttons
## Accessibility Notes
- All form inputs have associated labels with htmlFor
- Required fields marked with both asterisk and aria-required
- Error messages connected via aria-describedby
- Keyboard navigation for all selectable cards
- Focus visible indicators on interactive elements
- Screen reader announces step changes
- Color is not sole indicator of selection state
## Components Used
- Card, CardHeader, CardTitle, CardContent, CardDescription
- Button (default, outline, ghost variants)
- Badge (default, secondary, outline variants)
- Input, Textarea
- Label
- Separator
- Lucide icons (extensive use for visual clarity)
- Custom SelectableCard component
## Questions for Review
1. Is 6 steps too many? Should any be combined?
2. Is the complexity naming (Script/Simple/Medium/Complex) intuitive?
3. Should the agent chat step be required or skippable?
4. Is the approval matrix table too detailed or appropriately informative?
5. Should there be a "save draft" option for longer sessions?
6. Is the success screen adequate or should it show more next steps?
## How to View
Navigate to: `/prototypes/project-wizard`
## Next Steps
After approval:
1. Connect to backend API for project creation
2. Implement real form validation with backend rules
3. Add animated transitions between steps
4. Integrate with actual agent chat in Phase 4
5. Add keyboard shortcuts for navigation
6. Implement draft saving functionality
7. Add analytics tracking for wizard completion rates

File diff suppressed because it is too large Load Diff