diff --git a/AGENTS.md b/AGENTS.md index f52c7dd..bfe51ec 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -5,6 +5,9 @@ Self-hosted multi-user workout logger (Next.js app) packaged as a StartOS 0.4 `s > **Inbox check:** At session start, if `~/Projects/standards/INBOX.md` exists, scan it for > items tagged `(proof-of-work)` and surface them before proposing next steps; triage with `/triage`. +> **Design:** before building or changing any user-facing UI, read `design/DESIGN.md` and +> `design/tokens.tokens.json` and conform to them. + ## Stack (versions that matter) - **Next.js 15** (App Router, server components + server actions, SSE streaming) — dynamic request APIs are async (see Conventions) @@ -31,6 +34,7 @@ start9/0.4/ ← StartOS packaging wrapper docker_entrypoint.sh ← boot: first-boot seed, additive ALTERs, library reconcile Makefile / s9pk.mk ← s9pk build (ARCHES := x86) startos/versions/ ← one file per ExVer version + index.ts (the version graph) +design/ ← UI design contract: DESIGN.md + tokens.tokens.json (read before UI work); brand/ inspiration/ ~/.proof-of-work/ ← publish.sh + unpublish.sh (NOT in repo; self-hosted registry) ``` diff --git a/ROADMAP.md b/ROADMAP.md index ee7aa21..f090bcc 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -36,6 +36,20 @@ Done in 1.1.0:9 (P2 batch): input-validation 500s → 400 (`lib/http.ts readJson - CSV export↔import round-trip: export writes `setX`-prefixed headers (`setCalories`/`setWatts`/`setNotes`) the importer doesn't read (it expects `calories`/`watts`/`notes`), so the app's own CSV export silently drops those on re-import (calories long-standing; watts since 1.2.0:4). Fix by aligning export header names with the parser, or adding the prefixed names as `knownColumns` aliases. (JSON account export/import round-trips fine.) - Charts/progress views over history (the data and 1RM estimates already exist). +## Design + +Cleanup backlog from the 2026-06-19 design-contract extract (`design/DESIGN.md` + `tokens.tokens.json`). The contract documents the *intended* system; these are the spots where the code diverges. All cosmetic/mechanical — none ship-blocking. Found by the `design-checker` agent (re-run it for fresh file:line lists). + +- **Unify off-palette neutrals → zinc** (21 hits, 12 files): every `hover:bg-gray-100`/`active:bg-gray-200`/`hover:text-gray-200` on the white primary button → `zinc-200`/`zinc-300`. Canonical hover is `zinc-200` (`color.action.primary-hover-bg`). Worst: `app/main/workouts/page.tsx` (4), auth `LoginForm`/`SignupForm`, `dashboard/page.tsx`, `programs/page.tsx`. +- **Unify success hue → emerald** (10 hits): `green-*` → `emerald-*`. Worst: `components/settings/SettingsForm.tsx` (6), `WorkoutForm.tsx:696`, `SetRow.tsx:477`, `ExerciseCard.tsx:12`. +- **Unify warning hue → amber** (13 hits): `yellow-*` → `amber-*`. Worst: `app/main/import/page-csv.tsx` (6), `SettingsForm.tsx` (6), `ExerciseCard.tsx:14`. +- **No solid red button** (1): `components/settings/DangerZone.tsx:101` uses `bg-red-700` — convert to the ghost/wash destructive treatment (`text-red-400 border-red-800 hover:bg-red-950/30`). (Red elsewhere — 65 wash/outline/text uses — already conforms.) +- **Radius two-tier (4px control / 8px container)**: `rounded-md` (6px, 27 hits, mostly `SetRow.tsx` ×16 + `WorkoutForm.tsx` ×9) → `rounded`; `rounded-lg` on primary *buttons* (5 hits in `workouts/page.tsx`, `dashboard/page.tsx`) → `rounded`. (`rounded-full` on the FAB is fine.) +- **Shadows are overlay-only**: drop `shadow-2xl` on the static login/signup cards (`app/auth/{login,signup}/page.tsx:23`) and `shadow-lg` on the FAB + desktop CTA (`workouts/page.tsx:163,174`) — depth = bg layering + border. +- **Sub-scale font sizes** (10 hits): `text-[10px]`/`text-[11px]` (below the 12px `text-xs` floor) → `text-xs`. +- **Extract a shared ` + + + + + +
+
+
+
B · Blood Red
+
#DC2626 · Tailwind red-600 (deeper)
+
+
+
+
Personal Record
+
Back Squat
+
142.5▲ +5kg
+
+
+ + PR · Week 6 +
+
+
+
+ + +
+
+
+
C · Vermilion
+
#FF3B30 · hot orange-red
+
+
+
+
Personal Record
+
Back Squat
+
142.5▲ +5kg
+
+
+ + PR · Week 6 +
+
+
+
+ + +
+
+
+
D · Crimson
+
#E11D48 · cooler, pink-edge (rose-600)
+
+
+
+
Personal Record
+
Back Squat
+
142.5▲ +5kg
+
+
+ + PR · Week 6 +
+
+
+
+ + + diff --git a/design/inspiration/red-accent-candidates.png b/design/inspiration/red-accent-candidates.png new file mode 100644 index 0000000..7549fa7 Binary files /dev/null and b/design/inspiration/red-accent-candidates.png differ diff --git a/design/tokens.tokens.json b/design/tokens.tokens.json new file mode 100644 index 0000000..b4a06f2 --- /dev/null +++ b/design/tokens.tokens.json @@ -0,0 +1,93 @@ +{ + "$description": "Proof of Work design tokens (W3C DTCG). Extracted document-as-is from the as-built Tailwind UI on 2026-06-19; red accent (#DC2626) and the two-tier radius rule are owner-driven. Neutral ramp is Tailwind 'zinc'. Hexes are the literal Tailwind values the code already uses.", + "color": { + "bg": { + "canvas": { "$type": "color", "$value": "#0A0A0A", "$description": "App background; PWA theme_color and bg. The anchor value." }, + "surface": { "$type": "color", "$value": "#18181B", "$description": "zinc-900 — default raised surface (cards, panels)." }, + "raised": { "$type": "color", "$value": "#27272A", "$description": "zinc-800 — controls, chips, next step up / hover." }, + "inset": { "$type": "color", "$value": "#09090B", "$description": "zinc-950 — rare deep wells, slightly below canvas." } + }, + "border": { + "subtle": { "$type": "color", "$value": "#27272A", "$description": "zinc-800 — default hairline." }, + "default": { "$type": "color", "$value": "#3F3F46", "$description": "zinc-700." }, + "strong": { "$type": "color", "$value": "#52525B", "$description": "zinc-600 — emphasis/hover." } + }, + "text": { + "primary": { "$type": "color", "$value": "#FFFFFF" }, + "secondary": { "$type": "color", "$value": "#A1A1AA", "$description": "zinc-400." }, + "muted": { "$type": "color", "$value": "#71717A", "$description": "zinc-500." }, + "subtle": { "$type": "color", "$value": "#52525B", "$description": "zinc-600 — disabled/least emphasis." }, + "inverted": { "$type": "color", "$value": "#000000", "$description": "Text on white/light surfaces (primary button)." } + }, + "accent": { + "red": { "$type": "color", "$value": "#DC2626", "$description": "Canonical red — brand accent AND error/destructive (red-600). Distinguished by treatment, not by a second red." }, + "red-strong": { "$type": "color", "$value": "#B91C1C", "$description": "red-700 — hover/pressed for the accent." }, + "red-text": { "$type": "color", "$value": "#F87171", "$description": "red-400 — red text/icon on the dark canvas." }, + "red-border": { "$type": "color", "$value": "#991B1B", "$description": "red-800 — error/destructive outlines." }, + "red-wash": { "$type": "color", "$value": "rgba(127, 29, 29, 0.30)", "$description": "red-900/30 — error banner / destructive-hover background wash." } + }, + "state": { + "success": { "$type": "color", "$value": "#34D399", "$description": "emerald-400 — success text/icon on dark." }, + "success-fill": { "$type": "color", "$value": "#059669", "$description": "emerald-600 — success fill." }, + "warning": { "$type": "color", "$value": "#FBBF24", "$description": "amber-400 — warning text/icon." }, + "warning-edge": { "$type": "color", "$value": "#78350F", "$description": "amber-900 — warning border/wash edge." }, + "info": { "$type": "color", "$value": "#60A5FA", "$description": "blue-400 — info text (used sparingly)." }, + "info-wash": { "$type": "color", "$value": "#172554", "$description": "blue-950 — info wash background." } + }, + "action": { + "primary-bg": { "$type": "color", "$value": "{color.text.primary}", "$description": "Primary button background is WHITE — not a hue." }, + "primary-text": { "$type": "color", "$value": "{color.text.inverted}" }, + "primary-hover-bg": { "$type": "color", "$value": "#E4E4E7", "$description": "zinc-200." }, + "primary-disabled-bg": { "$type": "color", "$value": "#3F3F46", "$description": "zinc-700." }, + "primary-disabled-text": { "$type": "color", "$value": "#71717A", "$description": "zinc-500." } + } + }, + "font": { + "family": { + "display": { "$type": "fontFamily", "$value": "Bebas Neue", "$description": "var(--font-display). Headings, buttons, labels — always UPPERCASE + tracked." }, + "body": { "$type": "fontFamily", "$value": "Space Grotesk", "$description": "var(--font-sans). All running text, data, numbers." } + }, + "size": { + "xs": { "$type": "dimension", "$value": "12px" }, + "sm": { "$type": "dimension", "$value": "14px" }, + "base": { "$type": "dimension", "$value": "16px" }, + "lg": { "$type": "dimension", "$value": "18px" }, + "xl": { "$type": "dimension", "$value": "20px" }, + "2xl": { "$type": "dimension", "$value": "24px" }, + "3xl": { "$type": "dimension", "$value": "30px" }, + "4xl": { "$type": "dimension", "$value": "36px" } + }, + "weight": { + "normal": { "$type": "fontWeight", "$value": 400 }, + "medium": { "$type": "fontWeight", "$value": 500 }, + "semibold": { "$type": "fontWeight", "$value": 600 }, + "bold": { "$type": "fontWeight", "$value": 700 } + }, + "letterSpacing": { + "wider": { "$type": "dimension", "$value": "0.05em", "$description": "Tailwind tracking-wider — the signature pairing with UPPERCASE Bebas." } + } + }, + "space": { + "$description": "Tailwind 4px scale; the values the as-built UI leans on.", + "1": { "$type": "dimension", "$value": "4px" }, + "2": { "$type": "dimension", "$value": "8px" }, + "3": { "$type": "dimension", "$value": "12px" }, + "4": { "$type": "dimension", "$value": "16px" }, + "5": { "$type": "dimension", "$value": "20px" }, + "6": { "$type": "dimension", "$value": "24px" }, + "8": { "$type": "dimension", "$value": "32px" } + }, + "radius": { + "control": { "$type": "dimension", "$value": "4px", "$description": "Tailwind `rounded` — buttons, inputs, chips." }, + "container": { "$type": "dimension", "$value": "8px", "$description": "Tailwind `rounded-lg` — cards, panels, modals, banners." }, + "full": { "$type": "dimension", "$value": "9999px", "$description": "Pills, avatars, dots." } + }, + "size": { + "sidebar-width": { "$type": "dimension", "$value": "240px", "$description": "CSS var --sidebar-width; desktop sidebar (md+)." }, + "nav-height": { "$type": "dimension", "$value": "64px", "$description": "CSS var --nav-height." }, + "bottom-nav-height": { "$type": "dimension", "$value": "64px", "$description": "CSS var --bottom-nav-height; mobile bottom nav." } + }, + "shadow": { + "overlay": { "$type": "shadow", "$value": "0 10px 15px -3px rgba(0,0,0,0.5)", "$description": "Reserved for floating overlays ONLY (modals, popovers). Static cards use bg layering + borders, never a shadow. Value approximates Tailwind shadow-lg; the codebase also uses shadow-xl/2xl for overlays." } + } +}