diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4140f1c5c..05b52c625 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,6 +24,10 @@ jobs: - run: bun install --frozen-lockfile + # Shared design tokens live in packages/ui/src/styles/tokens.css; + # fails if an app re-declares one (landing ↔ web drift guard). + - run: bun run check:tokens + - run: bun turbo typecheck test build --filter='!@maple/ingest' knip: diff --git a/DESIGN.md b/DESIGN.md index 3d8b86056..141e73dca 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -4,8 +4,8 @@ description: Open-source OpenTelemetry observability platform — terminal-nativ colors: primary: "oklch(0.714 0.154 59)" primary-foreground: "oklch(0.207 0.008 67)" - primary-light: "oklch(0.59 0.14 242)" - primary-light-foreground: "oklch(0.98 0.01 237)" + primary-light: "oklch(0.66 0.16 59)" + primary-light-foreground: "oklch(0.21 0.008 67)" background: "oklch(0.207 0.008 67)" foreground: "oklch(0.91 0.016 74)" card: "oklch(0.224 0.009 75)" @@ -147,12 +147,16 @@ The shared visual posture is **Linear / Vercel / Axiom adjacent**: compact app s ## 2. Colors: The Operator Palette -The palette is **theme-divergent on purpose**. Light mode anchors on a vivid blue primary (`oklch(0.59 0.14 242)`); dark mode rebases on a warm amber-gold primary (`oklch(0.714 0.154 59)`). Both are correct — neither is the "true" Maple color. The dark theme is canonical for the frontmatter (matches `
`); light tokens travel alongside in the `.dark` ↔ `:root` swap. +The palette is **theme-tuned, same hue**. Both themes anchor on Maple's warm amber (hue ~59): dark mode uses `oklch(0.714 0.154 59)`, light mode the same hue tuned slightly deeper (`oklch(0.66 0.16 59)`) so it reads on a white canvas. The dark theme is canonical for the frontmatter (matches ``); light tokens travel alongside in the `.dark` ↔ `:root` swap. + +### Token source of truth + +All shared design tokens (color, radius, fonts, severity/service/chart palettes, the `@theme inline` mapping) live in **`packages/ui/src/styles/tokens.css`** and are imported by both `apps/web` and `apps/landing` via `@import "@maple/ui/styles/tokens.css"` (after `@import "tailwindcss"`). Apps must never re-declare a shared token name — `bun run check:tokens` fails CI if they do. App-local tokens stay in the apps: web's Sugar High `--sh-*` set, landing's marketing set (`--bg-elevated`, `--bg-deep`, `--amber-deep`, `--amber-soft`). Landing is dark-only: it pins `class="dark"` on `` instead of maintaining its own token copy. The ReactFlow controls chrome (flat, square corners, `border-radius: 0`) is part of the shared file — the product rendering is the reference for landing mockups. ### Primary - **Amber Signal** (`oklch(0.714 0.154 59)`, dark canonical): the accent used on primary actions, the active sidebar item, `chart-1` / `chart-p95`, and severity-warn. Warm enough to read at low ambient light without burning. (Note: `chart-throughput` used to share this hue, but was split to purple — `oklch(0.66 0.14 290)` — so the built-in alert signal chips can distinguish throughput from p95.) -- **Blue Beacon** (`oklch(0.59 0.14 242)`, light counterpart): the same role under light theme. Vivid blue, full chroma at a mid-lightness — feels appropriate in daylight, would feel hostile in a dim room (hence the swap). +- **Amber Signal, daylight cut** (`oklch(0.66 0.16 59)`, light counterpart): the same role and hue under light theme, a touch deeper and more chromatic so it holds contrast on white. (An earlier draft used a blue light primary; it shipped briefly as stale shadcn tokens and was rebased to amber.) ### Secondary: Severity Ramp (the system's loudest semantic color) diff --git a/apps/landing/src/components/AppMock.astro b/apps/landing/src/components/AppMock.astro index f1a649c8c..3e23a649e 100644 --- a/apps/landing/src/components/AppMock.astro +++ b/apps/landing/src/components/AppMock.astro @@ -20,12 +20,11 @@ interface MapNode { dbSystem?: DbSystem; platform?: Platform; rps: string; - rpsPrefix?: string; err?: string; avg: string; pods?: number; } -interface MapEdge { from: string; to: string; rps?: number; calls: string; hot?: boolean } +interface MapEdge { from: string; to: string; rps?: number; calls: string; err: string; hot?: boolean } const MAP_NODE_W = 220; const MAP_NODE_H = 62; @@ -38,22 +37,22 @@ const MAP_ROWS_PER_COL: number[][] = [ ]; const mapNodes: MapNode[] = [ - { id: "edge", label: "edge", col: 0, row: 0, platform: "cloudflare", rps: "12.4k", err: "0.1", avg: "8ms", pods: 6 }, - { id: "checkout", label: "checkout-api", col: 1, row: 0, platform: "kubernetes", rps: "4.2k", err: "0.3", avg: "24ms", pods: 12 }, - { id: "auth", label: "auth-svc", col: 2, row: 0, platform: "kubernetes", rps: "4.2k", err: "0.0", avg: "6ms", pods: 8 }, - { id: "pricing", label: "pricing", col: 2, row: 1, platform: "kubernetes", hot: true, rps: "2.8k", err: "2.4", avg: "142ms", pods: 4 }, - { id: "payments", label: "payments", col: 2, row: 2, platform: "lambda", rps: "1.1k", err: "0.5", avg: "38ms" }, - { id: "redis", label: "redis", col: 3, row: 0, db: true, dbSystem: "redis", rps: "18.2k", avg: "2ms" }, - { id: "postgres", label: "postgres", col: 3, row: 1, db: true, dbSystem: "postgres", rps: "6.4k", avg: "14ms" }, + { id: "edge", label: "edge", col: 0, row: 0, platform: "cloudflare", rps: "~12.4k", err: "0.1", avg: "8ms", pods: 6 }, + { id: "checkout", label: "checkout-api", col: 1, row: 0, platform: "kubernetes", rps: "~4.2k", err: "0.3", avg: "24ms", pods: 12 }, + { id: "auth", label: "auth-svc", col: 2, row: 0, platform: "kubernetes", rps: "~4.2k", err: "0.0", avg: "6ms", pods: 8 }, + { id: "pricing", label: "pricing", col: 2, row: 1, platform: "kubernetes", hot: true, rps: "~2.8k", err: "2.4", avg: "142ms", pods: 4 }, + { id: "payments", label: "payments", col: 2, row: 2, platform: "lambda", rps: "~1.1k", err: "0.5", avg: "38ms" }, + { id: "redis", label: "redis", col: 3, row: 0, db: true, dbSystem: "redis", rps: "~18.2k", err: "0.0", avg: "2ms" }, + { id: "postgres", label: "postgres", col: 3, row: 1, db: true, dbSystem: "postgres", rps: "~6.4k", err: "0.1", avg: "14ms" }, ]; const mapEdges: MapEdge[] = [ - { from: "edge", to: "checkout", rps: 4200, calls: "4.2k" }, - { from: "checkout", to: "auth", rps: 4200, calls: "4.2k" }, - { from: "checkout", to: "pricing", rps: 2800, calls: "2.8k", hot: true }, - { from: "checkout", to: "payments", rps: 1100, calls: "1.1k" }, - { from: "pricing", to: "redis", rps: 12400, calls: "12.4k" }, - { from: "pricing", to: "postgres", rps: 6400, calls: "6.4k" }, + { from: "edge", to: "checkout", rps: 4200, calls: "~4.2k", err: "0.3%" }, + { from: "checkout", to: "auth", rps: 4200, calls: "~4.2k", err: "0.0%" }, + { from: "checkout", to: "pricing", rps: 2800, calls: "~2.8k", err: "2.4%", hot: true }, + { from: "checkout", to: "payments", rps: 1100, calls: "~1.1k", err: "0.5%" }, + { from: "pricing", to: "redis", rps: 12400, calls: "~12.4k", err: "0.0%" }, + { from: "pricing", to: "postgres", rps: 6400, calls: "~6.4k", err: "0.1%" }, ]; // Service-color slot (matches packages/ui/src/lib/colors.ts: hash idx → --service-N) @@ -111,6 +110,81 @@ const mapEdgeRender = mapEdges.map((e, i) => { const MAP_VIEW_W = 1020; const MAP_VIEW_H = 360; + +// ─── Shared row data ──────────────────────────────────────────────────── +// Timestamps mirror the product's trace list ("Jul 14, 06:43:14 PM (12s ago)"). +interface TraceRow { + id: string; + method: "GET" | "POST" | "PUT"; + path: string; + at: string; + services: string[]; + dur: string; + status: string; + error?: boolean; +} +const traceRows: TraceRow[] = [ + { id: "cf61c660", method: "POST", path: "/api/checkout", at: "Jul 14, 06:43:14 PM (11s ago)", services: ["checkout-api", "pricing"], dur: "203.0ms", status: "200" }, + { id: "b04e6c50", method: "POST", path: "/api/payment/charge", at: "Jul 14, 06:43:12 PM (13s ago)", services: ["payments", "auth-svc"], dur: "1.24s", status: "500", error: true }, + { id: "40be760a", method: "GET", path: "/api/products", at: "Jul 14, 06:43:11 PM (14s ago)", services: ["edge", "checkout-api"], dur: "45.0ms", status: "200" }, + { id: "d9a3b5c7", method: "POST", path: "/api/auth/login", at: "Jul 14, 06:43:09 PM (16s ago)", services: ["auth-svc"], dur: "89.0ms", status: "200" }, + { id: "61f5f1aa", method: "GET", path: "/api/users/me", at: "Jul 14, 06:43:08 PM (17s ago)", services: ["edge", "auth-svc"], dur: "23.0ms", status: "200" }, + { id: "3fd5d93c", method: "PUT", path: "/api/orders/confirm", at: "Jul 14, 06:43:05 PM (20s ago)", services: ["checkout-api"], dur: "156.0ms", status: "200" }, +]; +const methodChip: Record