-
Notifications
You must be signed in to change notification settings - Fork 0
Add Skeleton Loading States for Spec Views #139
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
c3009cb
98cfd38
d187016
1513e41
d6f0f22
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,110 @@ | ||
| import { Card, CardContent, CardHeader } from '../ui/card'; | ||
|
|
||
| interface PatternSuggestionsSkeletonProps { | ||
| /** Number of pattern cards to render */ | ||
| count?: number; | ||
| /** Whether to show header section */ | ||
| showHeader?: boolean; | ||
| } | ||
|
|
||
| /** | ||
| * Skeleton loader for PatternSuggestions in CreateSpecView | ||
| * Matches the structure: header with badges, pattern cards with category badges, | ||
| * confidence levels, pattern text, action buttons, and expandable details | ||
| */ | ||
| export function PatternSuggestionsSkeleton({ | ||
| count = 3, | ||
| showHeader = true | ||
| }: PatternSuggestionsSkeletonProps) { | ||
|
Check warning on line 18 in apps/frontend/src/renderer/components/skeletons/PatternSuggestionsSkeleton.tsx
|
||
| return ( | ||
| <div className="space-y-4 animate-pulse"> | ||
| {/* Header Section */} | ||
| {showHeader && ( | ||
| <div className="flex items-start justify-between"> | ||
| <div className="flex-1 space-y-2"> | ||
| {/* Title with icon */} | ||
| <div className="flex items-center gap-2"> | ||
| <div className="h-5 w-5 bg-muted rounded" /> | ||
| <div className="h-6 w-48 bg-muted rounded" /> | ||
| </div> | ||
| {/* Subtitle */} | ||
| <div className="h-4 w-80 bg-muted rounded" /> | ||
| </div> | ||
| {/* Summary badges */} | ||
| <div className="flex items-center gap-2"> | ||
| <div className="h-6 w-24 bg-muted rounded-md" /> | ||
| <div className="h-6 w-20 bg-muted rounded-md" /> | ||
| <div className="h-6 w-22 bg-muted rounded-md" /> | ||
| </div> | ||
| </div> | ||
| )} | ||
|
|
||
| {/* Pattern Cards */} | ||
| <div className="space-y-3"> | ||
| {Array.from({ length: count }).map((_, index) => ( | ||
| <Card key={index} className="border"> | ||
|
Check warning on line 45 in apps/frontend/src/renderer/components/skeletons/PatternSuggestionsSkeleton.tsx
|
||
| <CardHeader className="pb-3"> | ||
| <div className="flex items-start justify-between gap-3"> | ||
| {/* Main content area */} | ||
| <div className="flex-1 space-y-2"> | ||
| {/* Category Badge and Confidence Level */} | ||
| <div className="flex items-center gap-2"> | ||
| <div className="h-5 w-32 bg-muted rounded-md" /> | ||
| <div className="h-4 w-20 bg-muted rounded" /> | ||
| </div> | ||
|
|
||
| {/* Pattern Text Lines */} | ||
| <div className="space-y-1.5"> | ||
| <div className="h-4 w-full bg-muted rounded" /> | ||
| <div className="h-4 w-5/6 bg-muted rounded" /> | ||
| {index % 2 === 0 && ( | ||
| <div className="h-4 w-4/6 bg-muted rounded" /> | ||
| )} | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* Action Buttons */} | ||
| <div className="flex items-center gap-1"> | ||
| <div className="h-8 w-8 bg-muted rounded-md" /> | ||
| <div className="h-8 w-8 bg-muted rounded-md" /> | ||
| <div className="h-8 w-8 bg-muted rounded-md" /> | ||
| </div> | ||
| </div> | ||
| </CardHeader> | ||
|
|
||
| <CardContent className="pt-0"> | ||
| {/* Expandable Details Section */} | ||
| <div className="mt-3 space-y-2"> | ||
| {/* "Show details" button placeholder */} | ||
| <div className="h-4 w-24 bg-muted rounded" /> | ||
|
|
||
| {/* Details content (simulate expanded state ~50% of the time) */} | ||
| {index % 2 === 0 && ( | ||
| <div className="mt-3 space-y-2 text-xs"> | ||
| {/* Reasoning line */} | ||
| <div className="flex items-start gap-2"> | ||
| <div className="h-3 w-16 bg-muted rounded" /> | ||
| <div className="h-3 w-full bg-muted rounded" /> | ||
| </div> | ||
|
|
||
| {/* Metadata row */} | ||
| <div className="flex items-center gap-4"> | ||
| <div className="flex items-center gap-2"> | ||
| <div className="h-3 w-14 bg-muted rounded" /> | ||
| <div className="h-3 w-8 bg-muted rounded" /> | ||
| </div> | ||
| <div className="flex items-center gap-2"> | ||
| <div className="h-3 w-10 bg-muted rounded" /> | ||
| <div className="h-3 w-16 bg-muted rounded" /> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| )} | ||
| </div> | ||
| </CardContent> | ||
| </Card> | ||
| ))} | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,108 @@ | ||
| import { Card } from '../ui/card'; | ||
|
|
||
| interface SpecDetailSkeletonProps { | ||
| /** Number of phase skeletons to render */ | ||
| phaseCount?: number; | ||
| /** Whether to show description placeholder */ | ||
| showDescription?: boolean; | ||
| } | ||
|
|
||
| /** | ||
| * Skeleton loader for SpecDetail/TaskOverview implementation plan | ||
| * Matches the structure: section header, phases, subtasks, metadata | ||
| */ | ||
| export function SpecDetailSkeleton({ phaseCount = 3, showDescription = true }: SpecDetailSkeletonProps) { | ||
|
Check warning on line 14 in apps/frontend/src/renderer/components/skeletons/SpecDetailSkeleton.tsx
|
||
| return ( | ||
| <div className="p-5 space-y-5 animate-pulse"> | ||
| {/* Section Header */} | ||
| <div className="mb-4"> | ||
| <div className="flex items-center gap-2"> | ||
| <div className="h-3 w-3 bg-muted rounded" /> | ||
| <div className="h-5 w-48 bg-muted rounded" /> | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* Optional Description */} | ||
| {showDescription && ( | ||
| <div className="mb-4"> | ||
| <div className="h-4 w-full bg-muted rounded" /> | ||
| <div className="h-4 w-2/3 bg-muted rounded mt-2" /> | ||
| </div> | ||
| )} | ||
|
|
||
| {/* Phases */} | ||
| <div className="space-y-2"> | ||
| {Array.from({ length: phaseCount }).map((_, phaseIndex) => ( | ||
| <Card | ||
| key={phaseIndex} | ||
|
Check warning on line 37 in apps/frontend/src/renderer/components/skeletons/SpecDetailSkeleton.tsx
|
||
| className="border border-border rounded-lg" | ||
| > | ||
| {/* Phase Header */} | ||
| <div className="px-4 py-3 flex items-center gap-3"> | ||
| {/* Chevron placeholder */} | ||
| <div className="h-4 w-4 bg-muted rounded shrink-0" /> | ||
|
|
||
| {/* Status icon placeholder */} | ||
| <div className="h-4 w-4 bg-muted rounded-full shrink-0" /> | ||
|
|
||
| {/* Phase title and badge */} | ||
| <div className="flex-1 min-w-0"> | ||
| <div className="flex items-center gap-2 flex-wrap mb-1"> | ||
| <div className="h-4 w-40 bg-muted rounded" /> | ||
| <div className="h-5 w-20 bg-muted rounded-md" /> | ||
| </div> | ||
|
|
||
| {/* Progress text */} | ||
| <div className="h-3 w-24 bg-muted rounded" /> | ||
| </div> | ||
| </div> | ||
|
|
||
| {/* Subtasks (simulate expanded phase) */} | ||
| <div className="px-4 pb-3 space-y-2"> | ||
| {/* Separator */} | ||
| <div className="h-px w-full bg-border" /> | ||
|
|
||
| {/* Subtask items */} | ||
| {Array.from({ length: 3 + (phaseIndex % 2) }).map((_, subtaskIndex) => ( | ||
| <div | ||
| key={subtaskIndex} | ||
|
Check warning on line 68 in apps/frontend/src/renderer/components/skeletons/SpecDetailSkeleton.tsx
|
||
| className="flex items-start gap-3 p-2" | ||
| > | ||
| {/* Status badge placeholder */} | ||
| <div className="h-5 w-16 bg-muted rounded-md shrink-0 mt-0.5" /> | ||
|
|
||
| {/* Subtask content */} | ||
| <div className="flex-1 min-w-0 space-y-1"> | ||
| {/* Subtask description */} | ||
| <div className="h-4 w-full bg-muted rounded" /> | ||
| {/* Verification text (optional) */} | ||
| {subtaskIndex % 2 === 0 && ( | ||
| <div className="h-3 w-32 bg-muted rounded" /> | ||
| )} | ||
| </div> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| </Card> | ||
| ))} | ||
| </div> | ||
|
|
||
| {/* QA Report Section (skeleton) */} | ||
| <div className="space-y-3 pt-4"> | ||
| {/* QA Report header */} | ||
| <div className="flex items-center gap-2"> | ||
| <div className="h-3 w-3 bg-muted rounded" /> | ||
| <div className="h-5 w-24 bg-muted rounded" /> | ||
| </div> | ||
|
|
||
| {/* QA content lines */} | ||
| <div className="space-y-2 pl-5"> | ||
| <div className="h-4 w-full bg-muted rounded" /> | ||
| <div className="h-4 w-5/6 bg-muted rounded" /> | ||
| <div className="h-4 w-4/6 bg-muted rounded" /> | ||
| <div className="h-4 w-3/4 bg-muted rounded" /> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| export { ChangelogSkeleton } from './ChangelogSkeleton'; | ||
| export { IssueListSkeleton } from './IssueListSkeleton'; | ||
| export { PRListSkeleton } from './PRListSkeleton'; | ||
| export { ProjectListSkeleton } from './ProjectListSkeleton'; | ||
| export { TaskCardSkeleton } from './TaskCardSkeleton'; | ||
| export { SpecDetailSkeleton } from './SpecDetailSkeleton'; | ||
| export { PatternSuggestionsSkeleton } from './PatternSuggestionsSkeleton'; |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -16,6 +16,7 @@ import { | |||||||||||||||||||||||||||||||||
| import { Badge } from '../ui/badge'; | ||||||||||||||||||||||||||||||||||
| import { Separator } from '../ui/separator'; | ||||||||||||||||||||||||||||||||||
| import { ScrollArea } from '../ui/scroll-area'; | ||||||||||||||||||||||||||||||||||
| import { SpecDetailSkeleton } from '../skeletons/SpecDetailSkeleton'; | ||||||||||||||||||||||||||||||||||
| import { cn } from '../../lib/utils'; | ||||||||||||||||||||||||||||||||||
| import type { Task, ImplementationPlan, Phase, SubtaskStatus, QAEscalation } from '../../../shared/types'; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
|
|
@@ -124,11 +125,7 @@ export function TaskOverview({ task }: TaskOverviewProps) { | |||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if (isLoading) { | ||||||||||||||||||||||||||||||||||
| return ( | ||||||||||||||||||||||||||||||||||
| <div className="flex items-center justify-center py-12"> | ||||||||||||||||||||||||||||||||||
| <Loader2 className="h-8 w-8 animate-spin text-muted-foreground" /> | ||||||||||||||||||||||||||||||||||
| </div> | ||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||
| return <SpecDetailSkeleton />; | ||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||
|
Comment on lines
127
to
129
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Add an accessible loading announcement for the skeleton state. Line 128 now renders only visual placeholders, so assistive tech loses explicit loading feedback. Suggested fix if (isLoading) {
- return <SpecDetailSkeleton />;
+ return (
+ <div role="status" aria-live="polite" aria-busy="true">
+ <span className="sr-only">{t('tasks:overview.loading')}</span>
+ <SpecDetailSkeleton />
+ </div>
+ );
}Also add As per coding guidelines: "All user-facing text must use i18n translation keys from 📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
| if (error) { | ||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Loading skeleton needs an accessible, localized status message.
Line 448 renders only visual placeholders, so users on assistive tech may not get loading feedback.
Suggested fix
Please also add
tasks:createSpec.patternSuggestionsLoadingto English and French locale files.As per coding guidelines: "All user-facing text must use i18n translation keys from
react-i18nextwith formatnamespace:section.key. Never use hardcoded strings in JSX/TSX. Update all language files (minimum: English and French) when adding new text."📝 Committable suggestion
🤖 Prompt for AI Agents