From d5fff7c6ddb7aee0a046f41d29440757db299820 Mon Sep 17 00:00:00 2001 From: Michele Bigi Date: Tue, 22 Sep 2026 18:41:20 +0200 Subject: [PATCH] Apple-like UI shell: findings tree and HDMI 1.4 UNKNOWN. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Keep Error/Warning/Info folders with + disclosure, then domains/rules, then Certificazioni/Protocolli. FAIL stays first. HDMI 1.4 TMDS Zdiff renders UNKNOWN — catalog JSON unchanged. --- periscope/src/frontend/content/changelog.md | 9 + periscope/src/frontend/package.json | 2 +- .../frontend/src/app/(app)/dashboard/page.tsx | 148 ++------- .../src/frontend/src/app/(app)/layout.tsx | 2 +- .../app/(app)/project/[id]/report/page.tsx | 39 ++- periscope/src/frontend/src/app/globals.css | 50 +-- periscope/src/frontend/src/app/layout.tsx | 19 +- .../src/components/layout/page-header.tsx | 25 ++ .../src/components/layout/sidebar.tsx | 160 +++++----- .../src/components/report/findings-list.tsx | 8 +- .../src/components/report/findings-tree.tsx | 300 +++++++++++++----- .../components/report/protocol-section.tsx | 254 +++++++-------- .../src/components/report/report-summary.tsx | 92 ++---- .../src/components/report/status-badge.tsx | 17 +- .../frontend/src/lib/findings-forest.test.ts | 24 +- .../src/frontend/src/lib/findings-forest.ts | 208 +++++++++++- .../frontend/src/lib/protocol-report.test.ts | 37 +++ .../src/frontend/src/lib/protocol-report.ts | 67 +++- periscope/src/frontend/src/lib/version.ts | 2 +- 19 files changed, 878 insertions(+), 585 deletions(-) create mode 100644 periscope/src/frontend/src/components/layout/page-header.tsx diff --git a/periscope/src/frontend/content/changelog.md b/periscope/src/frontend/content/changelog.md index fe1e9d0..8716c2a 100644 --- a/periscope/src/frontend/content/changelog.md +++ b/periscope/src/frontend/content/changelog.md @@ -2,6 +2,15 @@ What's new in Periscope. +## 2.83.0 — 2026-09-22 — Apple-like shell, findings tree, HDMI 1.4 UNKNOWN + +Shell Finder/Settings: sidebar, large title, system font, hairline. Findings tree keeps Error / Warning / Info folders (FAIL first) with + disclosure, then domains/rules, then Certificazioni/Protocolli. HDMI 1.4 TMDS electrical displays UNKNOWN — no guessed Zdiff. Catalog JSON untouched. + +- [Changed] App chrome (`sidebar`, `globals.css`, dashboard list, report large title). +- [Changed] Findings tree grouping + protocol section inside the tree. +- [Changed] Protocol UI: HDMI 1.4 TMDS Zdiff/limit/measured show `UNKNOWN`. +- [Changed] Frontend tests `findings-forest`, `protocol-report`. + ## 2.82.0 — 2026-09-22 — HDMI 2.0+ TI fields use source_type VENDOR Michele exception: HDMI 2.0 and FRL electrical/PCB numbers from TMDS1204 / SLLA633 use `source_type=VENDOR` (not HDMI Forum STANDARD). TMDS1204-specific limits (skew, vias, FRL data_rate, SIGDET) are PHY_DEPENDENT with a document/rev/section cite and no number. HDMI 1.4 TMDS stays UNKNOWN. pcbsync.com not packed. diff --git a/periscope/src/frontend/package.json b/periscope/src/frontend/package.json index 33648fb..39764ac 100644 --- a/periscope/src/frontend/package.json +++ b/periscope/src/frontend/package.json @@ -1,6 +1,6 @@ { "name": "periscope-web", - "version": "2.82.0", + "version": "2.83.0", "private": true, "scripts": { "sync-version": "node scripts/sync-version.mjs", diff --git a/periscope/src/frontend/src/app/(app)/dashboard/page.tsx b/periscope/src/frontend/src/app/(app)/dashboard/page.tsx index d75771f..bfb6fdd 100644 --- a/periscope/src/frontend/src/app/(app)/dashboard/page.tsx +++ b/periscope/src/frontend/src/app/(app)/dashboard/page.tsx @@ -6,16 +6,14 @@ import { useRouter, useSearchParams } from "next/navigation"; import { ArrowRight, CheckCircle2, - LayoutGrid, - List, Loader2, X, } from "lucide-react"; import { useCredits } from "@/components/billing/credits-context"; import { CreateProjectDialog } from "@/components/dashboard/create-project-dialog"; import { OnboardingSurvey } from "@/components/dashboard/onboarding-survey"; -import { ProjectCard } from "@/components/dashboard/project-card"; import { ProjectsTable } from "@/components/dashboard/projects-table"; +import { PageHeader } from "@/components/layout/page-header"; import { Button } from "@/components/ui/button"; import { Skeleton } from "@/components/ui/skeleton"; import { @@ -23,13 +21,10 @@ import { fetchProjects, reconcileCheckoutSession, } from "@/lib/api"; -import type { CreditSnapshot, Project } from "@/lib/types"; -import { cn } from "@/lib/utils"; +import type { Project } from "@/lib/types"; -type LayoutKind = "cards" | "table"; type TopupBanner = "pending" | "activated" | "timeout" | "dismissed"; -const LAYOUT_KEY = "periscopex:dashboard:view"; const CLONE_STASH_KEY = "periscopex:cloneAsNewProjectId"; export default function DashboardPage() { @@ -62,28 +57,9 @@ function DashboardBody() { const [bannerVisible, setBannerVisible] = useState(false); const [topupPhase, setTopupPhase] = useState("pending"); const [activatedDetail, setActivatedDetail] = useState(null); - const [layout, setLayout] = useState("cards"); const ordered = useMemo(() => newestFirst(projects), [projects]); - useEffect(() => { - try { - const stored = localStorage.getItem(LAYOUT_KEY); - if (stored === "cards" || stored === "table") setLayout(stored); - } catch { - /* storage blocked */ - } - }, []); - - function persistLayout(next: LayoutKind) { - setLayout(next); - try { - localStorage.setItem(LAYOUT_KEY, next); - } catch { - /* ignore */ - } - } - function stripCheckoutQuery() { const url = new URL(window.location.href); url.searchParams.delete("topup"); @@ -208,7 +184,7 @@ function DashboardBody() { } return ( -
+
{bannerVisible && topupPhase !== "dismissed" && ( )} -
-
-

