fix(ui): address PR #644 review comments

Keyboard accessibility:
- Add role="button", tabIndex, onKeyDown, and aria-label to clickable divs
  in project-status-card, recent-activity-feed, and running-agents-panel

Bug fixes:
- Fix handleActivityClick to use projectPath instead of projectId for
  initializeProject and check result before navigating
- Fix error handling in use-multi-project-status to use data.error string
  directly instead of data.error?.message

Improvements:
- Use GitBranch icon instead of Folder for branch display in running-agents-panel
- Add error logging for failed project loads in overview.ts
- Use type import for FeatureLoader in projects/index.ts
- Add data-testid to mobile Overview button in dashboard-view
- Add locale options for consistent time formatting in overview-view

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Stefan de Vogelaere
2026-01-23 02:26:57 +01:00
parent c8ed3fafce
commit fb6d6bbf2f
8 changed files with 62 additions and 13 deletions

View File

@@ -579,6 +579,7 @@ export function DashboardView() {
size="icon"
onClick={() => navigate({ to: '/overview' })}
title="Projects Overview"
data-testid="projects-overview-button-mobile"
>
<LayoutDashboard className="w-4 h-4" />
</Button>

View File

@@ -501,7 +501,12 @@ export function OverviewView() {
{/* Footer timestamp */}
<div className="text-center text-xs text-muted-foreground pt-4">
Last updated: {new Date(overview.generatedAt).toLocaleTimeString()}
Last updated:{' '}
{new Date(overview.generatedAt).toLocaleTimeString(undefined, {
hour: '2-digit',
minute: '2-digit',
second: '2-digit',
})}
</div>
</div>
)}

View File

@@ -87,8 +87,17 @@ export function ProjectStatusCard({ project, onProjectClick }: ProjectStatusCard
}
}, [project, onProjectClick, upsertAndSetCurrentProject, navigate]);
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleClick();
}
};
return (
<div
role="button"
tabIndex={0}
className={cn(
'group relative rounded-xl border bg-card/60 backdrop-blur-sm transition-all duration-300 cursor-pointer hover:-translate-y-0.5',
project.healthStatus === 'active' && 'border-green-500/30 hover:border-green-500/50',
@@ -98,6 +107,8 @@ export function ProjectStatusCard({ project, onProjectClick }: ProjectStatusCard
project.healthStatus === 'idle' && 'border-border hover:border-brand-500/40'
)}
onClick={handleClick}
onKeyDown={handleKeyDown}
aria-label={`Open project ${project.projectName}`}
data-testid={`project-status-card-${project.projectId}`}
>
<div className="p-4">

View File

@@ -120,16 +120,19 @@ export function RecentActivityFeed({ activities, maxItems = 10 }: RecentActivity
const handleActivityClick = useCallback(
async (activity: RecentActivity) => {
try {
const initResult = await initializeProject(
// We need to find the project path - use projectId as workaround
// In real implementation, this would look up the path from projects list
activity.projectId
);
// Navigate to the project
const projectPath = activity.projectId;
// Get project path from the activity (projectId is actually the path in our data model)
const projectPath = activity.projectPath || activity.projectId;
const projectName = activity.projectName;
const initResult = await initializeProject(projectPath);
if (!initResult.success) {
toast.error('Failed to initialize project', {
description: initResult.error || 'Unknown error',
});
return;
}
upsertAndSetCurrentProject(projectPath, projectName);
if (activity.featureId) {
@@ -147,6 +150,16 @@ export function RecentActivityFeed({ activities, maxItems = 10 }: RecentActivity
[navigate, upsertAndSetCurrentProject]
);
const handleActivityKeyDown = useCallback(
(e: React.KeyboardEvent, activity: RecentActivity) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleActivityClick(activity);
}
},
[handleActivityClick]
);
if (displayActivities.length === 0) {
return (
<div className="flex flex-col items-center justify-center py-8 text-muted-foreground">
@@ -166,8 +179,12 @@ export function RecentActivityFeed({ activities, maxItems = 10 }: RecentActivity
return (
<div
key={activity.id}
role="button"
tabIndex={0}
className="group flex items-start gap-3 p-2 rounded-lg hover:bg-muted/50 cursor-pointer transition-colors"
onClick={() => handleActivityClick(activity)}
onKeyDown={(e) => handleActivityKeyDown(e, activity)}
aria-label={`${config.label}: ${activity.featureName || activity.message} in ${activity.projectName}`}
data-testid={`activity-item-${activity.id}`}
>
{/* Icon */}

View File

@@ -11,7 +11,7 @@ import { initializeProject } from '@/lib/project-init';
import { toast } from 'sonner';
import { cn } from '@/lib/utils';
import type { ProjectStatus } from '@automaker/types';
import { Bot, Activity, Folder, ArrowRight } from 'lucide-react';
import { Bot, Activity, GitBranch, ArrowRight } from 'lucide-react';
import { Button } from '@/components/ui/button';
interface RunningAgentsPanelProps {
@@ -65,6 +65,16 @@ export function RunningAgentsPanel({ projects }: RunningAgentsPanelProps) {
[navigate, upsertAndSetCurrentProject]
);
const handleAgentKeyDown = useCallback(
(e: React.KeyboardEvent, agent: RunningAgentInfo) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
handleAgentClick(agent);
}
},
[handleAgentClick]
);
if (runningAgents.length === 0) {
return (
<div className="flex flex-col items-center justify-center py-8 text-muted-foreground">
@@ -80,8 +90,12 @@ export function RunningAgentsPanel({ projects }: RunningAgentsPanelProps) {
{runningAgents.map((agent) => (
<div
key={agent.projectId}
role="button"
tabIndex={0}
className="group flex items-center gap-3 p-3 rounded-lg border border-green-500/20 bg-green-500/5 hover:bg-green-500/10 cursor-pointer transition-all"
onClick={() => handleAgentClick(agent)}
onKeyDown={(e) => handleAgentKeyDown(e, agent)}
aria-label={`View running agent for ${agent.projectName}`}
data-testid={`running-agent-${agent.projectId}`}
>
{/* Animated icon */}
@@ -111,7 +125,7 @@ export function RunningAgentsPanel({ projects }: RunningAgentsPanelProps) {
)}
{agent.activeBranch && (
<span className="flex items-center gap-1">
<Folder className="w-3 h-3" />
<GitBranch className="w-3 h-3" />
{agent.activeBranch}
</span>
)}