Merge pull request #1089 from kaitranntt/kai/feat/design-system-references

feat(ui): bespoke health redesign + design-system docs (4 patterns)
This commit is contained in:
Kai (Tam Nhu) Tran authored and GitHub committed 2026-04-25 19:20:32 -04:00
commit 5b2729ba10
19 files changed
+1256 -359

No files matched your search

+39
View File
@@ -13,6 +13,45 @@ The 6 open questions from the brainstorm phase, resolved before implementation.
---
## v1.1 revision (2026-04-25) — identity-strip patterns
Phase 2 attempted to migrate `home` and `cliproxy` to a one-size-fits-all `PageShell + PageHeader` chrome. Both regressed on density:
- **home** had a single-row hero (logo + title + version + 4 inline stats) — splitting it into a stacked PageHeader + KpiRow doubled vertical footprint and lost scannability
- **cliproxy** had identity in the left rail — adding a top PageHeader duplicated branding and stole ~80px from the 3-pane body
**Resolution:** the design system is restructured around three identity-strip patterns extracted from the existing canonical references:
| # | Pattern | Reference | When |
|---|---------|-----------|------|
| 7a | `HeroBar` (1-row dense) | `pages/home.tsx` | Dashboard pages with ≤4 hero stats |
| 7b | Rail-anchored identity (no top chrome) | `pages/cliproxy.tsx` | Multi-entity Config pages where rail carries brand |
| 7c | `PageHeader` (current) | `pages/health.tsx` | Pages where description / status info is non-redundant |
Phase 2's home + cliproxy migrations are reverted. Health stays migrated (Monitor archetype + PageHeader works there). Future page migrations adapt to whichever pattern fits, NOT the other way around.
**Why bottom-up:** the existing references already proved their patterns work in production. The job of the design system is to formalize what works, not impose what should.
---
## v1.2 revision (2026-04-25) — health redesign and bespoke pattern
After the v1.1 restructure, the `health` page received a separate, focused redesign per a dedicated handoff brief (`plans/reports/handoff-260425-1417-health-page-redesign.md`). The redesign went bespoke:
- New domain components: `HealthStatusRibbon`, `HealthPriorityCard`, `HealthPriorityList`, `HealthAuditSection`
- Severity-driven layout: priority surfaces (errors/warnings) at the top with prominent fix affordances, audit accordion below
- Dynamic colored background tied to overall status, glassmorphic accents
- No `PageShell`, no `PageHeader`, no `MonitorLayout` — full custom shell
**Resolution:** add a fourth identity-strip pattern — **§1d Bespoke** — for pages whose content shape doesn't fit the three primary patterns. The v1.1 row's claim that "health stays migrated (Monitor archetype + PageHeader works there)" no longer holds; corrected here.
**Side effects:**
- `PageHeader` lost its canonical reference (was health). New reference TBD when another page adopts it.
- `MonitorLayout` lost its canonical reference. Primitives remain available for future Monitor pages.
- `Bespoke` is explicitly an escape hatch — not a default. Code review enforces "you tried the three patterns first."
---
## How to revisit
If a decision turns out wrong in practice, update this doc and bump the affected primitive — don't silently drift. Each row above should be appended with a "Revised: <date> · <reason>" line if changed.
+86
View File
@@ -0,0 +1,86 @@
<!doctype html><html lang="en"><head>
<meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>CCS Dashboard Design System — Phase 2 (final)</title>
<style>
:root{--bg:#0b0d12;--panel:#11151c;--line:#222a37;--ink:#e6edf6;--muted:#8a95a8;--dim:#5a6478;--accent:#7cc3ff;--good:#5ee2a0}
*{box-sizing:border-box}html,body{margin:0;background:var(--bg);color:var(--ink);font:14px/1.55 ui-sans-serif,system-ui,-apple-system,"Segoe UI",Roboto,sans-serif}
.wrap{max-width:1240px;margin:0 auto;padding:32px 24px 80px}
h1{font-size:28px;margin:0 0 6px}
h2{font-size:18px;margin:36px 0 12px;border-bottom:1px solid var(--line);padding-bottom:6px}
h3{font-size:14px;margin:18px 0 8px;color:var(--muted);text-transform:uppercase;letter-spacing:.06em}
p{color:#c8d2e0}
.lede{color:var(--muted);max-width:80ch}
.pill{display:inline-block;padding:2px 8px;border:1px solid var(--line);border-radius:999px;background:#1c2230;color:var(--muted);font-size:11px;margin-right:4px}
.pill.good{color:var(--good);border-color:#274d3a}
.pill.accent{color:var(--accent);border-color:#264866}
.compare{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin:12px 0}
.compare>div{display:flex;flex-direction:column;gap:6px}
.compare label{font-size:11px;color:var(--muted);text-transform:uppercase;letter-spacing:.06em}
.compare img{width:100%;border:1px solid var(--line);border-radius:8px}
@media (max-width:980px){.compare{grid-template-columns:1fr}}
table{width:100%;border-collapse:collapse;font-size:13px}
th,td{text-align:left;padding:8px 10px;border-bottom:1px solid var(--line);vertical-align:top}
th{color:var(--muted);font-weight:600;font-size:12px;text-transform:uppercase;letter-spacing:.06em}
.head{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:12px;margin-bottom:8px}
.callout{border-left:3px solid var(--accent);background:#0e1620;padding:10px 14px;border-radius:0 6px 6px 0;margin:10px 0}
</style></head><body>
<div class="wrap">
<header class="head">
<div>
<h1>CCS Dashboard Design System — Phase 2 (final)</h1>
<p class="lede">Phase 2 evolved through three stages: (1) initial migration of home/cliproxy/health to one-size-fits-all chrome — regressed; (2) revert + restructure docs around three identity-strip patterns extracted from canonical references; (3) bespoke health redesign per a dedicated brief, adding a fourth pattern for pages whose content shape demands custom hierarchy.</p>
</div>
<div>
<span class="pill accent">Phase 2 / 4</span>
<span class="pill good">Privacy mode ON</span>
</div>
</header>
<h2>1 · Final pages in this PR</h2>
<table>
<thead><tr><th>Page</th><th>State</th><th>Pattern</th></tr></thead>
<tbody>
<tr><td><code>home.tsx</code></td><td>Reverted to original (matches dev)</td><td>HeroBar (canonical)</td></tr>
<tr><td><code>cliproxy.tsx</code></td><td>Reverted to original (matches dev)</td><td>Rail-anchored identity (canonical)</td></tr>
<tr><td><code>health.tsx</code></td><td>Bespoke redesign on top of the original migration</td><td>Bespoke (canonical for §1d)</td></tr>
</tbody></table>
<h2>2 · health redesign — before / after</h2>
<p class="lede">The redesigned health page surfaces severity (HEALTH.ATTENTIONREQUIRED with priority cards) above the audit grid (HEALTH.ENVIRONMENTAUDIT). Status ribbon at top carries summary + version + ccs doctor + refresh. Dynamic background tints rose when issues exist, emerald when clean. New domain components: HealthStatusRibbon, HealthPriorityCard, HealthPriorityList, HealthAuditSection.</p>
<div class="compare">
<div><label>Before (dev branch baseline)</label><img src="data:image/png;base64,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 truncated
<div><label>After (final redesign)</label><img src="data:image/png;base64,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 truncated
</div>
<h2>3 · Four identity-strip patterns (v1.2 docs)</h2>
<table>
<thead><tr><th>Pattern</th><th>Reference</th><th>When</th></tr></thead>
<tbody>
<tr><td><b>HeroBar</b> (1-row dense)</td><td><code>pages/home.tsx</code></td><td>Dashboard pages with ≤4 hero stats packed tight</td></tr>
<tr><td><b>Rail-anchored</b> (no top chrome)</td><td><code>pages/cliproxy.tsx</code></td><td>Multi-entity Config pages where rail carries identity</td></tr>
<tr><td><b>PageHeader</b> (title + desc + actions)</td><td>None yet</td><td>Pages where description / status info is non-redundant context</td></tr>
<tr><td><b>Bespoke</b> (full custom)</td><td><code>pages/health.tsx</code></td><td>Severity / priority-driven pages with custom hierarchy</td></tr>
</tbody></table>
<h2>4 · What's actually in this PR</h2>
<table>
<thead><tr><th>File</th><th>Change</th></tr></thead>
<tbody>
<tr><td>ui/src/pages/health.tsx</td><td>Bespoke redesign — replaces the Monitor migration with priority + audit hierarchy</td></tr>
<tr><td>ui/src/components/health/health-status-ribbon.tsx</td><td>NEW — top status strip (severity, KPIs, version, actions)</td></tr>
<tr><td>ui/src/components/health/health-priority-card.tsx</td><td>NEW — clickable card surfacing a single failed/warning check</td></tr>
<tr><td>ui/src/components/health/health-priority-list.tsx</td><td>NEW — grid of priority cards under HEALTH.ATTENTIONREQUIRED</td></tr>
<tr><td>ui/src/components/health/health-audit-section.tsx</td><td>NEW — collapsible section for the lower audit grid</td></tr>
<tr><td>ui/src/components/layout/layout.tsx</td><td>Drop backdrop-blur on global header (interferes with health backdrop)</td></tr>
<tr><td>ui/src/pages/home.tsx</td><td>Reverted to original (matches dev)</td></tr>
<tr><td>ui/src/pages/cliproxy.tsx</td><td>Reverted to original (matches dev)</td></tr>
<tr><td>ui/src/components/monitor-layout/kpi.tsx</td><td>KpiCard gains optional onClick prop (still useful for future monitor pages)</td></tr>
<tr><td>ui/docs/design-system.md</td><td>v1.2 — four identity-strip patterns, Bespoke added as §1d</td></tr>
<tr><td>ui/docs/design-decisions.md</td><td>v1.2 revision row capturing the bespoke health redesign</td></tr>
<tr><td>ui/docs/health-redesign/</td><td>Verification report and screenshots from the redesign exercise</td></tr>
</tbody></table>
<p style="margin-top:32px;color:var(--dim);font-size:12px">
PR <a style="color:var(--accent)" href="https://github.com/kaitranntt/ccs/pull/1089">#1089</a> · plan <code>plans/260425-1132-ccs-dashboard-design-system/</code> · health brief <code>plans/reports/handoff-260425-1417-health-page-redesign.md</code>
</p>
</div></body></html>
+137 -124
View File
@@ -1,36 +1,116 @@
# CCS Dashboard Design System
The dashboard ships **two page archetypes**. Every page picks exactly one. All pages share the same outer chrome via `PageShell` + `PageHeader`.
A page-level design system extracted from the **canonical reference pages** — `home` and `cliproxy` — that already prove the patterns work in production. New pages should adapt to these references, not the other way around.
Some pages legitimately need a bespoke design (the redesigned `health` page is the current example) — when content shape demands custom hierarchy, the system should step out of the way rather than force the page into a wrong-fit archetype.
> Live preview in dev: `bun run dev` then visit `/_styleguide`.
---
## 1. The shell — every page
## 1. Identity-strip patterns (pick one per page)
Three patterns cover every page in the dashboard. The choice depends on what your page already has.
### 1a. `HeroBar` — single-row dense hero
**Canonical reference:** `pages/home.tsx`
```
PageShell
├─ PageHeader (title · description · status · actions)
└─ <archetype body>
┌────────────────────────────────────────────────────────────────────┐
│ [logo] Title [version] ┃ [Stat] [Stat] [Stat] [Stat] │
└────────────────────────────────────────────────────────────────────┘
```
```tsx
<PageShell>
<PageHeader title="Cliproxy" status={…} actions={…} />
<ConfigLayout … /> {/* OR <MonitorLayout> */}
</PageShell>
One row packs logo + title + version + ≤4 inline stats. Optional subtle dotted-pattern background. Stats are clickable when they double as navigation entry points.
**Use it when:**
- The page is a dashboard / monitor with a clear product identity
- ≤4 hero stats summarize the page in numbers
- Vertical real estate matters (this is half the height of a stacked PageHeader + KpiRow)
**Building blocks:**
- `<HeroSection version={…}/>` — logo + title + subtitle from `components/layout/hero-section.tsx`
- `<InlineStat title value icon variant onClick/>` — clickable stat tile (extracted from `home.tsx`); promote to a shared primitive when a 2nd page adopts it
### 1b. Rail-anchored identity — no top chrome
**Canonical reference:** `pages/cliproxy.tsx`
```
┌──────────┬─────────────────────────────────────────────────────────┐
│ ⚡ Brand │ │
│ subtitle │ │
│ [QSetup] │ full-height 3-pane body │
│ │ │
│ • prov A │ (form + raw json fill the entire viewport) │
│ • prov B │ │
│ … │ │
│ [status] │ │
└──────────┴─────────────────────────────────────────────────────────┘
```
Header rules:
- `title` is mandatory; `description` optional 1-line subtitle.
- `status` slot: small badges/chips (e.g. "Running · :8317").
- `actions` slot: button group; **primary action belongs in FormPane footer**, not here, for Config pages.
Page identity (brand + page-level CTA + status) lives **inside the left rail**. Zero top chrome — the body archetype gets the full vertical viewport.
**Use it when:**
- The page is a multi-entity Config (3-pane: list / form / json)
- The rail naturally carries the page name (you'd duplicate it in a top header)
- Vertical real estate is at a premium because the body has dense form content
**Building blocks:**
- The left rail's own header section (in-place markup, no extracted primitive yet — keep it bespoke until a 2nd page adopts the pattern)
- Recommended order in the rail: brand strip → primary CTA → entity list → status widget → footer summary
### 1c. `PageHeader` — title-row chrome
**Canonical reference:** none yet (was `health.tsx` until its bespoke redesign — see §1d).
```
┌────────────────────────────────────────────────────────────────────┐
│ Title [v-badge] [action] [action] │
│ Description / last-update / status info │
└────────────────────────────────────────────────────────────────────┘
```
Traditional title row with description and trailing actions.
**Use it when:**
- The page does NOT fit either canonical hero
- The description carries genuinely non-redundant context (last refresh, page hierarchy, filter state, version)
- Body archetype below benefits from a clear identity strip
**API:** `<PageHeader title description status actions />` — title + description on left, status badges + action buttons on right.
### 1d. Bespoke — full custom design
**Canonical reference:** `pages/health.tsx`
When a page's content shape demands its own hierarchy (priority-driven sections, dynamic backgrounds tied to status, custom card primitives like `HealthStatusRibbon` / `HealthPriorityCard`), the design system gets out of the way. Bespoke pages still respect global concerns (privacy mode, theme, sidebar) but build their own layout from scratch.
**Use it when:**
- None of the three patterns above fits without distorting the content
- The page's information hierarchy is genuinely unique (e.g. severity-driven priority surfaces with secondary audit lists)
- A bespoke implementation will be clearly better than forcing a fit
**Cost:** higher LOC, no reuse, no consistency — only justified when content demands it.
### Decision table
| Page shape | Identity strip |
|------------|---------------|
| Dashboard / overview with ≤4 hero stats | **HeroBar** (home pattern) |
| Multi-entity Config (3-pane: list/form/json) | **Rail-anchored** (cliproxy pattern, no top chrome) |
| Single-entity Config OR Monitor with a real hero viz | **PageHeader** + body archetype |
| Severity / priority-driven page with custom hierarchy | **Bespoke** (health pattern) |
| Wizard / login / dialog | None — bespoke shell |
---
## 2. Config archetype — 3-pane
## 2. Body archetypes
Used by every page that configures one or more entities (cliproxy, accounts, codex, copilot, cursor, droid, claude-extension, api, shared, profiles).
### 2a. Config — 3-pane
**Canonical reference:** `pages/cliproxy.tsx`
```
┌──────────┬──────────────────┬──────────┐
@@ -39,141 +119,74 @@ Used by every page that configures one or more entities (cliproxy, accounts, cod
└──────────┴──────────────────┴──────────┘
```
One component, prop-controlled left rail:
Left rail = `ListPane` (multi-entity) or `SectionRail` (single-entity, with `IntersectionObserver` scroll-spy). Form and JSON panes are middle and right respectively.
```tsx
// Multi-entity (cliproxy, accounts, providers)
<ConfigLayout
left={<ListPane items={…} selectedId={id} onSelect={…} />}
left={<ListPane …/>} // multi-entity
// OR
left={<SectionRail …/>} // single-entity
form={<FormPane>…</FormPane>}
json={<JsonPane data={…} />}
/>
// Single-entity (codex, copilot, cursor, droid, claude-extension)
<ConfigLayout
left={<SectionRail sections={…} />}
form={<FormPane>…</FormPane>}
json={<JsonPane data={…} />}
/>
// No rail (rare — only if page has neither entities to pick nor sections to navigate)
<ConfigLayout form={<FormPane>…</FormPane>} json={<JsonPane data={…} />} />
```
### Rules
**Rules:**
- Save action lives **only** in `FormPane` footer
- `<1024px`: collapses to tabs (Browse | Configure | JSON)
- `JsonPane` is read-only by default; opt-in `editable` for cliproxy-style inline editing
- **Layout**: left 260px / form flex / json 360px on `>=1024px`.
- **<1024px**: collapses to tabs (`Browse | Configure | JSON`).
- **Save action**: lives **only** in `FormPane footer`. Never duplicate in PageHeader.
- **Per-entity actions** (delete, duplicate, sync): ListPane row trailing actions or FormPane header secondary actions — never both.
- **JsonPane is read-only by default**. Pass `editable` prop only on pages that need inline editing (cliproxy is the canonical example).
### 2b. Monitor — KPI row + 12-col grid
### SectionRail
For single-entity pages. Provides anchor nav with `IntersectionObserver` scroll-spy. Each item must match the `id` of a `<FormSection id="…">` in the FormPane.
```tsx
<SectionRail
sections={[
{ id: 'general', label: 'General' },
{ id: 'auth', label: 'Authentication' },
{ id: 'routing', label: 'Routing' },
]}
/>
// FormPane:
<FormSection id="general" title="General">…</FormSection>
<FormSection id="auth" title="Authentication">…</FormSection>
```
---
## 3. Monitor archetype — KPI row + 12-col grid
Used by every page that shows live state (home, analytics, health, logs, future status pages).
**Canonical reference:** none in this PR. Health used to be the reference but went bespoke (§1d). The primitives (`MonitorLayout`, `KpiRow`, `KpiCard`, `MonitorGrid`, `MonitorCard`) ship and remain available; first page to genuinely need them becomes the next reference.
```
┌────────────────────────────────────────────────┐
│ KpiRow: [KPI] [KPI] [KPI] [KPI] │ (optional, ≤4 hero numbers)
├────────────────────────────────────────────────┤
│ MonitorGrid (12-col): │
│ <MonitorCard span={8}> primary viz │
│ <MonitorCard span={4}> side widget │
│ <MonitorCard span={6}> ... │
│ <MonitorCard span={6}> ... │
└────────────────────────────────────────────────┘
┌────────────────────────────────────────┐
│ KpiRow (≤4 hero numbers) │
├────────────────────────────────────────┤
│ MonitorGrid (12-col): │
│ <MonitorCard span={…}/> │
└────────────────────────────────────────┘
```
```tsx
<MonitorLayout
kpis={
<KpiRow>
<KpiCard label="Active accounts" value="87" hint="▲ 3" tone="positive" />
…
</KpiRow>
}
>
<MonitorLayout kpis={<KpiRow>…</KpiRow>}>
<MonitorGrid>
<MonitorCard span={8} title="Live monitor">…</MonitorCard>
<MonitorCard span={4} title="Top providers">…</MonitorCard>
<MonitorCard span={6} title="Requests" meta="last 24h">…</MonitorCard>
<MonitorCard span={6} variant="terminal" title="$ ccs health --watch">…</MonitorCard>
<MonitorCard span={6} variant="terminal" title=…>…</MonitorCard>
</MonitorGrid>
</MonitorLayout>
```
### Rules
**Rules:**
- `KpiRow` only when ≤4 hero numbers; more → group inside the grid
- One primary viz per page, span ≥8 cols
- `variant="terminal"` for live-log / `health --watch` aesthetics
- **KpiRow** only when there are ≤4 hero numbers. More than 4 → use `MonitorCard`s inside the grid instead.
- **One primary viz per page** — span ≥8 cols. Preserves the "punch" of home and analytics.
- **`variant="terminal"`** = dark monospace card, for live-log / `health --watch` aesthetics. Opt-in only.
- **Spans clamp at 6 cols on tablet**, single column on mobile.
---
## 3. Composing a new page
```tsx
// Example: a new dashboard-style page
<PageShell>
<HeroBar … /> {/* or PageHeader, or rail-anchored identity */}
<MonitorLayout … /> {/* or ConfigLayout */}
</PageShell>
```
Target LOC for a new page: **~80** for typical config, **~120** for monitor with hero strip. Target LOC for an outlier rewrite: **<400**.
---
## 4. When NOT to use either archetype
These remain bespoke:
- `/login` — minimal centered shell, no header/grid.
- Setup wizard — modal overlay, not a page.
- Dialogs — radix `Dialog`, not a layout.
If you find yourself fighting the shell, the answer is usually "this isn't a Config or Monitor page" — talk to the maintainers before inventing a third archetype.
These remain bespoke and are out of scope:
- `/login` — minimal centered shell
- Setup wizard — modal overlay
- Dialogs — Radix `Dialog`
---
## 5. Composing a new page
## 5. Decisions
The recipe for any new page is:
```tsx
import { PageShell, PageHeader } from '@/components/page-shell';
import { ConfigLayout, /*…*/ } from '@/components/config-layout';
export function MyPage() {
return (
<PageShell>
<PageHeader title="My Page" actions={<Actions/>} />
<ConfigLayout
left={…}
form={<FormPane>…</FormPane>}
json={<JsonPane data={…}/>}
/>
</PageShell>
);
}
```
Target LOC for a new page: **~80** for typical config, **~120** for monitor. If your page exceeds 400 LOC it should be split into `pages/<name>/` with section files.
---
## 6. Lint / enforcement
Phase 4 adds a lint rule: every file under `src/pages/*.tsx` (or `src/pages/*/index.tsx`) must import `PageShell`. Until then, code review enforces the contract.
---
## 7. Decisions
See [`design-decisions.md`](./design-decisions.md) for the resolutions of the 6 open questions from the brainstorm phase.
See [`design-decisions.md`](./design-decisions.md) for the resolved open questions and the v1.1 revision rationale.
Binary file not shown.

After

Width:  |  Height:  |  Size: 422 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 378 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 479 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 481 KiB

+129
View File
@@ -0,0 +1,129 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>UI Verification Report - Health Page Redesign</title>
<script src="https://cdn.tailwindcss.com"></script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&display=swap" rel="stylesheet">
<style>
body { font-family: 'Inter', sans-serif; background-color: #f8f9fa; }
.glass { background: rgba(255, 255, 255, 0.7); backdrop-filter: blur(10px); border: 1px solid rgba(255, 255, 255, 0.3); }
.screenshot-container { position: relative; border-radius: 12px; overflow: hidden; box-shadow: 0 10px 30px rgba(0,0,0,0.1); transition: transform 0.3s ease; }
.screenshot-container:hover { transform: scale(1.02); }
.status-badge { padding: 4px 12px; border-radius: 99px; font-size: 0.75rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.05em; }
.status-pass { background-color: #dcfce7; color: #166534; }
.status-warning { background-color: #fef9c3; color: #854d0e; }
</style>
</head>
<body class="p-8 md:p-16">
<div class="max-w-5xl mx-auto">
<!-- Header -->
<header class="mb-12 flex justify-between items-end">
<div>
<h1 class="text-4xl font-bold text-slate-900 tracking-tight mb-2">UI Verification Report</h1>
<p class="text-slate-500 font-medium">Project: CCS CLI Health Dashboard Redesign</p>
</div>
<div class="text-right">
<span class="status-badge status-pass">Pass</span>
<p class="text-xs text-slate-400 mt-2">Generated: April 25, 2026</p>
</div>
</header>
<!-- Summary -->
<section class="glass rounded-2xl p-8 mb-12 shadow-sm">
<h2 class="text-xl font-semibold mb-4 flex items-center">
<svg class="w-5 h-5 mr-2 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
Implementation Overview
</h2>
<div class="grid md:grid-cols-2 gap-8">
<div>
<h3 class="text-sm font-bold text-slate-400 uppercase tracking-widest mb-2">Aesthetic Improvements</h3>
<ul class="space-y-2 text-slate-700">
<li class="flex items-start"><span class="text-emerald-500 mr-2">✓</span> Solid opaque header with shadow separation</li>
<li class="flex items-start"><span class="text-emerald-500 mr-2">✓</span> Contained mesh gradient (no top-bar bleed)</li>
<li class="flex items-start"><span class="text-emerald-500 mr-2">✓</span> Premium grainy texture via local SVG asset</li>
<li class="flex items-start"><span class="text-emerald-500 mr-2">✓</span> Refined typography and spacing (OKLCH colors)</li>
</ul>
</div>
<div>
<h3 class="text-sm font-bold text-slate-400 uppercase tracking-widest mb-2">Functional Verification</h3>
<ul class="space-y-2 text-slate-700">
<li class="flex items-start"><span class="text-emerald-500 mr-2">✓</span> Priority cards collapsed by default (Fix for 'blank space' bug)</li>
<li class="flex items-start"><span class="text-emerald-500 mr-2">✓</span> Entire card header now clickable for expansion</li>
<li class="flex items-start"><span class="text-emerald-500 mr-2">✓</span> Refresh button loading state confirmed</li>
<li class="flex items-start"><span class="text-emerald-500 mr-2">✓</span> "Apply Fix" spinner feedback</li>
</ul>
</div>
</div>
</section>
<!-- Redteam Audit -->
<section class="mb-12">
<h2 class="text-xl font-semibold mb-6">Redteam Review & Edge Cases</h2>
<div class="space-y-4">
<div class="bg-white border border-slate-200 rounded-xl p-6 shadow-sm">
<h3 class="font-semibold text-slate-900 mb-2">Edge Case: Asset Availability</h3>
<p class="text-slate-600 text-sm italic mb-3">Problem: External SVG noise textures were failing (404).</p>
<p class="text-slate-700 text-sm">Resolution: Switched to a robust <strong>Base64 Data URI</strong> for the noise texture in <code>health.tsx</code>. This ensures the premium texture renders even in offline or high-security environments.</p>
</div>
<div class="bg-white border border-slate-200 rounded-xl p-6 shadow-sm">
<h3 class="font-semibold text-slate-900 mb-2">Edge Case: Responsive Overlap</h3>
<p class="text-slate-600 text-sm italic mb-3">Problem: Mesh gradient blur spilling into the header and sidebar.</p>
<p class="text-slate-700 text-sm">Resolution: Implemented <code>overflow-hidden</code> on the content wrapper and adjusted mesh circles to <code>top-[5%]</code> instead of negative offsets. Added <code>z-20</code> and solid background to the header for absolute containment.</p>
</div>
</div>
</section>
<!-- Screenshots -->
<section class="mb-12">
<h2 class="text-xl font-semibold mb-8">Visual Proof</h2>
<div class="space-y-12">
<div>
<h3 class="text-lg font-medium mb-4 text-slate-800">1. Initial State & Header Separation</h3>
<div class="screenshot-container">
<img src="initial_state.png" alt="Initial State" class="w-full">
</div>
<p class="text-sm text-slate-500 mt-4 italic text-center">Note the clean separation between the white header and the mesh background.</p>
</div>
<div>
<h3 class="text-lg font-medium mb-4 text-slate-800">2. Interactive Loading Feedback</h3>
<div class="screenshot-container">
<img src="loading_state.png" alt="Loading State" class="w-full">
</div>
<p class="text-sm text-slate-500 mt-4 italic text-center">Spinner state triggered during 'Refresh' action.</p>
</div>
<div>
<h3 class="text-lg font-medium mb-4 text-slate-800">3. Expanded Issue Drill-down</h3>
<div class="screenshot-container">
<img src="expanded_view.png" alt="Expanded Issue" class="w-full">
</div>
<p class="text-sm text-slate-500 mt-4 italic text-center">Detailed issue view with clear hierarchy and typography.</p>
</div>
<div>
<h3 class="text-lg font-medium mb-4 text-slate-800">4. Expansion Fix (Header Clickable)</h3>
<div class="screenshot-container">
<img src="fixed_expansion.png" alt="Expansion Fix" class="w-full">
</div>
<p class="text-sm text-slate-500 mt-4 italic text-center">Corrected expansion logic: Cards start collapsed, and expanding them shows the fix without 'blank space'.</p>
</div>
</div>
</section>
<!-- Final Verdict -->
<footer class="mt-20 pt-12 border-t border-slate-200 text-center">
<div class="inline-flex items-center justify-center p-4 bg-emerald-50 rounded-2xl border border-emerald-100 mb-6">
<span class="w-3 h-3 bg-emerald-500 rounded-full animate-pulse mr-3"></span>
<span class="font-bold text-emerald-800">READY FOR PRODUCTION INTEGRATION</span>
</div>
<p class="text-slate-400 text-xs uppercase tracking-widest font-semibold">Verified by Antigravity AI Assistant</p>
</footer>
</div>
</body>
</html>
Binary file not shown.

After

Width:  |  Height:  |  Size: 134 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 177 KiB

@@ -0,0 +1,93 @@
import { type HealthGroup } from '@/hooks/use-health';
import { HealthCheckItem } from './health-check-item';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
import { ChevronRight, ShieldCheck, Info } from 'lucide-react';
import { useState } from 'react';
import { cn } from '@/lib/utils';
import { useTranslation } from 'react-i18next';
interface HealthAuditSectionProps {
groups: HealthGroup[];
}
export function HealthAuditSection({ groups }: HealthAuditSectionProps) {
const { t } = useTranslation();
return (
<div className="space-y-6">
<div className="flex items-center gap-3 px-1">
<h2 className="text-sm font-bold tracking-widest uppercase text-muted-foreground/60">
{t('health.environmentAudit')}
</h2>
<div className="h-px flex-1 bg-border/40" />
<ShieldCheck className="w-4 h-4 text-emerald-500/60" />
</div>
<div className="space-y-4">
{groups.map((group) => (
<AuditGroup key={group.id} group={group} />
))}
</div>
</div>
);
}
function AuditGroup({ group }: { group: HealthGroup }) {
const [isOpen, setIsOpen] = useState(false);
const issuesCount = group.checks.filter(
(c) => c.status === 'error' || c.status === 'warning'
).length;
const hasIssues = issuesCount > 0;
return (
<Collapsible open={isOpen} onOpenChange={setIsOpen} className="group/audit">
<div
className={cn(
'rounded-2xl border transition-all duration-300',
isOpen ? 'bg-muted/30 border-border/60' : 'bg-background hover:border-border/60'
)}
>
<CollapsibleTrigger asChild>
<button className="w-full flex items-center gap-4 px-5 py-4 text-left">
<div
className={cn(
'flex items-center justify-center w-8 h-8 rounded-full transition-colors',
hasIssues ? 'bg-rose-500/10 text-rose-500' : 'bg-muted text-muted-foreground'
)}
>
{hasIssues ? <Info className="w-4 h-4" /> : <ShieldCheck className="w-4 h-4" />}
</div>
<div className="flex-1">
<h3 className="text-sm font-bold tracking-tight">{group.name}</h3>
<p className="text-[10px] text-muted-foreground uppercase tracking-widest font-mono">
{group.checks.length} Checks
</p>
</div>
{hasIssues && (
<div className="px-2 py-0.5 rounded-full bg-rose-500/10 text-rose-500 text-[10px] font-bold font-mono">
{issuesCount}
</div>
)}
<ChevronRight
className={cn(
'w-4 h-4 text-muted-foreground transition-transform duration-300',
isOpen && 'rotate-90'
)}
/>
</button>
</CollapsibleTrigger>
<CollapsibleContent>
<div className="px-5 pb-5 space-y-1">
{group.checks.map((check) => (
<HealthCheckItem key={check.id} check={check} />
))}
</div>
</CollapsibleContent>
</div>
</Collapsible>
);
}
@@ -0,0 +1,179 @@
import {
AlertCircle,
AlertTriangle,
Copy,
Terminal,
Wrench,
ChevronRight,
RefreshCw,
} from 'lucide-react';
import { Button } from '@/components/ui/button';
import { motion, AnimatePresence } from 'framer-motion';
import { useFixHealth, type HealthCheck } from '@/hooks/use-health';
import { cn } from '@/lib/utils';
import { toast } from 'sonner';
import { useTranslation } from 'react-i18next';
import { useState } from 'react';
interface HealthPriorityCardProps {
check: HealthCheck;
}
export function HealthPriorityCard({ check }: HealthPriorityCardProps) {
const { t } = useTranslation();
const fixMutation = useFixHealth();
const hasContent = !!(check.details || check.fix || check.fixable);
const [isExpanded, setIsExpanded] = useState(false);
const isError = check.status === 'error';
const Icon = isError ? AlertCircle : AlertTriangle;
const copyFix = () => {
if (check.fix) {
navigator.clipboard.writeText(check.fix);
toast.success(t('health.copied'));
}
};
const headerInner = (
<>
{/* Status Icon with Ring */}
<div
className={cn(
'flex items-center justify-center w-12 h-12 rounded-2xl shrink-0',
isError ? 'bg-rose-500/10 text-rose-500' : 'bg-amber-500/10 text-amber-500'
)}
>
<Icon className="w-6 h-6" />
</div>
<div className="flex-1 min-w-0 space-y-1">
<div className="flex items-center justify-between">
<h3 className="text-lg font-bold tracking-tight">{check.name}</h3>
{hasContent && (
<div className="p-1 hover:bg-muted rounded-full transition-colors">
<ChevronRight
className={cn(
'w-5 h-5 text-muted-foreground transition-transform',
isExpanded && 'rotate-90'
)}
/>
</div>
)}
</div>
<p className="text-sm font-medium leading-relaxed text-muted-foreground/80">
{check.message}
</p>
</div>
</>
);
return (
<motion.div
layout
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, scale: 0.95 }}
className={cn(
'group relative overflow-hidden rounded-[2rem] p-px transition-all duration-300',
isError
? 'bg-gradient-to-br from-rose-500/20 via-rose-500/5 to-transparent'
: 'bg-gradient-to-br from-amber-500/20 via-amber-500/5 to-transparent'
)}
>
<div className="relative h-full rounded-[calc(2rem-1px)] bg-background/80 backdrop-blur-md overflow-hidden">
{/* Double-Bezel Inner Highlight */}
<div className="absolute inset-0 rounded-[calc(2rem-1px)] shadow-[inset_0_1px_1px_rgba(255,255,255,0.1)] pointer-events-none" />
<div className="p-6">
{/*
* Header acts as the expand/collapse trigger when there's
* additional content (details, fix, fixable). Render as <button>
* so keyboard and screen-reader users can toggle it; aria-expanded
* reflects state. Without content, render a plain <div>.
*/}
{hasContent ? (
<button
type="button"
onClick={() => setIsExpanded(!isExpanded)}
aria-expanded={isExpanded}
aria-label={
isExpanded ? `Collapse ${check.name} details` : `Expand ${check.name} details`
}
className="flex w-full select-none items-start gap-4 rounded-2xl text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
>
{headerInner}
</button>
) : (
<div className="flex w-full items-start gap-4">{headerInner}</div>
)}
<AnimatePresence>
{isExpanded && (
<motion.div
initial={{ height: 0, opacity: 0 }}
animate={{ height: 'auto', opacity: 1 }}
exit={{ height: 0, opacity: 0 }}
transition={{ duration: 0.3, ease: [0.32, 0.72, 0, 1] }}
className="overflow-hidden"
>
<div className="mt-4 pt-4 border-t border-border/40 space-y-4">
{check.details && (
<div className="rounded-xl bg-muted/30 p-3 font-mono text-[11px] leading-relaxed text-muted-foreground break-all">
{check.details}
</div>
)}
{(check.fix || check.fixable) && (
<div className="flex flex-col sm:flex-row items-stretch sm:items-center gap-3">
{check.fix && (
<div className="flex-1 flex items-center gap-2 h-10 px-3 rounded-full bg-background/50 border border-border/40">
<Terminal className="w-3.5 h-3.5 text-muted-foreground" />
<code className="text-xs font-mono flex-1 truncate">{check.fix}</code>
{/*
* Always-visible copy button. The previous design hid it behind
* group-hover, which excluded keyboard users and touch-only
* devices (no hover state on mobile = effectively unreachable).
*/}
<Button
variant="ghost"
size="icon"
onClick={copyFix}
aria-label={t('health.copy') ?? 'Copy fix command'}
className="h-6 w-6 rounded-full text-muted-foreground hover:text-foreground"
>
<Copy className="w-3 h-3" />
</Button>
</div>
)}
{check.fixable && (
<Button
onClick={() => fixMutation.mutate(check.id)}
disabled={fixMutation.isPending}
className={cn(
'h-10 px-6 rounded-full font-bold shadow-lg shadow-primary/20 transition-all active:scale-95',
isError
? 'bg-rose-500 hover:bg-rose-600'
: 'bg-amber-500 hover:bg-amber-600'
)}
>
{fixMutation.isPending ? (
<RefreshCw className="w-4 h-4 mr-2 animate-spin" />
) : (
<Wrench className="w-4 h-4 mr-2" />
)}
{t('health.applyFix')}
</Button>
)}
</div>
)}
</div>
</motion.div>
)}
</AnimatePresence>
</div>
</div>
</motion.div>
);
}
@@ -0,0 +1,34 @@
import { HealthPriorityCard } from './health-priority-card';
import { type HealthCheck } from '@/hooks/use-health';
import { AnimatePresence } from 'framer-motion';
import { useTranslation } from 'react-i18next';
interface HealthPriorityListProps {
checks: HealthCheck[];
}
export function HealthPriorityList({ checks }: HealthPriorityListProps) {
const { t } = useTranslation();
if (checks.length === 0) return null;
return (
<div className="space-y-6">
<div className="flex items-center gap-3 px-1">
<h2 className="text-sm font-bold tracking-widest uppercase text-muted-foreground/60">
{t('health.attentionRequired')}
</h2>
<div className="h-px flex-1 bg-border/40" />
<span className="font-mono text-xs text-rose-500 font-bold">{checks.length}</span>
</div>
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<AnimatePresence mode="popLayout">
{checks.map((check) => (
<HealthPriorityCard key={check.id} check={check} />
))}
</AnimatePresence>
</div>
</div>
);
}
@@ -0,0 +1,179 @@
import { useEffect, useState } from 'react';
import { Cpu, RefreshCw, Terminal, Copy } from 'lucide-react';
import { Button } from '@/components/ui/button';
import { cn } from '@/lib/utils';
import { useTranslation } from 'react-i18next';
import { toast } from 'sonner';
interface HealthStatusRibbonProps {
summary: {
passed: number;
warnings: number;
errors: number;
total: number;
info: number;
};
version: string;
lastScan: number;
isLoading: boolean;
onRefresh: () => void;
}
function formatRelativeTime(
timestamp: number,
t: (key: string, options?: Record<string, unknown>) => string
) {
const seconds = Math.floor((Date.now() - timestamp) / 1000);
if (seconds < 5) return t('health.justNow');
if (seconds < 60) return t('health.secondsAgo', { count: seconds });
const minutes = Math.floor(seconds / 60);
if (minutes < 60) return t('health.minutesAgo', { count: minutes });
// Roll up to hours so a stale "120 minutes ago" reads as "2 hours ago"
const hours = Math.floor(minutes / 60);
return t('health.hoursAgo', { count: hours });
}
export function HealthStatusRibbon({
summary,
version,
lastScan,
isLoading,
onRefresh,
}: HealthStatusRibbonProps) {
const { t } = useTranslation();
// formatRelativeTime reads Date.now() during render. Without a ticking
// re-render, the "last scan" label freezes at whatever it showed on mount
// and never advances to "1 minute ago", "2 hours ago", etc. — even though
// the underlying lastScan timestamp is stale. Force a re-render every
// second; the cost is negligible (one tiny component) and the UX win is
// a label that actually behaves like a relative timestamp.
const [, setTick] = useState(0);
useEffect(() => {
const interval = setInterval(() => setTick((n) => n + 1), 1000);
return () => clearInterval(interval);
}, []);
const copyDoctorCommand = () => {
navigator.clipboard.writeText('ccs doctor');
toast.success(t('health.copied'));
};
const hasIssues = summary.errors > 0 || summary.warnings > 0;
return (
<div
className={cn(
'group relative overflow-hidden rounded-2xl border transition-all duration-500',
hasIssues
? 'bg-rose-500/5 border-rose-500/20 shadow-[0_0_20px_-12px_rgba(244,63,94,0.3)]'
: 'bg-emerald-500/5 border-emerald-500/20 shadow-[0_0_20px_-12px_rgba(16,185,129,0.3)]'
)}
>
{/* Animated Mesh Gradient Background */}
<div
className={cn(
'absolute inset-0 opacity-[0.05] blur-3xl pointer-events-none transition-colors duration-1000',
hasIssues ? 'bg-rose-500' : 'bg-emerald-500'
)}
/>
<div className="relative flex flex-col md:flex-row items-center gap-4 px-6 py-4">
{/* Status Indicator */}
<div className="flex items-center gap-3 shrink-0">
<div className="relative">
<div
className={cn(
'w-2.5 h-2.5 rounded-full',
hasIssues ? 'bg-rose-500' : 'bg-emerald-500'
)}
/>
<div
className={cn(
'absolute inset-0 rounded-full animate-ping opacity-40',
hasIssues ? 'bg-rose-500' : 'bg-emerald-500'
)}
/>
</div>
<h1 className="font-mono text-sm font-bold tracking-tight uppercase">
{hasIssues ? t('health.issuesDetected') : t('health.systemOptimal')}
</h1>
</div>
<div className="hidden md:block h-4 w-px bg-border/50" />
{/* Stats Summary */}
<div className="flex items-center gap-6 text-xs font-mono">
<div className="flex flex-col">
<span className="text-muted-foreground uppercase text-[10px] tracking-widest">
Checks
</span>
<span>{summary.total}</span>
</div>
<div className="flex flex-col">
<span className="text-muted-foreground uppercase text-[10px] tracking-widest">
Passed
</span>
<span className="text-emerald-500">{summary.passed}</span>
</div>
{summary.warnings > 0 && (
<div className="flex flex-col">
<span className="text-muted-foreground uppercase text-[10px] tracking-widest">
Warnings
</span>
<span className="text-amber-500 font-bold">{summary.warnings}</span>
</div>
)}
{summary.errors > 0 && (
<div className="flex flex-col">
<span className="text-muted-foreground uppercase text-[10px] tracking-widest">
Errors
</span>
<span className="text-rose-500 font-bold underline decoration-rose-500/30">
{summary.errors}
</span>
</div>
)}
</div>
<div className="flex-1" />
{/* Meta & Actions */}
<div className="flex items-center gap-4">
<div className="flex flex-col items-end text-[10px] font-mono text-muted-foreground">
<div className="flex items-center gap-1.5">
<RefreshCw className={cn('w-2.5 h-2.5', isLoading && 'animate-spin')} />
<span>{lastScan ? formatRelativeTime(lastScan, t) : '--'}</span>
</div>
<div className="flex items-center gap-1.5 opacity-60">
<Cpu className="w-2.5 h-2.5" />
<span>{version}</span>
</div>
</div>
<div className="flex items-center gap-2">
<Button
variant="ghost"
size="sm"
onClick={copyDoctorCommand}
className="h-8 px-3 gap-2 font-mono text-[10px] bg-background/40 hover:bg-background/60 border border-border/40 rounded-full"
>
<Terminal className="w-3 h-3" />
<span className="hidden sm:inline">ccs doctor</span>
<Copy className="w-3 h-3 opacity-40" />
</Button>
<Button
variant="ghost"
size="icon"
onClick={onRefresh}
disabled={isLoading}
className="h-8 w-8 rounded-full bg-background/40 hover:bg-background/60 border border-border/40"
>
<RefreshCw className={cn('w-3.5 h-3.5', isLoading && 'animate-spin')} />
</Button>
</div>
</div>
</div>
</div>
);
}
+3
View File
@@ -7,3 +7,6 @@ export { HealthCheckItem } from './health-check-item';
export { HealthGauge } from './health-gauge';
export { HealthGroupSection } from './health-group-section';
export { HealthStatsBar } from './health-stats-bar';
export { HealthStatusRibbon } from './health-status-ribbon';
export { HealthPriorityList } from './health-priority-list';
export { HealthAuditSection } from './health-audit-section';
+1 -1
View File
@@ -41,7 +41,7 @@ export function Layout() {
<SidebarProvider>
<AppSidebar />
<main className="flex-1 flex flex-col min-h-0 overflow-hidden bg-background">
<header className="flex h-14 items-center justify-between px-6 border-b shrink-0 bg-background/95 backdrop-blur supports-[backdrop-filter]:bg-background/60">
<header className="flex h-14 items-center justify-between px-6 border-b shrink-0 bg-background shadow-sm z-20">
<div className="flex items-center gap-3">
<ClaudeKitBadge />
<SponsorButton />
+45 -4
View File
@@ -27,6 +27,14 @@ interface KpiCardProps {
/** Tone for the hint line (e.g. positive delta, warning, etc.). */
tone?: 'default' | 'positive' | 'warning' | 'negative';
icon?: ReactNode;
/**
* Optional click handler. When set, the tile renders as a button with
* keyboard + hover affordances — used by pages where each KPI is also a
* navigation entry point (e.g. home dashboard).
*/
onClick?: () => void;
/** Accessible label override when onClick is set. Defaults to `label`. */
ariaLabel?: string;
className?: string;
}
@@ -39,10 +47,22 @@ const TONE_CLASSES: Record<NonNullable<KpiCardProps['tone']>, string> = {
/**
* KpiCard - Single hero stat tile inside a KpiRow.
*
* Renders as a static <div> by default; promotes to a clickable <button>
* when `onClick` is supplied so the tile gains keyboard focus + hover state.
*/
export function KpiCard({ label, value, hint, tone = 'default', icon, className }: KpiCardProps) {
return (
<div className={cn('rounded-xl border bg-card p-4 shadow-sm', className)}>
export function KpiCard({
label,
value,
hint,
tone = 'default',
icon,
onClick,
ariaLabel,
className,
}: KpiCardProps) {
const content = (
<>
<div className="flex items-center justify-between">
<p className="text-xs font-medium uppercase tracking-wider text-muted-foreground">
{label}
@@ -51,6 +71,27 @@ export function KpiCard({ label, value, hint, tone = 'default', icon, className
</div>
<p className="mt-2 text-2xl font-semibold tracking-tight tabular-nums">{value}</p>
{hint && <p className={cn('mt-1 text-xs', TONE_CLASSES[tone])}>{hint}</p>}
</div>
</>
);
const baseClasses = 'rounded-xl border bg-card p-4 shadow-sm';
if (onClick) {
return (
<button
type="button"
onClick={onClick}
aria-label={ariaLabel ?? (typeof label === 'string' ? label : undefined)}
className={cn(
baseClasses,
'text-left transition-all hover:bg-card/80 hover:shadow-md hover:-translate-y-0.5 active:scale-[0.99] focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring',
className
)}
>
{content}
</button>
);
}
return <div className={cn(baseClasses, className)}>{content}</div>;
}
+233 -6
View File
@@ -8,11 +8,26 @@
* safe to publish in PRs without enabling Privacy mode.
*/
import { useState } from 'react';
import { Activity, Bot, Cloud, Cpu, Plus, RefreshCcw, ShieldCheck, Zap } from 'lucide-react';
import {
Activity,
Bot,
Cloud,
Cpu,
Key,
Plus,
RefreshCcw,
RefreshCw,
ShieldCheck,
Sparkles,
Users,
Zap,
} from 'lucide-react';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { ScrollArea } from '@/components/ui/scroll-area';
import { cn } from '@/lib/utils';
import { PageShell, PageHeader, EmptyState, ErrorState } from '@/components/page-shell';
import {
ConfigLayout,
@@ -60,7 +75,25 @@ export function StyleguidePage() {
return (
<div className="space-y-12 bg-muted/20 px-4 py-8 sm:px-8">
<Intro />
<PrimitiveSection title="1. PageShell + PageHeader" anchor="page-shell">
<PrimitiveSection
title="1a. HeroBar — single-row dense identity strip (home pattern)"
anchor="hero-bar"
>
<DemoHeroBar />
</PrimitiveSection>
<PrimitiveSection
title="1b. Rail-anchored identity — no top chrome (cliproxy pattern)"
anchor="rail-anchored"
>
<DemoRailAnchored />
</PrimitiveSection>
<PrimitiveSection
title="1c. PageHeader — title + description + actions (health pattern)"
anchor="page-header"
>
<DemoPageHeader />
</PrimitiveSection>
@@ -121,9 +154,15 @@ function Intro() {
</Badge>
<h1 className="text-3xl font-bold tracking-tight">CCS Dashboard Design System</h1>
<p className="text-muted-foreground">
Two archetypes — <strong>Config</strong> (3-pane: list | form | JSON) and{' '}
<strong>Monitor</strong> (KPI row + grid) — wrapped by <code>PageShell</code>. Every page
picks one archetype.
Three identity-strip patterns — <strong>HeroBar</strong> (one-row dense, see <code>/</code>{' '}
home page), <strong>rail-anchored</strong> (no top chrome, see <code>/cliproxy</code>), and{' '}
<strong>PageHeader</strong> (title + description + actions, see <code>/health</code>) — and
two body archetypes: <strong>Config</strong> (3-pane: list / form / JSON) and{' '}
<strong>Monitor</strong> (KPI row + grid). Pick the identity strip that matches your
page&apos;s shape; pick the body archetype your content needs.
</p>
<p className="text-xs text-muted-foreground">
See <code>ui/docs/design-system.md</code> for the decision table.
</p>
</header>
);
@@ -147,7 +186,195 @@ function PrimitiveSection({
}
// -----------------------------------------------------------------------------
// PageHeader demo
// HeroBar demo (canonical: pages/home.tsx)
//
// One row packs logo + title + version + ≤4 inline stats. Optional dotted
// background pattern. Stats are clickable when they double as nav entry points.
// Use it for dashboard pages with a clear product identity and ≤4 hero numbers.
// -----------------------------------------------------------------------------
function DemoInlineStat({
title,
value,
icon: Icon,
variant = 'default',
}: {
title: string;
value: string | number;
icon: typeof Activity;
variant?: 'default' | 'accent' | 'success' | 'warning';
}) {
const styles = {
default: 'bg-muted text-muted-foreground',
accent: 'bg-accent/15 text-accent',
success: 'bg-emerald-600/15 text-emerald-700 dark:text-emerald-400',
warning: 'bg-amber-500/15 text-amber-700 dark:text-amber-400',
}[variant];
const valueColor = {
default: 'text-foreground',
accent: 'text-accent',
success: 'text-emerald-700 dark:text-emerald-400',
warning: 'text-amber-700 dark:text-amber-400',
}[variant];
return (
<button
type="button"
className="flex items-center gap-3 rounded-lg border bg-card/50 px-4 py-2.5 transition-all hover:-translate-y-0.5 hover:bg-card hover:shadow-sm active:scale-[0.98]"
>
<div className={cn('flex size-9 items-center justify-center rounded-md', styles)}>
<Icon className="size-4" />
</div>
<div className="text-left">
<p className="text-[10px] uppercase tracking-wider text-muted-foreground">{title}</p>
<p className={cn('font-mono text-lg font-bold leading-tight', valueColor)}>{value}</p>
</div>
</button>
);
}
function DemoHeroBar() {
return (
<div className="space-y-6 p-6">
<div className="relative overflow-hidden rounded-xl border bg-gradient-to-br from-background via-background to-muted/30">
<div className="pointer-events-none absolute inset-0 opacity-[0.03]">
<div
className="absolute inset-0"
style={{
backgroundImage: `radial-gradient(circle at 1px 1px, currentColor 1px, transparent 0)`,
backgroundSize: '24px 24px',
}}
/>
</div>
<div className="relative flex flex-col gap-4 p-6 lg:flex-row lg:items-center lg:justify-between">
<div className="flex items-center gap-4">
<div className="flex size-12 items-center justify-center rounded-xl bg-accent/15 text-accent">
<Zap className="size-6" />
</div>
<div>
<div className="flex items-center gap-3">
<h1 className="text-2xl font-bold">Demo Dashboard</h1>
<Badge variant="outline" className="font-mono text-xs">
v1.0.0
</Badge>
</div>
<p className="mt-1 text-sm text-muted-foreground">
Single-row dense hero — logo + title + version + inline stats
</p>
</div>
</div>
<div className="flex flex-wrap items-center gap-3">
<DemoInlineStat title="Profiles" value={4} icon={Key} variant="accent" />
<DemoInlineStat title="CLIProxy" value={2} icon={Zap} variant="accent" />
<DemoInlineStat title="Accounts" value={87} icon={Users} variant="default" />
<DemoInlineStat title="Health" value="23/43" icon={Activity} variant="warning" />
</div>
</div>
</div>
<p className="text-xs text-muted-foreground">
Body content renders below — no separate KpiRow needed since stats are absorbed into the
hero strip.
</p>
</div>
);
}
// -----------------------------------------------------------------------------
// Rail-anchored identity demo (canonical: pages/cliproxy.tsx)
//
// Page identity (brand + page-level CTA + status) lives inside the left rail of
// a Config 3-pane layout. Zero top chrome. The body archetype gets the full
// vertical viewport. Use for multi-entity Config pages where the rail naturally
// carries the page name.
// -----------------------------------------------------------------------------
function DemoRailAnchored() {
const [selectedId, setSelectedId] = useState('provider-a');
const railHeader = (
<div className="flex h-full flex-col bg-muted/30">
{/* Brand strip — replaces the global PageHeader */}
<div className="border-b bg-background p-4">
<div className="mb-1 flex items-center justify-between">
<div className="flex items-center gap-2">
<Zap className="size-5 text-primary" />
<h1 className="font-semibold">Demo Brand</h1>
</div>
<Button variant="ghost" size="icon" className="h-8 w-8">
<RefreshCw className="size-4" />
</Button>
</div>
<p className="mb-3 text-xs text-muted-foreground">Page subtitle / description</p>
<Button variant="default" size="sm" className="w-full gap-2">
<Sparkles className="size-4" /> Quick Setup
</Button>
</div>
{/* Provider list */}
<ScrollArea className="flex-1">
<div className="p-2">
<div className="px-3 py-2 text-xs font-medium uppercase tracking-wide text-muted-foreground">
Providers
</div>
<div className="space-y-1">
{DEMO_PROVIDERS.map((p) => (
<button
key={p.id}
type="button"
onClick={() => setSelectedId(p.id)}
className={cn(
'flex w-full items-center gap-2 rounded-md px-3 py-2 text-left text-sm transition-colors',
selectedId === p.id ? 'bg-accent text-accent-foreground' : 'hover:bg-accent/50'
)}
>
{p.icon}
<span className="flex-1 truncate">{p.label}</span>
<span className="text-xs text-muted-foreground">{p.badge}</span>
</button>
))}
</div>
</div>
</ScrollArea>
{/* Footer status */}
<div className="border-t bg-background p-3 text-xs text-muted-foreground">
<div className="flex items-center justify-between">
<span>4 providers</span>
<span className="flex items-center gap-1 text-emerald-600 dark:text-emerald-400">
<ShieldCheck className="size-3" /> 1 connected
</span>
</div>
</div>
</div>
);
return (
<div className="flex h-[640px] flex-col">
{/* NOTE: NO PageHeader at the top. Identity is inside the rail. */}
<ConfigLayout
left={railHeader}
form={
<FormPane
header={<p className="text-sm font-semibold">Provider A</p>}
footer={<Button size="sm">Save</Button>}
>
<FormSection id="general" title="General">
<Field label="Endpoint" defaultValue="https://example.local:8317" />
</FormSection>
</FormPane>
}
json={<JsonPane title="Effective" data={DEMO_CONFIG} />}
/>
</div>
);
}
// -----------------------------------------------------------------------------
// PageHeader demo (canonical: pages/health.tsx)
//
// Traditional title row with description and trailing actions. Use when the
// page does NOT fit either canonical hero AND the description carries
// non-redundant context (last refresh, page hierarchy, filter state, version).
// -----------------------------------------------------------------------------
function DemoPageHeader() {
+98 -224
View File
@@ -1,78 +1,34 @@
import { Button } from '@/components/ui/button';
import { Badge } from '@/components/ui/badge';
import { Skeleton } from '@/components/ui/skeleton';
import { RefreshCw, Terminal, Copy, Cpu } from 'lucide-react';
import { HealthGauge } from '@/components/health/health-gauge';
import { HealthStatsBar } from '@/components/health/health-stats-bar';
import { HealthGroupSection } from '@/components/health/health-group-section';
import { useHealth, type HealthGroup } from '@/hooks/use-health';
import { ShieldCheck } from 'lucide-react';
import { HealthStatusRibbon, HealthPriorityList, HealthAuditSection } from '@/components/health';
import { useHealth } from '@/hooks/use-health';
import { cn } from '@/lib/utils';
import { toast } from 'sonner';
import { useEffect, useState } from 'react';
import { motion } from 'framer-motion';
import { useTranslation } from 'react-i18next';
function getOverallStatus(summary: { passed: number; warnings: number; errors: number }) {
if (summary.errors > 0) return 'error';
if (summary.warnings > 0) return 'warning';
return 'ok';
}
function formatRelativeTime(
timestamp: number,
t: (key: string, options?: Record<string, unknown>) => string
): string {
const seconds = Math.floor((Date.now() - timestamp) / 1000);
if (seconds < 5) return t('health.justNow');
if (seconds < 60) return t('health.secondsAgo', { count: seconds });
const minutes = Math.floor(seconds / 60);
if (minutes < 60) return t('health.minutesAgo', { count: minutes });
const hours = Math.floor(minutes / 60);
return t('health.hoursAgo', { count: hours });
}
function sortGroupsByIssues(groups: HealthGroup[]): HealthGroup[] {
return [...groups].sort((a, b) => {
const aErrors = a.checks.filter((c) => c.status === 'error').length;
const bErrors = b.checks.filter((c) => c.status === 'error').length;
const aWarnings = a.checks.filter((c) => c.status === 'warning').length;
const bWarnings = b.checks.filter((c) => c.status === 'warning').length;
if (aErrors !== bErrors) return bErrors - aErrors;
return bWarnings - aWarnings;
});
}
function TerminalHeader() {
return (
<div className="font-mono text-sm text-muted-foreground flex items-center gap-2">
<span className="text-green-500">$</span>
<span>ccs doctor</span>
</div>
);
}
function LoadingSkeleton() {
return (
<div className="p-6 max-w-6xl mx-auto space-y-6">
{/* Hero skeleton */}
<div className="rounded-xl border bg-gradient-to-br from-background to-muted/20 p-6">
<div className="flex items-center gap-6">
<Skeleton className="w-[120px] h-[120px] rounded-full" />
<div className="flex-1 space-y-3">
<Skeleton className="h-5 w-48" />
<Skeleton className="h-8 w-64" />
<Skeleton className="h-4 w-32" />
<div className="p-6 max-w-6xl mx-auto space-y-12">
{/* Ribbon skeleton */}
<Skeleton className="h-20 w-full rounded-2xl" />
<div className="space-y-16">
{/* Priority skeleton */}
<div className="space-y-6">
<Skeleton className="h-4 w-48" />
<div className="grid grid-cols-1 lg:grid-cols-2 gap-6">
<Skeleton className="h-40 rounded-[2rem]" />
<Skeleton className="h-40 rounded-[2rem]" />
</div>
</div>
</div>
{/* Stats skeleton */}
<Skeleton className="h-16 w-full rounded-lg" />
{/* Groups skeleton */}
<div className="space-y-3">
{[1, 2, 3, 4].map((i) => (
<Skeleton key={i} className="h-20 rounded-lg" />
))}
{/* Audit skeleton */}
<div className="space-y-4">
<Skeleton className="h-4 w-48" />
{[1, 2, 3].map((i) => (
<Skeleton key={i} className="h-16 rounded-2xl" />
))}
</div>
</div>
</div>
);
@@ -82,180 +38,98 @@ export function HealthPage() {
const { t } = useTranslation();
const { data, isLoading, refetch, dataUpdatedAt } = useHealth();
// Use dataUpdatedAt directly instead of storing in state
const lastRefresh = dataUpdatedAt;
// Update relative time display by forcing re-render every second
const [tick, setTick] = useState(0);
useEffect(() => {
const interval = setInterval(() => setTick((t) => t + 1), 1000);
return () => clearInterval(interval);
}, []);
// Consume tick to prevent unused variable warning
void tick;
const copyDoctorCommand = () => {
navigator.clipboard.writeText('ccs doctor');
toast.success(t('health.copied'));
};
const handleRefresh = () => {
refetch();
toast.info(t('health.refreshing'));
};
if (isLoading && !data) {
return <LoadingSkeleton />;
}
const overallStatus = data ? getOverallStatus(data.summary) : 'ok';
const sortedGroups = data?.groups ? sortGroupsByIssues(data.groups) : [];
const priorityChecks =
data?.checks.filter((c) => c.status === 'error' || c.status === 'warning') ?? [];
const hasIssues = priorityChecks.length > 0;
return (
<div className="p-6 max-w-6xl mx-auto space-y-6">
{/* Hero Section - Terminal-inspired control center header */}
<div
className={cn(
'relative overflow-hidden rounded-xl border p-6',
'bg-gradient-to-br from-background via-background to-muted/30'
)}
>
{/* Subtle scan lines effect */}
<div className="relative pb-20 overflow-hidden">
{/* Dynamic Background */}
<div className="absolute inset-0 pointer-events-none">
<div
className="absolute inset-0 opacity-[0.02] pointer-events-none"
className={cn(
'absolute top-[5%] -left-[5%] w-[60%] h-[60%] blur-[100px] rounded-full opacity-[0.08] transition-colors duration-1000',
hasIssues ? 'bg-rose-500' : 'bg-emerald-500'
)}
/>
<div
className={cn(
'absolute -bottom-[5%] -right-[5%] w-[50%] h-[50%] blur-[100px] rounded-full opacity-[0.06] transition-colors duration-1000',
hasIssues ? 'bg-amber-500' : 'bg-blue-500'
)}
/>
{/* Grain overlay - Local SVG data URI for reliability */}
<div
className="absolute inset-0 opacity-[0.03] mix-blend-overlay"
style={{
backgroundImage: `repeating-linear-gradient(0deg, transparent, transparent 2px, currentColor 2px, currentColor 3px)`,
backgroundImage: `url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E")`,
}}
/>
{/* Grid pattern background */}
<div className="absolute inset-0 opacity-[0.03] pointer-events-none">
<div
className="absolute inset-0"
style={{
backgroundImage: `radial-gradient(circle at 1px 1px, currentColor 1px, transparent 0)`,
backgroundSize: '24px 24px',
}}
/>
</div>
<div className="relative flex flex-col md:flex-row items-start md:items-center gap-6">
{/* Left: Health Gauge - excludes info from percentage */}
{data && (
<div className="shrink-0">
<HealthGauge
passed={data.summary.passed}
total={data.summary.total - data.summary.info}
status={overallStatus}
size="md"
/>
</div>
)}
{/* Center: Title and status */}
<div className="flex-1 space-y-3">
{/* Terminal prompt */}
<TerminalHeader />
{/* Main title */}
<div className="flex items-center gap-3 flex-wrap">
<h1 className="text-2xl font-bold font-mono tracking-tight">
{t('health.systemHealth')}
</h1>
{data?.version && (
<Badge variant="outline" className="font-mono text-xs bg-muted/50">
{t('health.build', { version: data.version })}
</Badge>
)}
</div>
{/* Status message */}
<div className="flex items-center gap-2 text-sm">
<Cpu className="w-4 h-4 text-muted-foreground" />
<span className="text-muted-foreground">{t('health.lastScan')}</span>
<span className="font-mono">
{lastRefresh ? formatRelativeTime(lastRefresh, t) : '--'}
</span>
<span className="text-muted-foreground">|</span>
<span className="text-muted-foreground">{t('health.autoRefresh')}</span>
<span className="font-mono text-green-500">30s</span>
</div>
</div>
{/* Right: Actions */}
<div className="flex items-center gap-2 shrink-0">
<Button
variant="outline"
size="sm"
onClick={copyDoctorCommand}
className="gap-2 font-mono text-xs"
>
<Terminal className="w-3 h-3" />
ccs doctor
<Copy className="w-3 h-3 opacity-50" />
</Button>
<Button
variant="outline"
size="sm"
onClick={handleRefresh}
disabled={isLoading}
className="gap-2"
>
<RefreshCw className={cn('w-4 h-4', isLoading && 'animate-spin')} />
<span className="hidden sm:inline">{t('health.refresh')}</span>
<kbd className="hidden md:inline-flex h-5 items-center gap-1 rounded border bg-muted px-1.5 font-mono text-[10px] font-medium text-muted-foreground">
R
</kbd>
</Button>
</div>
</div>
</div>
{/* Stats Bar */}
{data && (
<div className="rounded-lg border bg-card p-4">
<HealthStatsBar
total={data.summary.total}
passed={data.summary.passed}
warnings={data.summary.warnings}
errors={data.summary.errors}
info={data.summary.info}
<div className="relative p-6 max-w-6xl mx-auto space-y-12">
{/* Status Ribbon */}
{data && (
<HealthStatusRibbon
summary={data.summary}
version={data.version}
lastScan={dataUpdatedAt}
isLoading={isLoading}
onRefresh={refetch}
/>
</div>
)}
)}
{/* Health Check Groups - Single column layout */}
{sortedGroups.length > 0 && (
<div className="space-y-3">
{sortedGroups.map((group, index) => (
<HealthGroupSection
key={group.id}
group={group}
defaultOpen={
index < 2 ||
group.checks.some((c) => c.status === 'error' || c.status === 'warning')
}
/>
))}
</div>
)}
<div className="space-y-16">
{/* Priority Issues */}
{hasIssues ? (
<HealthPriorityList checks={priorityChecks} />
) : (
<motion.div
initial={{ opacity: 0, scale: 0.98 }}
animate={{ opacity: 1, scale: 1 }}
className="py-20 flex flex-col items-center text-center space-y-6"
>
<div className="relative">
<div className="w-24 h-24 rounded-[2.5rem] bg-emerald-500/10 flex items-center justify-center">
<ShieldCheck className="w-12 h-12 text-emerald-500" />
</div>
<div className="absolute inset-0 w-24 h-24 rounded-[2.5rem] bg-emerald-500/20 animate-ping opacity-20" />
</div>
<div className="space-y-2">
<h2 className="text-3xl font-bold tracking-tight">{t('health.allSystemsClear')}</h2>
<p className="text-muted-foreground max-w-[40ch] mx-auto">
{t('health.optimalStateDesc')}
</p>
</div>
</motion.div>
)}
{/* Footer metadata */}
<div className="flex items-center justify-between text-xs text-muted-foreground border-t pt-4">
<div className="flex items-center gap-4">
<span>
{t('health.version')} <span className="font-mono">{data?.version ?? '--'}</span>
</span>
<span>
{t('health.platform')}{' '}
<span className="font-mono">
{typeof navigator !== 'undefined' ? navigator.platform : 'linux'}
</span>
</span>
{/* Detailed Audit */}
{data?.groups && <HealthAuditSection groups={data.groups} />}
</div>
<div className="flex items-center gap-2">
<div className="w-2 h-2 rounded-full bg-green-500 animate-pulse" />
<span>{t('health.liveMonitoring')}</span>
{/* Footer metadata */}
<div className="flex flex-col sm:flex-row items-center justify-between gap-4 text-[10px] uppercase tracking-widest font-mono text-muted-foreground/60 border-t border-border/40 pt-8">
<div className="flex items-center gap-6">
<div className="flex flex-col">
<span className="opacity-50">Build</span>
<span className="text-foreground">{data?.version ?? '--'}</span>
</div>
<div className="flex flex-col">
<span className="opacity-50">Platform</span>
<span className="text-foreground">
{typeof navigator !== 'undefined' ? navigator.platform : 'linux'}
</span>
</div>
</div>
<div className="flex items-center gap-2 px-3 py-1 rounded-full bg-muted/30 border border-border/40">
<div className="w-1.5 h-1.5 rounded-full bg-emerald-500 animate-pulse" />
<span>{t('health.liveMonitoring')}</span>
</div>
</div>
</div>
</div>