Projects

- -
- setRerunProject(null)} - cloneAsNewProject={cloneAsNewProject} - onCloneAsNewDone={() => setCloneAsNewProject(null)} - onCreateProject={upsertProject} - /> -
- - {!loading && projects.length > 0 && ( -
-
- - -
-
- )} + setRerunProject(null)} + cloneAsNewProject={cloneAsNewProject} + onCloneAsNewDone={() => setCloneAsNewProject(null)} + onCreateProject={upsertProject} + /> + } + /> {loading ? ( -
- {Array.from({ length: 3 }).map((_, i) => ( - +
+ {Array.from({ length: 5 }).map((_, i) => ( + ))}
) : projects.length === 0 ? ( -

- No projects yet. Create one to get started. -

- ) : layout === "table" ? ( +
+

Nessun progetto

+

+ Crea un progetto e carica netlist, BOM e PCB. L’esame parte da lì. +

+
+ ) : ( - ) : ( -
- {ordered.map((p) => ( - dropProject(p.id)} - onRerun={setRerunProject} - /> - ))} -
)}
); } -function CreditsLine({ credits }: { credits: CreditSnapshot | null }) { - if (!credits) { - return ( -

- Schematic validation projects -

- ); - } - return ( -

- {credits.balance.toFixed(2)} credits - {" · "} - - buy more - - {" · "} - - ledger - -

- ); -} - function TopupBanner({ phase, detail, diff --git a/periscope/src/frontend/src/app/(app)/layout.tsx b/periscope/src/frontend/src/app/(app)/layout.tsx index cd69735..a76c84a 100644 --- a/periscope/src/frontend/src/app/(app)/layout.tsx +++ b/periscope/src/frontend/src/app/(app)/layout.tsx @@ -14,7 +14,7 @@ export default function AppShellLayout({
-
+
{children}
diff --git a/periscope/src/frontend/src/app/(app)/project/[id]/report/page.tsx b/periscope/src/frontend/src/app/(app)/project/[id]/report/page.tsx index 3d5f093..7b63c5e 100644 --- a/periscope/src/frontend/src/app/(app)/project/[id]/report/page.tsx +++ b/periscope/src/frontend/src/app/(app)/project/[id]/report/page.tsx @@ -18,7 +18,7 @@ import { useReviewedFindings } from "@/hooks/use-reviewed-findings"; import { ReportSummary } from "@/components/report/report-summary"; import { FindingsList } from "@/components/report/findings-list"; import { FindingFocusView } from "@/components/report/finding-focus-view"; -import { ProtocolSection } from "@/components/report/protocol-section"; +import { PageHeader } from "@/components/layout/page-header"; import { Button } from "@/components/ui/button"; import { Skeleton } from "@/components/ui/skeleton"; import { Toast, useToast } from "@/components/ui/toast"; @@ -240,18 +240,14 @@ function ReportContent({ projectId }: { projectId: string }) { return (
-
-
-
-

Validation Report

-

- {report.findings.length} findings ·{" "} - {new Date(report.timestamp).toLocaleDateString()} -

-
-
+
+ -
-
+
+ } + /> - {failedReviews > 0 && (

@@ -344,12 +340,12 @@ function ReportContent({ projectId }: { projectId: string }) {

)} - {report.summary.total === 0 ? ( -
-

No findings

-

- No IC datasheets were available for review. Upload datasheets for each IC - on the project page and re-run the pipeline to get findings. + {report.summary.total === 0 && + !(report.protocol_certification?.recognized_instances?.length) ? ( +

+

Nessun finding

+

+ Carica i datasheet degli IC sulla pagina progetto e rilancia l’esame.

) : ( @@ -370,6 +366,7 @@ function ReportContent({ projectId }: { projectId: string }) { reportedFindingIds={reportedFindingIds} reviews={reviews} onReviewSaved={handleReviewSaved} + protocolSection={report.protocol_certification} /> )}
diff --git a/periscope/src/frontend/src/app/globals.css b/periscope/src/frontend/src/app/globals.css index 2045e81..bbc6891 100644 --- a/periscope/src/frontend/src/app/globals.css +++ b/periscope/src/frontend/src/app/globals.css @@ -6,10 +6,11 @@ /* Map semantic tokens onto Tailwind theme keys. Values live on :root / .dark. */ @theme inline { - --font-sans: "Geist", "Geist Fallback", ui-sans-serif, system-ui, sans-serif; - --font-mono: "Geist Mono", "Geist Mono Fallback", ui-monospace, monospace; + --font-sans: -apple-system, BlinkMacSystemFont, "SF Pro Text", "SF Pro Display", + system-ui, "Segoe UI", sans-serif; + --font-mono: ui-monospace, "SF Mono", "Geist Mono", Menlo, monospace; --font-heading: var(--font-sans); - --font-headline: var(--font-dm-serif-display), Georgia, serif; + --font-headline: var(--font-sans); --color-background: var(--background); --color-foreground: var(--foreground); @@ -53,15 +54,15 @@ } :root { - --radius: 0.625rem; - --background: oklch(1 0 0); - --foreground: oklch(0.145 0 0); - --card: oklch(1 0 0); - --card-foreground: oklch(0.145 0 0); - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.145 0 0); - --primary: oklch(0.205 0 0); - --primary-foreground: oklch(0.985 0 0); + --radius: 0.6875rem; + --background: #f5f5f7; + --foreground: #1d1d1f; + --card: #ffffff; + --card-foreground: #1d1d1f; + --popover: #ffffff; + --popover-foreground: #1d1d1f; + --primary: #007aff; + --primary-foreground: #ffffff; --secondary: oklch(0.97 0 0); --secondary-foreground: oklch(0.205 0 0); --muted: oklch(0.97 0 0); @@ -69,22 +70,22 @@ --accent: oklch(0.97 0 0); --accent-foreground: oklch(0.205 0 0); --destructive: oklch(0.577 0.245 27.325); - --border: oklch(0.922 0 0); - --input: oklch(0.922 0 0); - --ring: oklch(0.708 0 0); + --border: rgba(0, 0, 0, 0.1); + --input: rgba(0, 0, 0, 0.1); + --ring: #007aff; --chart-1: oklch(0.87 0 0); --chart-2: oklch(0.556 0 0); --chart-3: oklch(0.439 0 0); --chart-4: oklch(0.371 0 0); --chart-5: oklch(0.269 0 0); - --sidebar: oklch(0.985 0 0); - --sidebar-foreground: oklch(0.145 0 0); - --sidebar-primary: oklch(0.205 0 0); - --sidebar-primary-foreground: oklch(0.985 0 0); - --sidebar-accent: oklch(0.97 0 0); - --sidebar-accent-foreground: oklch(0.205 0 0); - --sidebar-border: oklch(0.922 0 0); - --sidebar-ring: oklch(0.708 0 0); + --sidebar: #f5f5f7; + --sidebar-foreground: #1d1d1f; + --sidebar-primary: #007aff; + --sidebar-primary-foreground: #ffffff; + --sidebar-accent: rgba(0, 122, 255, 0.12); + --sidebar-accent-foreground: #007aff; + --sidebar-border: rgba(0, 0, 0, 0.1); + --sidebar-ring: #007aff; } .dark { @@ -129,7 +130,8 @@ @apply font-sans; } body { - @apply bg-background text-foreground; + @apply bg-background text-foreground antialiased; + font-feature-settings: "tnum" 1; } } diff --git a/periscope/src/frontend/src/app/layout.tsx b/periscope/src/frontend/src/app/layout.tsx index 63dee3e..299a01a 100644 --- a/periscope/src/frontend/src/app/layout.tsx +++ b/periscope/src/frontend/src/app/layout.tsx @@ -1,5 +1,4 @@ import type { Metadata, Viewport } from "next"; -import { DM_Serif_Display, Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; import { ThemeProvider } from "@/components/theme/theme-provider"; import { ClerkThemeProvider } from "@/components/theme/clerk-theme-provider"; @@ -13,22 +12,6 @@ import { TWITTER_HANDLE, } from "@/lib/site"; -const geistSans = Geist({ - variable: "--font-geist-sans", - subsets: ["latin"], -}); - -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", - subsets: ["latin"], -}); - -const dmSerifDisplay = DM_Serif_Display({ - variable: "--font-dm-serif-display", - weight: "400", - subsets: ["latin"], -}); - export const metadata: Metadata = { metadataBase: new URL(SITE_URL), title: { @@ -109,7 +92,7 @@ export default function RootLayout({ diff --git a/periscope/src/frontend/src/components/layout/page-header.tsx b/periscope/src/frontend/src/components/layout/page-header.tsx new file mode 100644 index 0000000..bc7ee11 --- /dev/null +++ b/periscope/src/frontend/src/components/layout/page-header.tsx @@ -0,0 +1,25 @@ +import type { ReactNode } from "react"; + +export function PageHeader({ + title, + meta, + actions, +}: { + title: string; + meta?: string; + actions?: ReactNode; +}) { + return ( +
+
+

+ {title} +

+ {meta ? ( +

{meta}

+ ) : null} +
+ {actions ?
{actions}
: null} +
+ ); +} diff --git a/periscope/src/frontend/src/components/layout/sidebar.tsx b/periscope/src/frontend/src/components/layout/sidebar.tsx index 7da6423..b79891b 100644 --- a/periscope/src/frontend/src/components/layout/sidebar.tsx +++ b/periscope/src/frontend/src/components/layout/sidebar.tsx @@ -4,20 +4,15 @@ import Link from "next/link"; import { usePathname, useSearchParams } from "next/navigation"; import { Suspense, useEffect, useState, type ReactNode } from "react"; import { - ArrowLeft, - Boxes, - CircuitBoard, + BookOpen, ClipboardList, - LayoutDashboard, + Folder, Library, Loader2, MessageSquareWarning, - Ruler, - ScrollText, + ScanLine, Settings, Shield, - TableProperties, - Zap, } from "lucide-react"; import { PeriscopeMark } from "@/components/brand/periscope-mark"; import { FeedbackDialog } from "@/components/feedback/feedback-dialog"; @@ -72,15 +67,15 @@ export function Sidebar() { const isAdmin = user?.isAdmin ?? false; return ( -