refactor(frontend): clean up code by consolidating multi-line JSX into single lines where feasible

- Refactored JSX elements to improve readability by collapsing multi-line props and attributes into single lines if their length permits.
- Improved consistency in component imports by grouping and consolidating them.
- No functional changes, purely restructuring for clarity and maintainability.
This commit is contained in:
2026-01-01 11:46:57 +01:00
parent a7ba0f9bd8
commit a4c91cb8c3
77 changed files with 600 additions and 907 deletions

View File

@@ -199,13 +199,10 @@ export default function ActivityFeedPage() {
<BellOff className="h-4 w-4" />
)}
</Button>
<Button
variant="ghost"
size="icon"
onClick={reconnect}
aria-label="Refresh connection"
>
<RefreshCw className={connectionState === 'connecting' ? 'h-4 w-4 animate-spin' : 'h-4 w-4'} />
<Button variant="ghost" size="icon" onClick={reconnect} aria-label="Refresh connection">
<RefreshCw
className={connectionState === 'connecting' ? 'h-4 w-4 animate-spin' : 'h-4 w-4'}
/>
</Button>
</div>
</div>
@@ -214,7 +211,8 @@ export default function ActivityFeedPage() {
{(!isConnected || sseEvents.length === 0) && (
<div className="rounded-lg border border-yellow-200 bg-yellow-50 p-4 dark:border-yellow-800 dark:bg-yellow-950">
<p className="text-sm text-yellow-800 dark:text-yellow-200">
<strong>Demo Mode:</strong> Showing sample events. Connect to a real project to see live updates.
<strong>Demo Mode:</strong> Showing sample events. Connect to a real project to see
live updates.
</p>
</div>
)}

View File

@@ -32,11 +32,7 @@ export default function AgentTypeDetailPage() {
const [viewMode, setViewMode] = useState<ViewMode>(isNew ? 'create' : 'detail');
// Fetch agent type data (skip if creating new)
const {
data: agentType,
isLoading,
error,
} = useAgentType(isNew ? null : id);
const { data: agentType, isLoading, error } = useAgentType(isNew ? null : id);
// Mutations
const createMutation = useCreateAgentType();
@@ -171,7 +167,7 @@ export default function AgentTypeDetailPage() {
<div className="container mx-auto px-4 py-6">
{(viewMode === 'create' || viewMode === 'edit') && (
<AgentTypeForm
agentType={viewMode === 'edit' ? agentType ?? undefined : undefined}
agentType={viewMode === 'edit' ? (agentType ?? undefined) : undefined}
onSubmit={handleSubmit}
onCancel={handleCancel}
isSubmitting={createMutation.isPending || updateMutation.isPending}

View File

@@ -10,13 +10,7 @@
import { use } from 'react';
import Link from 'next/link';
import {
ArrowLeft,
Calendar,
Clock,
ExternalLink,
Edit,
} from 'lucide-react';
import { ArrowLeft, Calendar, Clock, ExternalLink, Edit } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
import { Skeleton } from '@/components/ui/skeleton';
@@ -65,10 +59,7 @@ export default function IssueDetailPage({ params }: IssueDetailPageProps) {
<Link href={`/${locale}/projects/${projectId}/issues`}>
<Button variant="outline">Back to Issues</Button>
</Link>
<Button
variant="outline"
onClick={() => window.location.reload()}
>
<Button variant="outline" onClick={() => window.location.reload()}>
Retry
</Button>
</div>
@@ -171,9 +162,7 @@ export default function IssueDetailPage({ params }: IssueDetailPageProps) {
</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>
<pre className="whitespace-pre-wrap font-sans text-sm">{issue.description}</pre>
</div>
</CardContent>
</Card>

View File

@@ -14,12 +14,7 @@ import { useRouter } from 'next/navigation';
import { Plus, Upload } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { Skeleton } from '@/components/ui/skeleton';
import {
IssueFilters,
IssueTable,
BulkActions,
useIssues,
} from '@/features/issues';
import { IssueFilters, IssueTable, BulkActions, useIssues } from '@/features/issues';
import type { IssueFiltersType, IssueSort } from '@/features/issues';
interface ProjectIssuesPageProps {
@@ -95,11 +90,7 @@ export default function ProjectIssuesPage({ params }: ProjectIssuesPageProps) {
<p className="mt-2 text-sm text-muted-foreground">
Failed to load issues. Please try again later.
</p>
<Button
variant="outline"
className="mt-4"
onClick={() => window.location.reload()}
>
<Button variant="outline" className="mt-4" onClick={() => window.location.reload()}>
Retry
</Button>
</div>
@@ -169,26 +160,15 @@ export default function ProjectIssuesPage({ params }: ProjectIssuesPageProps) {
<div className="flex items-center justify-between text-sm text-muted-foreground">
<span>
Showing {(data.pagination.page - 1) * data.pagination.page_size + 1} to{' '}
{Math.min(
data.pagination.page * data.pagination.page_size,
data.pagination.total
)}{' '}
of {data.pagination.total} issues
{Math.min(data.pagination.page * data.pagination.page_size, data.pagination.total)} of{' '}
{data.pagination.total} issues
</span>
{data.pagination.total_pages > 1 && (
<div className="flex gap-2">
<Button
variant="outline"
size="sm"
disabled={!data.pagination.has_prev}
>
<Button variant="outline" size="sm" disabled={!data.pagination.has_prev}>
Previous
</Button>
<Button
variant="outline"
size="sm"
disabled={!data.pagination.has_next}
>
<Button variant="outline" size="sm" disabled={!data.pagination.has_next}>
Next
</Button>
</div>

View File

@@ -14,44 +14,52 @@ This document contains the comments to be added to each Gitea issue for the desi
The Project Dashboard prototype has been created and is ready for review.
### How to View
1. Start the frontend dev server: `cd frontend && npm run dev`
2. Navigate to: `http://localhost:3000/en/prototypes/project-dashboard`
### What's Included
**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)
**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
**Sprint Overview**
- Current sprint progress bar
- Issue statistics grid (Completed, In Progress, Blocked, To Do)
- Visual burndown chart with ideal vs actual lines
- Sprint selector dropdown
**Issue Summary Sidebar**
- Count of issues by status with color-coded icons
- Quick links to view all issues
**Recent Activity Feed**
- Chronological event list with type icons
- Agent attribution
- Highlighted approval requests with action buttons
### Key Design Decisions
- Three-column layout on desktop (2/3 main, 1/3 sidebar)
- Agent status uses traffic light colors for intuitive understanding
- Burndown chart is simplified for quick scanning
- Activity feed limited to 5 items with "View All" link
### Questions for Review
1. Is the burndown chart detailed enough?
2. Should agent cards be expandable for more details?
3. Is the 5-item activity feed sufficient?
@@ -59,6 +67,7 @@ The Project Dashboard prototype has been created and is ready for review.
**Please review and approve or provide feedback.**
Files:
- `/frontend/src/app/[locale]/prototypes/project-dashboard/page.tsx`
- `/frontend/src/app/[locale]/prototypes/project-dashboard/README.md`
```
@@ -75,6 +84,7 @@ Files:
The Agent Configuration UI prototype has been created and is ready for review.
### How to View
1. Start the frontend dev server: `cd frontend && npm run dev`
2. Navigate to: `http://localhost:3000/en/prototypes/agent-configuration`
@@ -102,18 +112,21 @@ The Agent Configuration UI prototype has been created and is ready for review.
- **Personality Tab**: Large textarea for personality prompt
### Key Design Decisions
- Separate views for browsing, viewing, and editing
- Tabbed editor reduces cognitive load
- MCP permissions show nested scopes when enabled
- Model parameters have helpful descriptions
### User Flows to Test
1. Click any card to see detail view
2. Click "Edit" to see editor view
3. Click "Create Agent Type" for blank editor
4. Navigate tabs in editor
### Questions for Review
1. Is the tabbed editor the right approach?
2. Should expertise be free-form tags or predefined list?
3. Should model parameters have "presets"?
@@ -121,6 +134,7 @@ The Agent Configuration UI prototype has been created and is ready for review.
**Please review and approve or provide feedback.**
Files:
- `/frontend/src/app/[locale]/prototypes/agent-configuration/page.tsx`
- `/frontend/src/app/[locale]/prototypes/agent-configuration/README.md`
```
@@ -137,12 +151,14 @@ Files:
The Issue List and Detail Views prototype has been created and is ready for review.
### How to View
1. Start the frontend dev server: `cd frontend && npm run dev`
2. Navigate to: `http://localhost:3000/en/prototypes/issue-management`
### What's Included
**List View**
- Filterable table with sortable columns
- Quick status filter + expandable advanced filters
- Bulk action bar (appears when selecting issues)
@@ -150,6 +166,7 @@ The Issue List and Detail Views prototype has been created and is ready for revi
- Labels displayed as badges
**Filter Options**
- Status: Open, In Progress, In Review, Blocked, Done
- Priority: High, Medium, Low
- Sprint: Current sprints, Backlog
@@ -157,6 +174,7 @@ The Issue List and Detail Views prototype has been created and is ready for revi
- Labels: Feature, Bug, Backend, Frontend, etc.
**Detail View**
- Full issue content (markdown-like display)
- Status workflow panel (click to change status)
- Assignment panel with agent avatar
@@ -168,12 +186,14 @@ The Issue List and Detail Views prototype has been created and is ready for revi
- Development section (branch, PR link)
### Key Design Decisions
- Table layout for density and scannability
- Status workflow matches common issue tracker patterns
- Sync status indicator shows data freshness
- Activity timeline shows issue history
### User Flows to Test
1. Use search and filters
2. Click checkboxes to see bulk actions
3. Sort by clicking column headers
@@ -181,6 +201,7 @@ The Issue List and Detail Views prototype has been created and is ready for revi
5. Click status buttons in detail view
### Questions for Review
1. Should we add Kanban view as alternative?
2. Is the sync indicator clear enough?
3. Should there be inline editing?
@@ -188,6 +209,7 @@ The Issue List and Detail Views prototype has been created and is ready for revi
**Please review and approve or provide feedback.**
Files:
- `/frontend/src/app/[locale]/prototypes/issue-management/page.tsx`
- `/frontend/src/app/[locale]/prototypes/issue-management/README.md`
```
@@ -204,12 +226,14 @@ Files:
The Real-time Activity Feed prototype has been created and is ready for review.
### How to View
1. Start the frontend dev server: `cd frontend && npm run dev`
2. Navigate to: `http://localhost:3000/en/prototypes/activity-feed`
### What's Included
**Event Types Displayed**
- Agent Status: Started, paused, resumed, stopped
- Agent Message: Updates, questions, progress reports
- Issue Update: Status changes, assignments, creation
@@ -219,6 +243,7 @@ The Real-time Activity Feed prototype has been created and is ready for review.
- Milestone: Goals achieved, completions
**Features**
- Real-time connection indicator (pulsing green when connected)
- Time-based event grouping (New, Earlier Today, Yesterday, etc.)
- Search functionality
@@ -231,6 +256,7 @@ The Real-time Activity Feed prototype has been created and is ready for review.
- Mark all read functionality
### Key Design Decisions
- Card-based layout for clear event separation
- Orange left border highlights action-required items
- Time grouping helps users orient in timeline
@@ -238,6 +264,7 @@ The Real-time Activity Feed prototype has been created and is ready for review.
- Real-time indicator builds trust in data freshness
### User Flows to Test
1. Scroll through the event feed
2. Click events to expand details
3. Open filter panel and select filters
@@ -245,6 +272,7 @@ The Real-time Activity Feed prototype has been created and is ready for review.
5. Click "Mark all read"
### Questions for Review
1. Should events be grouped by time or show flat?
2. Should there be sound notifications for urgent items?
3. Should users be able to "star" events?
@@ -252,6 +280,7 @@ The Real-time Activity Feed prototype has been created and is ready for review.
**Please review and approve or provide feedback.**
Files:
- `/frontend/src/app/[locale]/prototypes/activity-feed/page.tsx`
- `/frontend/src/app/[locale]/prototypes/activity-feed/README.md`
```
@@ -269,6 +298,7 @@ The comments above should be added to the respective Gitea issues at:
- Issue #39: Real-time Activity Feed
After the user reviews each prototype and provides feedback:
1. Iterate on the design based on feedback
2. Get explicit approval
3. Begin implementation

View File

@@ -1,13 +1,7 @@
'use client';
import Link from 'next/link';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import {

View File

@@ -248,12 +248,47 @@ const EVENT_TYPE_CONFIG: Record<
};
const FILTER_CATEGORIES = [
{ id: 'agent', label: 'Agent Actions', types: [EventType.AGENT_SPAWNED, EventType.AGENT_MESSAGE, EventType.AGENT_STATUS_CHANGED, EventType.AGENT_TERMINATED] },
{ id: 'issue', label: 'Issues', types: [EventType.ISSUE_CREATED, EventType.ISSUE_UPDATED, EventType.ISSUE_ASSIGNED, EventType.ISSUE_CLOSED] },
{
id: 'agent',
label: 'Agent Actions',
types: [
EventType.AGENT_SPAWNED,
EventType.AGENT_MESSAGE,
EventType.AGENT_STATUS_CHANGED,
EventType.AGENT_TERMINATED,
],
},
{
id: 'issue',
label: 'Issues',
types: [
EventType.ISSUE_CREATED,
EventType.ISSUE_UPDATED,
EventType.ISSUE_ASSIGNED,
EventType.ISSUE_CLOSED,
],
},
{ id: 'sprint', label: 'Sprints', types: [EventType.SPRINT_STARTED, EventType.SPRINT_COMPLETED] },
{ id: 'approval', label: 'Approvals', types: [EventType.APPROVAL_REQUESTED, EventType.APPROVAL_GRANTED, EventType.APPROVAL_DENIED] },
{ id: 'workflow', label: 'Workflows', types: [EventType.WORKFLOW_STARTED, EventType.WORKFLOW_STEP_COMPLETED, EventType.WORKFLOW_COMPLETED, EventType.WORKFLOW_FAILED] },
{ id: 'project', label: 'Projects', types: [EventType.PROJECT_CREATED, EventType.PROJECT_UPDATED, EventType.PROJECT_ARCHIVED] },
{
id: 'approval',
label: 'Approvals',
types: [EventType.APPROVAL_REQUESTED, EventType.APPROVAL_GRANTED, EventType.APPROVAL_DENIED],
},
{
id: 'workflow',
label: 'Workflows',
types: [
EventType.WORKFLOW_STARTED,
EventType.WORKFLOW_STEP_COMPLETED,
EventType.WORKFLOW_COMPLETED,
EventType.WORKFLOW_FAILED,
],
},
{
id: 'project',
label: 'Projects',
types: [EventType.PROJECT_CREATED, EventType.PROJECT_UPDATED, EventType.PROJECT_ARCHIVED],
},
];
// ============================================================================
@@ -266,25 +301,60 @@ function getEventConfig(event: ProjectEvent) {
// Fallback based on event category
if (isAgentEvent(event)) {
return { icon: Bot, label: event.type, color: 'text-blue-500', bgColor: 'bg-blue-100 dark:bg-blue-900' };
return {
icon: Bot,
label: event.type,
color: 'text-blue-500',
bgColor: 'bg-blue-100 dark:bg-blue-900',
};
}
if (isIssueEvent(event)) {
return { icon: FileText, label: event.type, color: 'text-green-500', bgColor: 'bg-green-100 dark:bg-green-900' };
return {
icon: FileText,
label: event.type,
color: 'text-green-500',
bgColor: 'bg-green-100 dark:bg-green-900',
};
}
if (isSprintEvent(event)) {
return { icon: PlayCircle, label: event.type, color: 'text-indigo-500', bgColor: 'bg-indigo-100 dark:bg-indigo-900' };
return {
icon: PlayCircle,
label: event.type,
color: 'text-indigo-500',
bgColor: 'bg-indigo-100 dark:bg-indigo-900',
};
}
if (isApprovalEvent(event)) {
return { icon: AlertTriangle, label: event.type, color: 'text-orange-500', bgColor: 'bg-orange-100 dark:bg-orange-900' };
return {
icon: AlertTriangle,
label: event.type,
color: 'text-orange-500',
bgColor: 'bg-orange-100 dark:bg-orange-900',
};
}
if (isWorkflowEvent(event)) {
return { icon: Workflow, label: event.type, color: 'text-cyan-500', bgColor: 'bg-cyan-100 dark:bg-cyan-900' };
return {
icon: Workflow,
label: event.type,
color: 'text-cyan-500',
bgColor: 'bg-cyan-100 dark:bg-cyan-900',
};
}
if (isProjectEvent(event)) {
return { icon: Folder, label: event.type, color: 'text-teal-500', bgColor: 'bg-teal-100 dark:bg-teal-900' };
return {
icon: Folder,
label: event.type,
color: 'text-teal-500',
bgColor: 'bg-teal-100 dark:bg-teal-900',
};
}
return { icon: Activity, label: event.type, color: 'text-gray-500', bgColor: 'bg-gray-100 dark:bg-gray-800' };
return {
icon: Activity,
label: event.type,
color: 'text-gray-500',
bgColor: 'bg-gray-100 dark:bg-gray-800',
};
}
function getEventSummary(event: ProjectEvent): string {
@@ -304,7 +374,9 @@ function getEventSummary(event: ProjectEvent): string {
case EventType.ISSUE_UPDATED:
return `Issue ${payload.issue_id || ''} updated`;
case EventType.ISSUE_ASSIGNED:
return payload.assignee_name ? `Assigned to ${payload.assignee_name}` : 'Issue assignment changed';
return payload.assignee_name
? `Assigned to ${payload.assignee_name}`
: 'Issue assignment changed';
case EventType.ISSUE_CLOSED:
return payload.resolution ? `Closed: ${payload.resolution}` : 'Issue closed';
case EventType.SPRINT_STARTED:
@@ -318,11 +390,15 @@ function getEventSummary(event: ProjectEvent): string {
case EventType.APPROVAL_DENIED:
return payload.reason ? `Denied: ${payload.reason}` : 'Approval denied';
case EventType.WORKFLOW_STARTED:
return payload.workflow_type ? `${payload.workflow_type} workflow started` : 'Workflow started';
return payload.workflow_type
? `${payload.workflow_type} workflow started`
: 'Workflow started';
case EventType.WORKFLOW_STEP_COMPLETED:
return `Step ${payload.step_number}/${payload.total_steps}: ${payload.step_name || 'completed'}`;
case EventType.WORKFLOW_COMPLETED:
return payload.duration_seconds ? `Completed in ${payload.duration_seconds}s` : 'Workflow completed';
return payload.duration_seconds
? `Completed in ${payload.duration_seconds}s`
: 'Workflow completed';
case EventType.WORKFLOW_FAILED:
return payload.error_message ? String(payload.error_message) : 'Workflow failed';
default:
@@ -391,11 +467,7 @@ function ConnectionIndicator({ state, onReconnect, className }: ConnectionIndica
return (
<div className={cn('flex items-center gap-2', className)} data-testid="connection-indicator">
<span
className={cn(
'h-2 w-2 rounded-full',
config.color,
config.pulse && 'animate-pulse'
)}
className={cn('h-2 w-2 rounded-full', config.color, config.pulse && 'animate-pulse')}
aria-hidden="true"
/>
<span className="text-sm text-muted-foreground">{config.label}</span>
@@ -475,7 +547,10 @@ function FilterPanel({
checked={showPendingOnly}
onCheckedChange={(checked) => onShowPendingOnlyChange(checked as boolean)}
/>
<Label htmlFor="filter-pending" className="flex items-center gap-1 text-sm font-normal cursor-pointer">
<Label
htmlFor="filter-pending"
className="flex items-center gap-1 text-sm font-normal cursor-pointer"
>
Show only pending approvals
{pendingCount > 0 && (
<Badge variant="destructive" className="text-xs">
@@ -598,77 +673,85 @@ function EventItem({
}}
aria-label={expanded ? 'Collapse details' : 'Expand details'}
>
{expanded ? <ChevronDown className="h-4 w-4" /> : <ChevronRight className="h-4 w-4" />}
{expanded ? (
<ChevronDown className="h-4 w-4" />
) : (
<ChevronRight className="h-4 w-4" />
)}
</Button>
</div>
</div>
{/* Expanded Details */}
{expanded && (() => {
const issueId = payload.issue_id as string | undefined;
const pullRequest = payload.pullRequest as string | number | undefined;
const documentUrl = payload.documentUrl as string | undefined;
const progress = payload.progress as number | undefined;
{expanded &&
(() => {
const issueId = payload.issue_id as string | undefined;
const pullRequest = payload.pullRequest as string | number | undefined;
const documentUrl = payload.documentUrl as string | undefined;
const progress = payload.progress as number | undefined;
return (
<div className="mt-3 rounded-md bg-muted/50 p-3 space-y-3" data-testid="event-details">
{/* Issue/PR Links */}
{issueId && (
<div className="flex items-center gap-2 text-sm">
<CircleDot className="h-4 w-4" aria-hidden="true" />
<span>Issue #{issueId}</span>
</div>
)}
{pullRequest && (
<div className="flex items-center gap-2 text-sm">
<GitPullRequest className="h-4 w-4" aria-hidden="true" />
<span>PR #{String(pullRequest)}</span>
</div>
)}
{/* Document Links */}
{documentUrl && (
<div className="flex items-center gap-2 text-sm">
<ExternalLink className="h-4 w-4" aria-hidden="true" />
<a href={documentUrl} className="text-primary hover:underline">
{documentUrl}
</a>
</div>
)}
{/* Progress */}
{progress !== undefined && (
<div className="space-y-1">
<div className="flex justify-between text-sm">
<span>Progress</span>
<span>{progress}%</span>
return (
<div
className="mt-3 rounded-md bg-muted/50 p-3 space-y-3"
data-testid="event-details"
>
{/* Issue/PR Links */}
{issueId && (
<div className="flex items-center gap-2 text-sm">
<CircleDot className="h-4 w-4" aria-hidden="true" />
<span>Issue #{issueId}</span>
</div>
<div className="h-2 rounded-full bg-muted">
<div
className="h-full rounded-full bg-primary"
style={{ width: `${progress}%` }}
/>
)}
{pullRequest && (
<div className="flex items-center gap-2 text-sm">
<GitPullRequest className="h-4 w-4" aria-hidden="true" />
<span>PR #{String(pullRequest)}</span>
</div>
</div>
)}
)}
{/* Timestamp */}
<p className="text-xs text-muted-foreground">
{new Date(event.timestamp).toLocaleString()}
</p>
{/* Document Links */}
{documentUrl && (
<div className="flex items-center gap-2 text-sm">
<ExternalLink className="h-4 w-4" aria-hidden="true" />
<a href={documentUrl} className="text-primary hover:underline">
{documentUrl}
</a>
</div>
)}
{/* Raw Payload (for debugging) */}
<details className="text-xs">
<summary className="cursor-pointer text-muted-foreground hover:text-foreground">
View raw payload
</summary>
<pre className="mt-2 overflow-x-auto whitespace-pre-wrap break-words rounded bg-muted p-2">
{JSON.stringify(event.payload, null, 2)}
</pre>
</details>
</div>
);
})()}
{/* Progress */}
{progress !== undefined && (
<div className="space-y-1">
<div className="flex justify-between text-sm">
<span>Progress</span>
<span>{progress}%</span>
</div>
<div className="h-2 rounded-full bg-muted">
<div
className="h-full rounded-full bg-primary"
style={{ width: `${progress}%` }}
/>
</div>
</div>
)}
{/* Timestamp */}
<p className="text-xs text-muted-foreground">
{new Date(event.timestamp).toLocaleString()}
</p>
{/* Raw Payload (for debugging) */}
<details className="text-xs">
<summary className="cursor-pointer text-muted-foreground hover:text-foreground">
View raw payload
</summary>
<pre className="mt-2 overflow-x-auto whitespace-pre-wrap break-words rounded bg-muted p-2">
{JSON.stringify(event.payload, null, 2)}
</pre>
</details>
</div>
);
})()}
{/* Approval Actions */}
{isPendingApproval && (onApprove || onReject) && (
@@ -680,7 +763,12 @@ function EventItem({
</Button>
)}
{onReject && (
<Button variant="outline" size="sm" onClick={handleReject} data-testid="reject-button">
<Button
variant="outline"
size="sm"
onClick={handleReject}
data-testid="reject-button"
>
<XCircle className="mr-2 h-4 w-4" />
Reject
</Button>
@@ -712,7 +800,10 @@ function LoadingSkeleton() {
function EmptyState({ hasFilters }: { hasFilters: boolean }) {
return (
<div className="flex flex-col items-center justify-center py-12 text-muted-foreground" data-testid="empty-state">
<div
className="flex flex-col items-center justify-center py-12 text-muted-foreground"
data-testid="empty-state"
>
<Activity className="h-12 w-12 mb-4" aria-hidden="true" />
<h3 className="font-semibold">No activity found</h3>
<p className="text-sm">
@@ -894,7 +985,10 @@ export function ActivityFeed({
) : (
<div className="space-y-6">
{groupedEvents.map((group) => (
<div key={group.label} data-testid={`event-group-${group.label.toLowerCase().replace(' ', '-')}`}>
<div
key={group.label}
data-testid={`event-group-${group.label.toLowerCase().replace(' ', '-')}`}
>
<div className="mb-3 flex items-center gap-2">
<h3 className="text-sm font-medium text-muted-foreground">{group.label}</h3>
<Badge variant="secondary" className="text-xs">

View File

@@ -57,13 +57,19 @@ interface AgentTypeDetailProps {
function AgentTypeStatusBadge({ isActive }: { isActive: boolean }) {
if (isActive) {
return (
<Badge className="bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200" variant="outline">
<Badge
className="bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200"
variant="outline"
>
Active
</Badge>
);
}
return (
<Badge className="bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200" variant="outline">
<Badge
className="bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200"
variant="outline"
>
Inactive
</Badge>
);
@@ -139,9 +145,7 @@ export function AgentTypeDetail({
<div className="py-12 text-center">
<AlertTriangle className="mx-auto h-12 w-12 text-muted-foreground" />
<h3 className="mt-4 font-semibold">Agent type not found</h3>
<p className="text-muted-foreground">
The requested agent type could not be found
</p>
<p className="text-muted-foreground">The requested agent type could not be found</p>
<Button onClick={onBack} variant="outline" className="mt-4">
<ArrowLeft className="mr-2 h-4 w-4" />
Go Back
@@ -265,9 +269,7 @@ export function AgentTypeDetail({
<div
key={server.id}
className={`flex items-center justify-between rounded-lg border p-3 ${
isEnabled
? 'border-primary/20 bg-primary/5'
: 'border-muted bg-muted/50'
isEnabled ? 'border-primary/20 bg-primary/5' : 'border-muted bg-muted/50'
}`}
>
<div className="flex items-center gap-3">
@@ -284,9 +286,7 @@ export function AgentTypeDetail({
</div>
<div>
<p className="font-medium">{server.name}</p>
<p className="text-xs text-muted-foreground">
{server.description}
</p>
<p className="text-xs text-muted-foreground">{server.description}</p>
</div>
</div>
<Badge variant={isEnabled ? 'default' : 'secondary'}>
@@ -313,9 +313,7 @@ export function AgentTypeDetail({
<CardContent className="space-y-4">
<div>
<p className="text-sm text-muted-foreground">Primary Model</p>
<p className="font-medium">
{getModelDisplayName(agentType.primary_model)}
</p>
<p className="font-medium">{getModelDisplayName(agentType.primary_model)}</p>
</div>
<div>
<p className="text-sm text-muted-foreground">Failover Model</p>
@@ -355,9 +353,7 @@ export function AgentTypeDetail({
</CardHeader>
<CardContent>
<div className="text-center">
<p className="text-4xl font-bold text-primary">
{agentType.instance_count}
</p>
<p className="text-4xl font-bold text-primary">{agentType.instance_count}</p>
<p className="text-sm text-muted-foreground">Active instances</p>
</div>
<Button variant="outline" className="mt-4 w-full" size="sm" disabled>

View File

@@ -26,16 +26,7 @@ import {
} from '@/components/ui/select';
import { Badge } from '@/components/ui/badge';
import { Separator } from '@/components/ui/separator';
import {
FileText,
Cpu,
Shield,
MessageSquare,
Sliders,
Save,
ArrowLeft,
X,
} from 'lucide-react';
import { FileText, Cpu, Shield, MessageSquare, Sliders, Save, ArrowLeft, X } from 'lucide-react';
import {
agentTypeCreateSchema,
type AgentTypeCreateFormValues,
@@ -151,9 +142,7 @@ export function AgentTypeForm({
{isEditing ? 'Edit Agent Type' : 'Create Agent Type'}
</h1>
<p className="text-muted-foreground">
{isEditing
? 'Modify agent type configuration'
: 'Define a new agent type template'}
{isEditing ? 'Modify agent type configuration' : 'Define a new agent type template'}
</p>
</div>
<div className="flex gap-2">
@@ -281,9 +270,7 @@ export function AgentTypeForm({
<div className="space-y-2">
<Label>Expertise Areas</Label>
<p className="text-sm text-muted-foreground">
Add skills and areas of expertise
</p>
<p className="text-sm text-muted-foreground">Add skills and areas of expertise</p>
<div className="flex gap-2">
<Input
placeholder="e.g., System Design"
@@ -325,9 +312,7 @@ export function AgentTypeForm({
<Card>
<CardHeader>
<CardTitle>Model Selection</CardTitle>
<CardDescription>
Choose the AI models that power this agent type
</CardDescription>
<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">
@@ -358,9 +343,7 @@ export function AgentTypeForm({
{errors.primary_model.message}
</p>
)}
<p className="text-xs text-muted-foreground">
Main model used for this agent
</p>
<p className="text-xs text-muted-foreground">Main model used for this agent</p>
</div>
<div className="space-y-2">
<Label htmlFor="fallback_model">Fallover Model</Label>
@@ -420,9 +403,7 @@ export function AgentTypeForm({
/>
)}
/>
<p className="text-xs text-muted-foreground">
0 = deterministic, 2 = creative
</p>
<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>
@@ -472,9 +453,7 @@ export function AgentTypeForm({
<Card>
<CardHeader>
<CardTitle>MCP Server Permissions</CardTitle>
<CardDescription>
Configure which MCP servers this agent can access
</CardDescription>
<CardDescription>Configure which MCP servers this agent can access</CardDescription>
</CardHeader>
<CardContent className="space-y-4">
{AVAILABLE_MCP_SERVERS.map((server) => (
@@ -508,8 +487,8 @@ export function AgentTypeForm({
<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.
Define the agent&apos;s personality, behavior, and communication style. This prompt
shapes how the agent approaches tasks and interacts.
</CardDescription>
</CardHeader>
<CardContent>
@@ -535,9 +514,7 @@ export function AgentTypeForm({
</p>
)}
<div className="flex items-center gap-4 text-sm text-muted-foreground">
<span>
Character count: {watch('personality_prompt')?.length || 0}
</span>
<span>Character count: {watch('personality_prompt')?.length || 0}</span>
<Separator orientation="vertical" className="h-4" />
<span className="text-xs">
Tip: Be specific about expertise, communication style, and decision-making

View File

@@ -41,13 +41,19 @@ interface AgentTypeListProps {
function AgentTypeStatusBadge({ isActive }: { isActive: boolean }) {
if (isActive) {
return (
<Badge className="bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200" variant="outline">
<Badge
className="bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-200"
variant="outline"
>
Active
</Badge>
);
}
return (
<Badge className="bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200" variant="outline">
<Badge
className="bg-gray-100 text-gray-800 dark:bg-gray-800 dark:text-gray-200"
variant="outline"
>
Inactive
</Badge>
);

View File

@@ -12,13 +12,7 @@
import { Component, type ReactNode } from 'react';
import { AlertTriangle, RefreshCw } from 'lucide-react';
import { Button } from '@/components/ui/button';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
// ============================================================================
// Types
@@ -59,25 +53,17 @@ function DefaultFallback({ error, onReset, showReset }: DefaultFallbackProps) {
Something went wrong
</CardTitle>
<CardDescription>
An unexpected error occurred. Please try again or contact support if
the problem persists.
An unexpected error occurred. Please try again or contact support if the problem persists.
</CardDescription>
</CardHeader>
<CardContent>
{error && (
<div className="mb-4 rounded-md bg-muted p-3">
<p className="font-mono text-sm text-muted-foreground">
{error.message}
</p>
<p className="font-mono text-sm text-muted-foreground">{error.message}</p>
</div>
)}
{showReset && (
<Button
variant="outline"
size="sm"
onClick={onReset}
className="gap-2"
>
<Button variant="outline" size="sm" onClick={onReset} className="gap-2">
<RefreshCw className="h-4 w-4" aria-hidden="true" />
Try again
</Button>
@@ -108,10 +94,7 @@ function DefaultFallback({ error, onReset, showReset }: DefaultFallbackProps) {
* </ErrorBoundary>
* ```
*/
export class ErrorBoundary extends Component<
ErrorBoundaryProps,
ErrorBoundaryState
> {
export class ErrorBoundary extends Component<ErrorBoundaryProps, ErrorBoundaryState> {
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { hasError: false, error: null };
@@ -142,13 +125,7 @@ export class ErrorBoundary extends Component<
return fallback;
}
return (
<DefaultFallback
error={error}
onReset={this.handleReset}
showReset={showReset}
/>
);
return <DefaultFallback error={error} onReset={this.handleReset} showReset={showReset} />;
}
return children;

View File

@@ -153,7 +153,8 @@ export function ConnectionStatus({
className={cn(
'flex flex-col gap-3 rounded-lg border p-4',
state === 'error' && 'border-destructive bg-destructive/5',
state === 'connected' && 'border-green-200 bg-green-50 dark:border-green-900 dark:bg-green-950',
state === 'connected' &&
'border-green-200 bg-green-50 dark:border-green-900 dark:bg-green-950',
className
)}
role="status"
@@ -199,11 +200,7 @@ export function ConnectionStatus({
{showErrorDetails && error && (
<div className="rounded-md bg-destructive/10 p-3 text-sm">
<p className="font-medium text-destructive">Error: {error.message}</p>
{error.code && (
<p className="mt-1 text-muted-foreground">
Code: {error.code}
</p>
)}
{error.code && <p className="mt-1 text-muted-foreground">Code: {error.code}</p>}
<p className="mt-1 text-xs text-muted-foreground">
{new Date(error.timestamp).toLocaleTimeString()}
</p>

View File

@@ -250,17 +250,11 @@ function getEventSummary(event: ProjectEvent): string {
? `Assigned to ${payload.assignee_name}`
: 'Issue assignment changed';
case EventType.ISSUE_CLOSED:
return payload.resolution
? `Closed: ${payload.resolution}`
: 'Issue closed';
return payload.resolution ? `Closed: ${payload.resolution}` : 'Issue closed';
case EventType.SPRINT_STARTED:
return payload.sprint_name
? `Sprint "${payload.sprint_name}" started`
: 'Sprint started';
return payload.sprint_name ? `Sprint "${payload.sprint_name}" started` : 'Sprint started';
case EventType.SPRINT_COMPLETED:
return payload.sprint_name
? `Sprint "${payload.sprint_name}" completed`
: 'Sprint completed';
return payload.sprint_name ? `Sprint "${payload.sprint_name}" completed` : 'Sprint completed';
case EventType.APPROVAL_REQUESTED:
return String(payload.description || 'Approval requested');
case EventType.APPROVAL_GRANTED:
@@ -278,9 +272,7 @@ function getEventSummary(event: ProjectEvent): string {
? `Completed in ${payload.duration_seconds}s`
: 'Workflow completed';
case EventType.WORKFLOW_FAILED:
return payload.error_message
? String(payload.error_message)
: 'Workflow failed';
return payload.error_message ? String(payload.error_message) : 'Workflow failed';
default:
return event.type;
}

View File

@@ -72,8 +72,8 @@ export function HeroSection({ onOpenDemoModal }: HeroSectionProps) {
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.2 }}
>
Opinionated, secure, and production-ready. Syndarix gives you the solid foundation
you need to stop configuring and start shipping.{' '}
Opinionated, secure, and production-ready. Syndarix gives you the solid foundation you
need to stop configuring and start shipping.{' '}
<span className="text-foreground font-medium">Start building features on day one.</span>
</motion.p>

View File

@@ -74,11 +74,7 @@ function generateBreadcrumbs(pathname: string): BreadcrumbItem[] {
return breadcrumbs;
}
export function AppBreadcrumbs({
items,
showHome = true,
className,
}: AppBreadcrumbsProps) {
export function AppBreadcrumbs({ items, showHome = true, className }: AppBreadcrumbsProps) {
const pathname = usePathname();
// Use provided items or generate from pathname

View File

@@ -49,11 +49,7 @@ export function AppHeader({
{/* Left side - Logo and Project Switcher */}
<div className="flex items-center gap-4">
{/* Logo - visible on mobile, hidden on desktop when sidebar is visible */}
<Link
href="/"
className="flex items-center gap-2 lg:hidden"
aria-label="Syndarix home"
>
<Link href="/" className="flex items-center gap-2 lg:hidden" aria-label="Syndarix home">
<Image
src="/logo-icon.svg"
alt=""

View File

@@ -73,11 +73,7 @@ export function AppLayout({
{!hideBreadcrumbs && <AppBreadcrumbs items={breadcrumbs} />}
{/* Main content */}
<main
className={cn('flex-1', className)}
id="main-content"
tabIndex={-1}
>
<main className={cn('flex-1', className)} id="main-content" tabIndex={-1}>
{children}
</main>
</div>
@@ -110,11 +106,7 @@ const maxWidthClasses: Record<string, string> = {
full: 'max-w-full',
};
export function PageContainer({
children,
maxWidth = '6xl',
className,
}: PageContainerProps) {
export function PageContainer({ children, maxWidth = '6xl', className }: PageContainerProps) {
return (
<div
className={cn(
@@ -144,12 +136,7 @@ interface PageHeaderProps {
className?: string;
}
export function PageHeader({
title,
description,
actions,
className,
}: PageHeaderProps) {
export function PageHeader({ title, description, actions, className }: PageHeaderProps) {
return (
<div
className={cn(
@@ -160,9 +147,7 @@ export function PageHeader({
>
<div className="space-y-1">
<h1 className="text-2xl font-bold tracking-tight sm:text-3xl">{title}</h1>
{description && (
<p className="text-muted-foreground">{description}</p>
)}
{description && <p className="text-muted-foreground">{description}</p>}
</div>
{actions && <div className="flex items-center gap-2">{actions}</div>}
</div>

View File

@@ -98,9 +98,7 @@ export function ProjectSwitcher({
className={cn('gap-2 min-w-[160px] justify-between', className)}
data-testid="project-switcher-trigger"
aria-label={
currentProject
? `Switch project, current: ${currentProject.name}`
: 'Select project'
currentProject ? `Switch project, current: ${currentProject.name}` : 'Select project'
}
>
<div className="flex items-center gap-2">
@@ -112,11 +110,7 @@ export function ProjectSwitcher({
<ChevronDown className="h-4 w-4 opacity-50" aria-hidden="true" />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
align="start"
className="w-[200px]"
data-testid="project-switcher-menu"
>
<DropdownMenuContent align="start" className="w-[200px]" data-testid="project-switcher-menu">
<DropdownMenuLabel>Projects</DropdownMenuLabel>
<DropdownMenuSeparator />
{projects.map((project) => (

View File

@@ -11,13 +11,7 @@ import { Link } from '@/lib/i18n/routing';
import { usePathname } from '@/lib/i18n/routing';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
import {
Sheet,
SheetContent,
SheetHeader,
SheetTitle,
SheetTrigger,
} from '@/components/ui/sheet';
import { Sheet, SheetContent, SheetHeader, SheetTitle, SheetTrigger } from '@/components/ui/sheet';
import {
FolderKanban,
Bot,
@@ -113,9 +107,7 @@ function NavLink({ item, collapsed, basePath = '' }: NavLinkProps) {
const pathname = usePathname();
const href = basePath ? `${basePath}${item.href}` : item.href;
const isActive = item.exact
? pathname === href
: pathname.startsWith(href);
const isActive = item.exact ? pathname === href : pathname.startsWith(href);
const Icon = item.icon;
@@ -155,9 +147,7 @@ function SidebarContent({
<div className="flex h-full flex-col">
{/* Sidebar Header */}
<div className="flex h-14 items-center justify-between border-b px-4">
{!collapsed && (
<span className="text-lg font-semibold text-foreground">Navigation</span>
)}
{!collapsed && <span className="text-lg font-semibold text-foreground">Navigation</span>}
<Button
variant="ghost"
size="icon"
@@ -308,11 +298,7 @@ export function Sidebar({ projectSlug, className }: SidebarProps) {
data-testid="sidebar"
aria-label="Main navigation"
>
<SidebarContent
collapsed={collapsed}
projectSlug={projectSlug}
onToggle={handleToggle}
/>
<SidebarContent collapsed={collapsed} projectSlug={projectSlug} onToggle={handleToggle} />
</aside>
</>
);

View File

@@ -20,14 +20,7 @@ import {
} from '@/components/ui/dropdown-menu';
import { Button } from '@/components/ui/button';
import { Avatar, AvatarFallback } from '@/components/ui/avatar';
import {
User,
LogOut,
Shield,
Lock,
Monitor,
UserCog,
} from 'lucide-react';
import { User, LogOut, Shield, Lock, Monitor, UserCog } from 'lucide-react';
import { cn } from '@/lib/utils';
interface UserMenuProps {
@@ -76,20 +69,14 @@ export function UserMenu({ className }: UserMenuProps) {
</Avatar>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent
className="w-56"
align="end"
data-testid="user-menu-content"
>
<DropdownMenuContent className="w-56" align="end" data-testid="user-menu-content">
{/* User info header */}
<DropdownMenuLabel className="font-normal">
<div className="flex flex-col space-y-1">
<p className="text-sm font-medium leading-none">
{user.first_name} {user.last_name}
</p>
<p className="text-xs leading-none text-muted-foreground">
{user.email}
</p>
<p className="text-xs leading-none text-muted-foreground">{user.email}</p>
</div>
</DropdownMenuLabel>
<DropdownMenuSeparator />
@@ -143,11 +130,7 @@ export function UserMenu({ className }: UserMenuProps) {
<>
<DropdownMenuSeparator />
<DropdownMenuItem asChild>
<Link
href="/admin"
className="cursor-pointer"
data-testid="user-menu-admin"
>
<Link href="/admin" className="cursor-pointer" data-testid="user-menu-admin">
<Shield className="mr-2 h-4 w-4" aria-hidden="true" />
{t('adminPanel')}
</Link>

View File

@@ -9,13 +9,7 @@
import { Bot, MoreVertical } from 'lucide-react';
import { formatDistanceToNow } from 'date-fns';
import { Button } from '@/components/ui/button';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import {
DropdownMenu,
DropdownMenuContent,
@@ -228,11 +222,7 @@ export function AgentPanel({
) : (
<div className="space-y-3">
{agents.map((agent) => (
<AgentListItem
key={agent.id}
agent={agent}
onAction={onAgentAction}
/>
<AgentListItem key={agent.id} agent={agent} onAction={onAgentAction} />
))}
</div>
)}

View File

@@ -60,16 +60,10 @@ export function AgentStatusIndicator({
aria-label={`Status: ${config.label}`}
>
<span
className={cn(
'inline-block rounded-full',
sizeClasses[size],
config.color
)}
className={cn('inline-block rounded-full', sizeClasses[size], config.color)}
aria-hidden="true"
/>
{showLabel && (
<span className="text-xs text-muted-foreground">{config.label}</span>
)}
{showLabel && <span className="text-xs text-muted-foreground">{config.label}</span>}
</span>
);
}

View File

@@ -109,15 +109,7 @@ export function BurndownChart({
{data.map((d, i) => {
const x = padding.left + (i / (data.length - 1)) * innerWidth;
const y = padding.top + innerHeight - (d.remaining / maxPoints) * innerHeight;
return (
<circle
key={i}
cx={x}
cy={y}
r="2"
className="fill-primary"
/>
);
return <circle key={i} cx={x} cy={y} r="2" className="fill-primary" />;
})}
</svg>

View File

@@ -6,22 +6,10 @@
'use client';
import {
GitBranch,
CircleDot,
PlayCircle,
Clock,
AlertCircle,
CheckCircle2,
} from 'lucide-react';
import { GitBranch, CircleDot, PlayCircle, Clock, AlertCircle, CheckCircle2 } from 'lucide-react';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Separator } from '@/components/ui/separator';
import { Skeleton } from '@/components/ui/skeleton';
import type { IssueCountSummary } from './types';
@@ -141,12 +129,7 @@ export function IssueSummary({
</CardHeader>
<CardContent>
<div className="space-y-3" role="list" aria-label="Issue counts by status">
<StatusRow
icon={CircleDot}
iconColor="text-blue-500"
label="Open"
count={summary.open}
/>
<StatusRow icon={CircleDot} iconColor="text-blue-500" label="Open" count={summary.open} />
<StatusRow
icon={PlayCircle}
iconColor="text-yellow-500"
@@ -177,12 +160,7 @@ export function IssueSummary({
{onViewAllIssues && (
<div className="pt-2">
<Button
variant="outline"
className="w-full"
size="sm"
onClick={onViewAllIssues}
>
<Button variant="outline" className="w-full" size="sm" onClick={onViewAllIssues}>
View All Issues ({summary.total})
</Button>
</div>

View File

@@ -85,14 +85,12 @@ export function ProjectHeader({
}
const showPauseButton = canPause && project.status === 'active';
const showStartButton = canStart && project.status !== 'completed' && project.status !== 'archived';
const showStartButton =
canStart && project.status !== 'completed' && project.status !== 'archived';
return (
<div
className={cn(
'flex flex-col gap-4 md:flex-row md:items-start md:justify-between',
className
)}
className={cn('flex flex-col gap-4 md:flex-row md:items-start md:justify-between', className)}
data-testid="project-header"
>
{/* Project Info */}
@@ -102,20 +100,13 @@ export function ProjectHeader({
<ProjectStatusBadge status={project.status} />
<AutonomyBadge level={project.autonomy_level} />
</div>
{project.description && (
<p className="text-muted-foreground">{project.description}</p>
)}
{project.description && <p className="text-muted-foreground">{project.description}</p>}
</div>
{/* Quick Actions */}
<div className="flex flex-wrap gap-2">
{onSettings && (
<Button
variant="ghost"
size="icon"
onClick={onSettings}
aria-label="Project settings"
>
<Button variant="ghost" size="icon" onClick={onSettings} aria-label="Project settings">
<Settings className="h-4 w-4" />
</Button>
)}

View File

@@ -19,12 +19,7 @@ import {
} from 'lucide-react';
import { cn } from '@/lib/utils';
import { Button } from '@/components/ui/button';
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { Skeleton } from '@/components/ui/skeleton';
import type { ActivityItem } from './types';
@@ -104,9 +99,7 @@ function ActivityItemRow({ activity, onActionClick }: ActivityItemRowProps) {
</div>
<div className="min-w-0 flex-1">
<p className="text-sm">
{activity.agent && (
<span className="font-medium">{activity.agent}</span>
)}{' '}
{activity.agent && <span className="font-medium">{activity.agent}</span>}{' '}
<span className="text-muted-foreground">{activity.message}</span>
</p>
<p className="text-xs text-muted-foreground">{timestamp}</p>

View File

@@ -9,13 +9,7 @@
import { TrendingUp, Calendar } from 'lucide-react';
import { format } from 'date-fns';
import { cn } from '@/lib/utils';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import {
Select,
SelectContent,
@@ -188,10 +182,7 @@ export function SprintProgress({
</div>
{availableSprints.length > 1 && onSprintChange && (
<Select
value={selectedSprintId || sprint.id}
onValueChange={onSprintChange}
>
<Select value={selectedSprintId || sprint.id} onValueChange={onSprintChange}>
<SelectTrigger className="w-32" aria-label="Select sprint">
<SelectValue />
</SelectTrigger>
@@ -231,16 +222,8 @@ export function SprintProgress({
label="In Progress"
colorClass="text-blue-600"
/>
<StatCard
value={sprint.blocked_issues}
label="Blocked"
colorClass="text-red-600"
/>
<StatCard
value={sprint.todo_issues}
label="To Do"
colorClass="text-gray-600"
/>
<StatCard value={sprint.blocked_issues} label="Blocked" colorClass="text-red-600" />
<StatCard value={sprint.todo_issues} label="To Do" colorClass="text-gray-600" />
</div>
{/* Burndown Chart */}

View File

@@ -81,9 +81,7 @@ export function AutonomyBadge({ level, showDescription = false, className }: Aut
<Badge variant="secondary" className={cn('gap-1', className)} title={config.description}>
<CircleDot className="h-3 w-3" aria-hidden="true" />
{config.label}
{showDescription && (
<span className="text-muted-foreground"> - {config.description}</span>
)}
{showDescription && <span className="text-muted-foreground"> - {config.description}</span>}
</Badge>
);
}

View File

@@ -123,7 +123,13 @@ export interface IssueCountSummary {
export interface ActivityItem {
id: string;
type: 'agent_message' | 'issue_update' | 'agent_status' | 'approval_request' | 'sprint_event' | 'system';
type:
| 'agent_message'
| 'issue_update'
| 'agent_status'
| 'approval_request'
| 'sprint_event'
| 'system';
agent?: string;
message: string;
timestamp: string;

View File

@@ -73,16 +73,13 @@ export function ProjectWizard({ locale, className }: ProjectWizardProps) {
mutationFn: async (projectData: ProjectCreateData): Promise<ProjectResponse> => {
// Call the projects API endpoint
// Note: The API client already handles authentication via interceptors
const response = await apiClient.instance.post<ProjectResponse>(
'/api/v1/projects',
{
name: projectData.name,
slug: projectData.slug,
description: projectData.description,
autonomy_level: projectData.autonomy_level,
settings: projectData.settings,
}
);
const response = await apiClient.instance.post<ProjectResponse>('/api/v1/projects', {
name: projectData.name,
slug: projectData.slug,
description: projectData.description,
autonomy_level: projectData.autonomy_level,
settings: projectData.settings,
});
return response.data;
},
@@ -123,7 +120,10 @@ export function ProjectWizard({ locale, className }: ProjectWizardProps) {
<Card className="text-center">
<CardContent className="space-y-6 p-8">
<div className="mx-auto flex h-16 w-16 items-center justify-center rounded-full bg-green-100 dark:bg-green-900">
<CheckCircle2 className="h-8 w-8 text-green-600 dark:text-green-400" aria-hidden="true" />
<CheckCircle2
className="h-8 w-8 text-green-600 dark:text-green-400"
aria-hidden="true"
/>
</div>
<div>
<h2 className="text-2xl font-bold">Project Created Successfully!</h2>
@@ -192,10 +192,7 @@ export function ProjectWizard({ locale, className }: ProjectWizardProps) {
<ArrowRight className="ml-2 h-4 w-4" aria-hidden="true" />
</Button>
) : (
<Button
onClick={handleCreate}
disabled={createProjectMutation.isPending}
>
<Button onClick={handleCreate} disabled={createProjectMutation.isPending}>
{createProjectMutation.isPending ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" aria-hidden="true" />

View File

@@ -29,7 +29,12 @@ export function StepIndicator({ currentStep, isScriptMode, className }: StepIndi
</span>
<span>{steps[displayStep - 1]}</span>
</div>
<div className="flex gap-1" role="progressbar" aria-valuenow={displayStep} aria-valuemax={totalSteps}>
<div
className="flex gap-1"
role="progressbar"
aria-valuenow={displayStep}
aria-valuemax={totalSteps}
>
{Array.from({ length: totalSteps }, (_, i) => (
<div
key={i}

View File

@@ -18,9 +18,4 @@ export type {
} from './types';
// Re-export constants
export {
complexityOptions,
clientModeOptions,
autonomyOptions,
WIZARD_STEPS,
} from './constants';
export { complexityOptions, clientModeOptions, autonomyOptions, WIZARD_STEPS } from './constants';

View File

@@ -11,13 +11,7 @@ import { Bot, User, MessageSquare, Sparkles } from 'lucide-react';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import {
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Input } from '@/components/ui/input';
import { cn } from '@/lib/utils';

View File

@@ -11,12 +11,7 @@
import { Check, AlertCircle } from 'lucide-react';
import { Badge } from '@/components/ui/badge';
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { cn } from '@/lib/utils';
import { SelectableCard } from '../SelectableCard';
import { autonomyOptions } from '../constants';

View File

@@ -34,7 +34,11 @@ export function ClientModeStep({ state, updateState }: ClientModeStepProps) {
</p>
</div>
<div className="grid gap-6 md:grid-cols-2" role="radiogroup" aria-label="Client interaction mode options">
<div
className="grid gap-6 md:grid-cols-2"
role="radiogroup"
aria-label="Client interaction mode options"
>
{clientModeOptions.map((option) => {
const Icon = option.icon;
const isSelected = state.clientMode === option.id;

View File

@@ -39,7 +39,11 @@ export function ComplexityStep({ state, updateState }: ComplexityStepProps) {
)}
</div>
<div className="grid gap-4 md:grid-cols-2" role="radiogroup" aria-label="Project complexity options">
<div
className="grid gap-4 md:grid-cols-2"
role="radiogroup"
aria-label="Project complexity options"
>
{complexityOptions.map((option) => {
const Icon = option.icon;
const isSelected = state.complexity === option.id;

View File

@@ -8,12 +8,7 @@
import { CheckCircle2 } from 'lucide-react';
import {
Card,
CardContent,
CardHeader,
CardTitle,
} from '@/components/ui/card';
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card';
import { complexityOptions, clientModeOptions, autonomyOptions } from '../constants';
import type { WizardState } from '../types';

View File

@@ -20,11 +20,7 @@ interface ActivityTimelineProps {
className?: string;
}
export function ActivityTimeline({
activities,
onAddComment,
className,
}: ActivityTimelineProps) {
export function ActivityTimeline({ activities, onAddComment, className }: ActivityTimelineProps) {
return (
<Card className={className}>
<CardHeader>
@@ -43,11 +39,7 @@ export function ActivityTimeline({
<CardContent>
<div className="space-y-6" role="list" aria-label="Issue activity">
{activities.map((item, index) => (
<div
key={item.id}
className="flex gap-4"
role="listitem"
>
<div key={item.id} className="flex gap-4" role="listitem">
<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' ? (
@@ -74,9 +66,7 @@ export function ActivityTimeline({
</div>
{activities.length === 0 && (
<div className="py-8 text-center text-muted-foreground">
No activity yet
</div>
<div className="py-8 text-center text-muted-foreground">No activity yet</div>
)}
</CardContent>
</Card>

View File

@@ -34,16 +34,11 @@ export function BulkActions({
return (
<div
className={cn(
'flex items-center gap-4 rounded-lg border bg-muted/50 p-3',
className
)}
className={cn('flex items-center gap-4 rounded-lg border bg-muted/50 p-3', className)}
role="toolbar"
aria-label="Bulk actions for selected issues"
>
<span className="text-sm font-medium">
{selectedCount} selected
</span>
<span className="text-sm font-medium">{selectedCount} selected</span>
<Separator orientation="vertical" className="h-6" />
<div className="flex gap-2">
<Button variant="outline" size="sm" onClick={onChangeStatus}>

View File

@@ -44,9 +44,7 @@ export function IssueDetailPanel({ issue, className }: IssueDetailPanelProps) {
</div>
<div>
<p className="font-medium">{issue.assignee.name}</p>
<p className="text-xs text-muted-foreground capitalize">
{issue.assignee.type}
</p>
<p className="text-xs text-muted-foreground capitalize">{issue.assignee.type}</p>
</div>
</div>
) : (
@@ -92,9 +90,7 @@ export function IssueDetailPanel({ issue, className }: IssueDetailPanelProps) {
{issue.due_date && (
<div>
<p className="text-sm text-muted-foreground">Due Date</p>
<p className="font-medium">
{new Date(issue.due_date).toLocaleDateString()}
</p>
<p className="font-medium">{new Date(issue.due_date).toLocaleDateString()}</p>
</div>
)}
@@ -136,19 +132,13 @@ export function IssueDetailPanel({ issue, className }: IssueDetailPanelProps) {
<CardContent className="space-y-4">
{issue.branch && (
<div className="flex items-center gap-2">
<GitBranch
className="h-4 w-4 text-muted-foreground"
aria-hidden="true"
/>
<GitBranch className="h-4 w-4 text-muted-foreground" aria-hidden="true" />
<span className="font-mono text-sm">{issue.branch}</span>
</div>
)}
{issue.pull_request && (
<div className="flex items-center gap-2">
<GitPullRequest
className="h-4 w-4 text-muted-foreground"
aria-hidden="true"
/>
<GitPullRequest className="h-4 w-4 text-muted-foreground" aria-hidden="true" />
<span className="text-sm">{issue.pull_request}</span>
<Badge variant="outline" className="text-xs">
Open

View File

@@ -136,10 +136,7 @@ export function IssueFilters({ filters, onFiltersChange, className }: IssueFilte
<div className="grid gap-4 sm:grid-cols-4">
<div className="space-y-2">
<Label htmlFor="priority-filter">Priority</Label>
<Select
value={filters.priority || 'all'}
onValueChange={handlePriorityChange}
>
<Select value={filters.priority || 'all'} onValueChange={handlePriorityChange}>
<SelectTrigger id="priority-filter">
<SelectValue placeholder="All" />
</SelectTrigger>
@@ -172,10 +169,7 @@ export function IssueFilters({ filters, onFiltersChange, className }: IssueFilte
</div>
<div className="space-y-2">
<Label htmlFor="assignee-filter">Assignee</Label>
<Select
value={filters.assignee || 'all'}
onValueChange={handleAssigneeChange}
>
<Select value={filters.assignee || 'all'} onValueChange={handleAssigneeChange}>
<SelectTrigger id="assignee-filter">
<SelectValue placeholder="All" />
</SelectTrigger>

View File

@@ -8,14 +8,7 @@
* @module features/issues/components/StatusBadge
*/
import {
CircleDot,
PlayCircle,
Clock,
AlertCircle,
CheckCircle2,
XCircle,
} from 'lucide-react';
import { CircleDot, PlayCircle, Clock, AlertCircle, CheckCircle2, XCircle } from 'lucide-react';
import { cn } from '@/lib/utils';
import type { IssueStatus } from '../types';
import { STATUS_CONFIG } from '../constants';
@@ -42,9 +35,7 @@ export function StatusBadge({ status, className, showLabel = true }: StatusBadge
return (
<div className={cn('flex items-center gap-1.5', config.color, className)}>
<Icon className="h-4 w-4" aria-hidden="true" />
{showLabel && (
<span className="text-sm font-medium">{config.label}</span>
)}
{showLabel && <span className="text-sm font-medium">{config.label}</span>}
<span className="sr-only">{config.label}</span>
</div>
);

View File

@@ -8,14 +8,7 @@
* @module features/issues/components/StatusWorkflow
*/
import {
CircleDot,
PlayCircle,
Clock,
AlertCircle,
CheckCircle2,
XCircle,
} from 'lucide-react';
import { CircleDot, PlayCircle, Clock, AlertCircle, CheckCircle2, XCircle } from 'lucide-react';
import { Card, CardHeader, CardTitle, CardContent } from '@/components/ui/card';
import { cn } from '@/lib/utils';
import type { IssueStatus } from '../types';
@@ -63,18 +56,14 @@ export function StatusWorkflow({
disabled={disabled}
className={cn(
'flex w-full items-center gap-2 rounded-lg p-2 text-left transition-colors',
isActive
? 'bg-primary/10 text-primary'
: 'hover:bg-muted',
isActive ? 'bg-primary/10 text-primary' : 'hover:bg-muted',
disabled && 'cursor-not-allowed opacity-50'
)}
onClick={() => !disabled && onStatusChange(status)}
>
<Icon className={cn('h-4 w-4', config.color)} aria-hidden="true" />
<span className="text-sm">{config.label}</span>
{isActive && (
<CheckCircle2 className="ml-auto h-4 w-4" aria-hidden="true" />
)}
{isActive && <CheckCircle2 className="ml-auto h-4 w-4" aria-hidden="true" />}
</button>
);
})}

View File

@@ -18,8 +18,7 @@ export const mockIssues: IssueSummary[] = [
number: 42,
type: 'story',
title: 'Implement user authentication flow',
description:
'Create complete authentication flow with login, register, and password reset.',
description: 'Create complete authentication flow with login, register, and password reset.',
status: 'in_progress',
priority: 'high',
labels: ['feature', 'auth', 'backend'],

View File

@@ -44,12 +44,7 @@ const DEFAULT_PAGE_LIMIT = 20;
export function useAgentTypes(params: AgentTypeListParams = {}) {
const { user } = useAuth();
const {
page = 1,
limit = DEFAULT_PAGE_LIMIT,
is_active = true,
search,
} = params;
const { page = 1, limit = DEFAULT_PAGE_LIMIT, is_active = true, search } = params;
return useQuery({
queryKey: agentTypeKeys.list({ page, limit, is_active, search }),
@@ -152,10 +147,7 @@ export function useUpdateAgentType() {
},
onSuccess: (updatedAgentType) => {
// Update the cache for this specific agent type
queryClient.setQueryData(
agentTypeKeys.detail(updatedAgentType.id),
updatedAgentType
);
queryClient.setQueryData(agentTypeKeys.detail(updatedAgentType.id), updatedAgentType);
// Invalidate lists to reflect changes
queryClient.invalidateQueries({ queryKey: agentTypeKeys.lists() });
},

View File

@@ -5,4 +5,8 @@
*/
export { useDebounce } from './useDebounce';
export { useProjectEvents, type UseProjectEventsOptions, type UseProjectEventsResult } from './useProjectEvents';
export {
useProjectEvents,
type UseProjectEventsOptions,
type UseProjectEventsResult,
} from './useProjectEvents';

View File

@@ -385,7 +385,16 @@ export function useProjectEvents(
mountedRef.current = false;
cleanup();
};
}, [autoConnect, isAuthenticated, accessToken, projectId, connectionState, connect, disconnect, cleanup]);
}, [
autoConnect,
isAuthenticated,
accessToken,
projectId,
connectionState,
connect,
disconnect,
cleanup,
]);
return {
events,

View File

@@ -53,10 +53,7 @@ const modelParamsSchema = z.object({
* Schema for agent type form fields
*/
export const agentTypeFormSchema = z.object({
name: z
.string()
.min(1, 'Name is required')
.max(255, 'Name must be less than 255 characters'),
name: z.string().min(1, 'Name is required').max(255, 'Name must be less than 255 characters'),
slug: z
.string()