From 399c37a327baa2e6dd109e2e5e287dcef0b35469 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 12 Jun 2026 16:15:55 +0000 Subject: [PATCH] =?UTF-8?q?feat(compliance):=20focused=20first-load=20?= =?UTF-8?q?=E2=80=94=20setup=20panel=20for=20empty=20orgs,=20two-block=20r?= =?UTF-8?q?egister?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit For an org with zero obligations the landing page rendered four stacked mostly-empty blocks: a 4-metric hero of zeros, two uniform quick-link cards, a 3-select filter bar and an empty table with pagination. The page now branches server-side on the org's obligation count: - No obligations yet: a single centered setup panel with one primary action (Browse frameworks). Fail-open — any resolution/query error renders the full register. - Populated org: hero + register only. The Compliance health and Manual attestations quick-link cards fold into the hero's actions row as quiet links, so the page is two blocks instead of four. Reuses PageHero actions, the health page's empty-state pattern and StartHereCard's primary CTA styling — no new primitives or tokens. Keeps the 'Obligations Register' heading and compliance-health-link testid asserted/used by e2e. --- app/app/compliance/page.tsx | 97 +++++++++++--------- components/compliance/CompliancePageHero.tsx | 19 +++- 2 files changed, 70 insertions(+), 46 deletions(-) diff --git a/app/app/compliance/page.tsx b/app/app/compliance/page.tsx index 99c569417..a11aa134a 100644 --- a/app/app/compliance/page.tsx +++ b/app/app/compliance/page.tsx @@ -1,58 +1,65 @@ import { Suspense } from 'react'; import Link from 'next/link'; -import { ClipboardCheck, ArrowRight, Activity } from 'lucide-react'; +import { Layers, ShieldCheck } from 'lucide-react'; import { ObligationsTable } from '@/components/compliance/ObligationsTable'; import { CompliancePageHero } from '@/components/compliance/CompliancePageHero'; import { SkeletonCard } from '@/components/ui/skeleton'; +import { getCurrentOrgId } from '@/lib/frameworks/org-frameworks'; +import { createSupabaseServerClient } from '@/lib/supabase/server'; + +/** + * First-load density gate: a brand-new org has no obligations, so the full + * register chrome (4-metric hero, quick-link cards, filter bar, empty table, + * pagination) renders as a wall of zero states. Only show it once at least + * one obligation exists. Fail open — any resolution/query error renders the + * full register, never a false "set up" state for a populated org. + */ +async function hasObligations(): Promise { + try { + const orgId = await getCurrentOrgId(); + const supabase = await createSupabaseServerClient(); + const { count, error } = await supabase + .from('org_tasks') + .select('id', { count: 'exact', head: true }) + .eq('organization_id', orgId); + if (error) return true; + return (count ?? 0) > 0; + } catch { + return true; + } +} + +export default async function ComplianceIndexPage() { + const populated = await hasObligations(); + + if (!populated) { + return ( +
+
+ +

+ Set up your obligations register +

+

+ Install a compliance framework and FormaOS generates the + obligations, owners and due dates it tracks here. +

+ + + Browse frameworks + +
+
+ ); + } -export default function ComplianceIndexPage() { return (
- {/* - * Audit Sprint 7a (2026-05-24): surface the manual-attestation - * workflow (Sprint 6c PR #173) from the compliance landing. - * Audit 2026-05-27 Tier 2.C: also surface the Health dashboard. - */} -
- -
- -
-

- Compliance health -

-

- Cross-framework score, status counts, top outstanding controls. -

-
-
- - - -
- -
-

- Manual attestations -

-

- Controls whose evaluator requires a human sign-off. -

-
-
- - -
-
}> diff --git a/components/compliance/CompliancePageHero.tsx b/components/compliance/CompliancePageHero.tsx index d229878a6..2940f014b 100644 --- a/components/compliance/CompliancePageHero.tsx +++ b/components/compliance/CompliancePageHero.tsx @@ -2,7 +2,7 @@ import Link from 'next/link'; import { useEffect } from 'react'; -import { Layers, ShieldCheck } from 'lucide-react'; +import { Activity, ClipboardCheck, Layers, ShieldCheck } from 'lucide-react'; import { useComplianceStore, useComplianceSummary, @@ -74,6 +74,23 @@ export function CompliancePageHero() { Frameworks + {/* Health + Attestations live here (not as standalone cards below) + so the landing page stays two blocks: hero + register. */} + + + Health + + + + Attestations +