Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions src/components/shared/ScanSkeleton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import GlassCard from "../GlassCard";
export default function ScanSkeleton() {
return (
<div className="p-6 space-y-6 animate-pulse">
<div className="flex flex-col md:flex-row gap-6">
<GlassCard className="flex-1 p-8">
<div className="h-4 w-32 skeleton-shimmer rounded mb-4"></div>
<div className="h-16 w-40 skeleton-shimmer rounded mb-4"></div>
<div className="h-2 w-full skeleton-shimmer rounded"></div>
</GlassCard>

<GlassCard className="md:w-72 p-6">
<div className="h-4 w-24 skeleton-shimmer rounded mb-4"></div>
<div className="h-8 w-full skeleton-shimmer rounded mb-3"></div>
<div className="h-8 w-full skeleton-shimmer rounded"></div>
</GlassCard>
</div>

<GlassCard className="p-6">
<div className="h-5 w-40 skeleton-shimmer rounded mb-4"></div>
<div className="space-y-3">
<div className="h-16 skeleton-shimmer rounded"></div>
<div className="h-16 skeleton-shimmer rounded"></div>
<div className="h-16 skeleton-shimmer rounded"></div>
</div>
</GlassCard>

<GlassCard className="p-4">
<div className="h-12 skeleton-shimmer rounded"></div>
</GlassCard>
</div>
Comment on lines +2 to +31

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Expose loading semantics for assistive tech.

This skeleton is now the loading UI, but it has no role="status"/aria-live/loading text, so screen reader users don’t get a meaningful loading announcement.

Suggested fix
 export default function ScanSkeleton()  {
   return (
-    <div className="p-6 space-y-6 animate-pulse">
+    <div
+      className="p-6 space-y-6 animate-pulse"
+      role="status"
+      aria-live="polite"
+      aria-busy="true"
+    >
+      <span className="sr-only">Loading analysis dashboard</span>
       <div className="flex flex-col md:flex-row gap-6">
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export default function ScanSkeleton() {
return (
<div className="p-6 space-y-6 animate-pulse">
<div className="flex flex-col md:flex-row gap-6">
<GlassCard className="flex-1 p-8">
<div className="h-4 w-32 skeleton-shimmer rounded mb-4"></div>
<div className="h-16 w-40 skeleton-shimmer rounded mb-4"></div>
<div className="h-2 w-full skeleton-shimmer rounded"></div>
</GlassCard>
<GlassCard className="md:w-72 p-6">
<div className="h-4 w-24 skeleton-shimmer rounded mb-4"></div>
<div className="h-8 w-full skeleton-shimmer rounded mb-3"></div>
<div className="h-8 w-full skeleton-shimmer rounded"></div>
</GlassCard>
</div>
<GlassCard className="p-6">
<div className="h-5 w-40 skeleton-shimmer rounded mb-4"></div>
<div className="space-y-3">
<div className="h-16 skeleton-shimmer rounded"></div>
<div className="h-16 skeleton-shimmer rounded"></div>
<div className="h-16 skeleton-shimmer rounded"></div>
</div>
</GlassCard>
<GlassCard className="p-4">
<div className="h-12 skeleton-shimmer rounded"></div>
</GlassCard>
</div>
export default function ScanSkeleton() {
return (
<div
className="p-6 space-y-6 animate-pulse"
role="status"
aria-live="polite"
aria-busy="true"
>
<span className="sr-only">Loading analysis dashboard</span>
<div className="flex flex-col md:flex-row gap-6">
<GlassCard className="flex-1 p-8">
<div className="h-4 w-32 skeleton-shimmer rounded mb-4"></div>
<div className="h-16 w-40 skeleton-shimmer rounded mb-4"></div>
<div className="h-2 w-full skeleton-shimmer rounded"></div>
</GlassCard>
<GlassCard className="md:w-72 p-6">
<div className="h-4 w-24 skeleton-shimmer rounded mb-4"></div>
<div className="h-8 w-full skeleton-shimmer rounded mb-3"></div>
<div className="h-8 w-full skeleton-shimmer rounded"></div>
</GlassCard>
</div>
<GlassCard className="p-6">
<div className="h-5 w-40 skeleton-shimmer rounded mb-4"></div>
<div className="space-y-3">
<div className="h-16 skeleton-shimmer rounded"></div>
<div className="h-16 skeleton-shimmer rounded"></div>
<div className="h-16 skeleton-shimmer rounded"></div>
</div>
</GlassCard>
<GlassCard className="p-4">
<div className="h-12 skeleton-shimmer rounded"></div>
</GlassCard>
</div>
)
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/shared/ScanSkeleton.tsx` around lines 2 - 31, The ScanSkeleton
component currently provides no accessible loading semantics; update the
top-level container returned by ScanSkeleton to include role="status" and
aria-live="polite" (or "assertive" if immediate interruption is desired) and add
a visually-hidden text node such as a <span className="sr-only">Loading…</span>
(or localized message) inside it so screen readers announce the loading state;
keep the existing visual structure (GlassCard usage) but ensure the hidden text
is inside the same root element so assistive tech receives the status.

);
}
21 changes: 21 additions & 0 deletions src/index.css
Original file line number Diff line number Diff line change
Expand Up @@ -396,4 +396,25 @@ input::placeholder {

@media print {
nav, .print\:hidden { display: none !important; }
}
@keyframes shimmer {
0% {
background-position: -200% 0;
}

100% {
background-position: 200% 0;
}
}

.skeleton-shimmer {
background: linear-gradient(
90deg,
var(--color-surface-mid) 25%,
var(--color-surface-highest) 50%,
var(--color-surface-mid) 75%
);

background-size: 200% 100%;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Resolve the Stylelint rule violation in .skeleton-shimmer.

Line 418 is currently flagged by Stylelint (declaration-empty-line-before). Please remove the extra empty line before background-size (or align with your stylelint config) to keep lint green.

🧰 Tools
🪛 Stylelint (17.12.0)

[error] 418-418: Expected no empty line before declaration (declaration-empty-line-before)

(declaration-empty-line-before)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/index.css` at line 418, The .skeleton-shimmer rule contains an extra
empty line before the background-size declaration which triggers the Stylelint
declaration-empty-line-before error; open the .skeleton-shimmer block, remove
the blank line immediately preceding the background-size: 200% 100%; line (or
adjust to match your project's declaration-empty-line-before style) so the
declaration follows the previous rule without an unexpected empty line.

Source: Linters/SAST tools

animation: shimmer 1.5s linear infinite;
}
Comment on lines +410 to 420

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Add a reduced-motion fallback for shimmer animation.

Line 419 runs an infinite animation, but there’s no prefers-reduced-motion override. This can cause accessibility issues for motion-sensitive users and should be disabled/reduced in that mode.

Suggested fix
 .skeleton-shimmer {
   background: linear-gradient(
     90deg,
     var(--color-surface-mid) 25%,
     var(--color-surface-highest) 50%,
     var(--color-surface-mid) 75%
   );
   background-size: 200% 100%;
   animation: shimmer 1.5s linear infinite;
 }
+
+@media (prefers-reduced-motion: reduce) {
+  .skeleton-shimmer {
+    animation: none;
+    background-position: 0 0;
+  }
+}
🧰 Tools
🪛 Stylelint (17.12.0)

[error] 418-418: Expected no empty line before declaration (declaration-empty-line-before)

(declaration-empty-line-before)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/index.css` around lines 410 - 420, The shimmer animation on the
.skeleton-shimmer selector lacks a prefers-reduced-motion override; add a media
query for prefers-reduced-motion: reduce that disables or greatly reduces the
animation for .skeleton-shimmer (e.g., set animation to none or
animation-duration to 0 and fix background-position), and ensure the `@keyframes`
shimmer remains but is not applied in that media query so motion-sensitive users
do not get the infinite shimmer.

14 changes: 6 additions & 8 deletions src/pages/AnalysisDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import GlassCard from '../components/GlassCard';
import StatusTerminal from '../components/StatusTerminal';
import { api } from '../lib/api';
import type { ScanResult } from '../lib/types';
import ScanSkeleton from "../components/shared/ScanSkeleton";

const BIOMARKER_META = {
gill_saturation: { label: 'Gill Saturation', icon: Droplets },
Expand All @@ -21,6 +22,7 @@ function gradeColor(grade: string) {
}

export default function AnalysisDashboard() {

const [params] = useSearchParams();
const [scan, setScan] = useState<ScanResult | null>(null);
const [loading, setLoading] = useState(true);
Expand All @@ -43,20 +45,16 @@ export default function AnalysisDashboard() {
} catch (err) {
setError(err instanceof Error ? err.message : 'Failed to load scan data.');
} finally {
setLoading(false);
setLoading(false);
}
}
load();
}, [params]);

// ── Loading state ────────────────────────────────────────────────────────
if (loading) {
return (
<div className="min-h-[calc(100vh-4rem)] flex items-center justify-center">
<StatusTerminal messages={['LOADING_ANALYSIS...', 'FETCHING_RESULT']} />
</div>
);
}
if (loading) {
return <ScanSkeleton />;
}

// ── Error state ──────────────────────────────────────────────────────────
if (error || !scan) {
Expand Down
Loading