From b0e6db36a198647f48259654bd251bde3b4d8646 Mon Sep 17 00:00:00 2001 From: Usman Baig Date: Sun, 22 Mar 2026 18:54:45 +0100 Subject: [PATCH] feat(pagespeed): add screenshot display and expandable diagnostics - Page screenshot thumbnail next to score gauges - Expandable audit rows with description and detail items table - Shows URLs, HTML snippets, wasted bytes/ms for each failing element - AuditRow component replaces flat diagnostic rows --- app/sites/[id]/pagespeed/page.tsx | 141 +++++++++++++++++++++--------- lib/api/pagespeed.ts | 5 ++ 2 files changed, 104 insertions(+), 42 deletions(-) diff --git a/app/sites/[id]/pagespeed/page.tsx b/app/sites/[id]/pagespeed/page.tsx index 6d7c894..8e86056 100644 --- a/app/sites/[id]/pagespeed/page.tsx +++ b/app/sites/[id]/pagespeed/page.tsx @@ -306,19 +306,34 @@ export default function PageSpeedPage() { {/* Section 1 — Score Overview */} -
-
- -
-
- -
-
- -
-
- +
+ {/* Score gauges */} +
+
+
+ +
+
+ +
+
+ +
+
+ +
+
+ {/* Screenshot */} + {currentCheck?.screenshot && ( +
+ {`${strategy} +
+ )}
{/* Last checked info */} @@ -432,16 +447,8 @@ export default function PageSpeedPage() { Opportunities ({opportunities.length}) -
- {opportunities.map(audit => ( -
-
- {audit.title} - {audit.display_value && ( - {audit.display_value} - )} -
- ))} +
+ {opportunities.map(audit => )}
)} @@ -452,16 +459,8 @@ export default function PageSpeedPage() { Diagnostics ({diagnostics.length}) -
- {diagnostics.map(audit => ( -
-
- {audit.title} - {audit.display_value && ( - {audit.display_value} - )} -
- ))} +
+ {diagnostics.map(audit => )}
)} @@ -472,16 +471,8 @@ export default function PageSpeedPage() { Passed Audits ({passed.length}) -
- {passed.map(audit => ( -
-
- {audit.title} - {audit.display_value && ( - {audit.display_value} - )} -
- ))} +
+ {passed.map(audit => )}
)} @@ -492,6 +483,72 @@ export default function PageSpeedPage() { ) } +// * Expandable audit row with description and detail items +function AuditRow({ audit }: { audit: AuditSummary }) { + return ( +
+ +
+ {audit.title} + {audit.display_value && ( + {audit.display_value} + )} + + + +
+
+ {/* Description */} + {audit.description && ( +

{audit.description}

+ )} + {/* Items table */} + {audit.details && Array.isArray(audit.details) && audit.details.length > 0 && ( +
+ + + {audit.details.slice(0, 10).map((item: Record, idx: number) => ( + + {/* URL or label */} + + {/* Wasted bytes */} + {item.wastedBytes != null && ( + + )} + {/* Total bytes */} + {item.totalBytes != null && !item.wastedBytes && ( + + )} + {/* Wasted ms */} + {item.wastedMs != null && ( + + )} + + ))} + +
+ {item.url ? ( + {item.url} + ) : item.node?.snippet ? ( + {item.node.snippet} + ) : item.label || item.groupLabel || item.statistic || ''} + + {item.wastedBytes < 1024 ? `${item.wastedBytes} B` : `${(item.wastedBytes / 1024).toFixed(1)} KiB`} + + {item.totalBytes < 1024 ? `${item.totalBytes} B` : `${(item.totalBytes / 1024).toFixed(1)} KiB`} + + {item.wastedMs < 1000 ? `${Math.round(item.wastedMs)}ms` : `${(item.wastedMs / 1000).toFixed(1)}s`} +
+ {audit.details.length > 10 && ( +

+ {audit.details.length - 10} more items

+ )} +
+ )} +
+
+ ) +} + // * Skeleton loading state function PageSpeedSkeleton() { return ( diff --git a/lib/api/pagespeed.ts b/lib/api/pagespeed.ts index 5650a9c..d3f4654 100644 --- a/lib/api/pagespeed.ts +++ b/lib/api/pagespeed.ts @@ -20,8 +20,12 @@ export interface AuditSummary { display_value?: string savings_ms?: number category: 'opportunity' | 'diagnostic' | 'passed' + details?: AuditDetailItem[] } +// eslint-disable-next-line @typescript-eslint/no-explicit-any +export type AuditDetailItem = Record + export interface PageSpeedCheck { id: string site_id: string @@ -37,6 +41,7 @@ export interface PageSpeedCheck { si_ms: number | null tti_ms: number | null audits: AuditSummary[] | null + screenshot?: string | null triggered_by: 'scheduled' | 'manual' checked_at: string }