mirror of
https://github.com/tiennm99/ccs.git
synced 2026-10-14 06:17:27 +00:00
feat(ui): add design system foundations
Introduce two locked page archetypes -- Config (3-pane) and Monitor (KPI row + 12-col grid) -- wrapped by shared PageShell + PageHeader. Primitives: - page-shell/: PageShell, PageHeader, EmptyState, ErrorState - config-layout/: ConfigLayout, ListPane, SectionRail, FormPane, FormSection, JsonPane (read-only by default, opt-in editable) - monitor-layout/: MonitorLayout, KpiRow, KpiCard, MonitorGrid, MonitorCard (incl. variant="terminal") Single ConfigLayout component, prop-controlled left rail: - ListPane for multi-entity (cliproxy, accounts, providers) - SectionRail for single-entity (codex, copilot, cursor, droid) with IntersectionObserver scroll-spy - omit for none DEV-ONLY /_styleguide route gated by import.meta.env.DEV. Shows every primitive plus composed Config (multi + single) and Monitor demos using fully anonymized data (Provider A/B/C, fake metrics). Phase 1 of dashboard design system unification. Existing pages untouched this phase -- migrations land in Phase 2+. Locked decisions in ui/docs/design-decisions.md: 1. In-app /_styleguide over Storybook 2. Archetype name: Monitor (not Dashboard) 3. JsonPane read-only by default 4. Health terminal aesthetic kept as variant 5. i18n per-page namespaces 6. SectionRail uses scroll-spy
This commit is contained in:
1 parent
eba53715e7
commit
64e78f6e69
20 files changed
+1513
No files matched your search
@@ -0,0 +1,18 @@
|
||||
# Design System — Decisions Log
|
||||
|
||||
The 6 open questions from the brainstorm phase, resolved before implementation.
|
||||
|
||||
| # | Question | Decision | Rationale |
|
||||
|---|----------|----------|-----------|
|
||||
| 1 | Storybook vs in-app `/_styleguide` | **In-app `/_styleguide`** | Zero-config, lives in the repo, gated by `import.meta.env.DEV`. Storybook is a heavy second build pipeline for marginal benefit at this scale. |
|
||||
| 2 | Archetype B name (Monitor vs Dashboard) | **Monitor** | "Dashboard" is the whole product. "Monitor" matches existing health/analytics framing and avoids overloading the term. |
|
||||
| 3 | JsonPane editability scope | **Read-only by default**, opt-in `editable` prop | Cliproxy is the only page that genuinely needs in-pane editing today. Opt-in keeps the API safe for codex/copilot/cursor (read-only). |
|
||||
| 4 | Health terminal aesthetic | **Keep** as `MonitorCard variant="terminal"` | Preserves the `ccs health --watch` feel. Implemented as opt-in variant, not a separate primitive. |
|
||||
| 5 | i18n key namespace convention | **Per-page namespaces** (`pages.<name>.*`) + shared (`common.*`) | Current i18n already uses ad-hoc per-page keys; this just formalizes it. Primitives use `common.*` so they're translation-stable across pages. |
|
||||
| 6 | SectionRail activation | **Scroll-spy** via `IntersectionObserver` | Preserves long-form config feel + shows all validation errors at once. Click-to-switch hides errors in inactive sections, which is worse UX for forms. |
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
@@ -0,0 +1,179 @@
|
||||
# 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`.
|
||||
|
||||
> Live preview in dev: `bun run dev` then visit `/_styleguide`.
|
||||
|
||||
---
|
||||
|
||||
## 1. The shell — every page
|
||||
|
||||
```
|
||||
PageShell
|
||||
├─ PageHeader (title · description · status · actions)
|
||||
└─ <archetype body>
|
||||
```
|
||||
|
||||
```tsx
|
||||
<PageShell>
|
||||
<PageHeader title="Cliproxy" status={…} actions={…} />
|
||||
<ConfigLayout … /> {/* OR <MonitorLayout> */}
|
||||
</PageShell>
|
||||
```
|
||||
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
## 2. Config archetype — 3-pane
|
||||
|
||||
Used by every page that configures one or more entities (cliproxy, accounts, codex, copilot, cursor, droid, claude-extension, api, shared, profiles).
|
||||
|
||||
```
|
||||
┌──────────┬──────────────────┬──────────┐
|
||||
│ left │ form (FormPane) │ json │
|
||||
│ rail │ │ (right) │
|
||||
└──────────┴──────────────────┴──────────┘
|
||||
```
|
||||
|
||||
One component, prop-controlled left rail:
|
||||
|
||||
```tsx
|
||||
// Multi-entity (cliproxy, accounts, providers)
|
||||
<ConfigLayout
|
||||
left={<ListPane items={…} selectedId={id} onSelect={…} />}
|
||||
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
|
||||
|
||||
- **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).
|
||||
|
||||
### 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).
|
||||
|
||||
```
|
||||
┌────────────────────────────────────────────────┐
|
||||
│ 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}> ... │
|
||||
└────────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
```tsx
|
||||
<MonitorLayout
|
||||
kpis={
|
||||
<KpiRow>
|
||||
<KpiCard label="Active accounts" value="87" hint="▲ 3" tone="positive" />
|
||||
…
|
||||
</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>
|
||||
</MonitorGrid>
|
||||
</MonitorLayout>
|
||||
```
|
||||
|
||||
### Rules
|
||||
|
||||
- **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.
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
|
||||
---
|
||||
|
||||
## 5. Composing a new page
|
||||
|
||||
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.
|
||||
Reference in new issue
Block a user