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

@@ -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';