From 59cfa1547f0589f1867db5f3d9f5a018c9028f72 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Tue, 8 Sep 2026 16:28:40 +0000 Subject: [PATCH] chore(design): convert gstack's own DESIGN.md to the open spec `gstack-design-md convert --write` on the repo's DESIGN.md: tokens in YAML front matter (typography.display/body/label/mono, colors with their light/dark qualifiers, spacing scale, rounded scale), Overview from Product Context and Aesthetic Direction, Colors / Typography / Layout as canonical sections, Motion, Grain Texture, and Decisions Log preserved as extras, format marker on line 2. Hand-checked; `check` reports spec with no token-reference errors. A Decisions Log row records the conversion and that DM Sans stays the body face: it is on the overused-as-display list, and body/UI use on an Operate surface is the allowed exception under the role-scoped rule. The pre-conversion file lives on as test/fixtures/design-md-legacy.md, which test/design-md.test.ts now uses for its legacy cases; the converted root file is asserted to be spec. Co-Authored-By: Claude Fable 5.1 --- DESIGN.md | 103 ++++++++++++++++++++++-------- test/design-md.test.ts | 11 +++- test/fixtures/design-md-legacy.md | 86 +++++++++++++++++++++++++ 3 files changed, 172 insertions(+), 28 deletions(-) create mode 100644 test/fixtures/design-md-legacy.md diff --git a/DESIGN.md b/DESIGN.md index d1f3ce3db..ac20f2ab8 100644 --- a/DESIGN.md +++ b/DESIGN.md @@ -1,37 +1,65 @@ +--- +# gstack: design-md-format=spec +name: gstack +typography: + display: + fontFamily: Satoshi + body: + fontFamily: DM Sans + label: + fontFamily: DM Sans + mono: + fontFamily: JetBrains Mono + fontFeature: tnum +colors: + primary-dark-mode: "#F59E0B" + primary-light-mode: "#D97706" + primary-text-accent-dark-mode: "#FBBF24" + primary-text-accent-light-mode: "#B45309" + zinc-50: "#FAFAFA" + zinc-400: "#A1A1AA" + zinc-600: "#52525B" + zinc-800: "#27272A" + surface-dark: "#141414" + base-dark: "#0C0C0C" + surface-light: "#FFFFFF" + base-light: "#FAFAF9" + success: "#22C55E" + warning: "#F59E0B" + error: "#EF4444" + info: "#3B82F6" +spacing: + 2xs: 2px + xs: 4px + sm: 8px + md: 16px + lg: 24px + xl: 32px + 2xl: 48px + 3xl: 64px +rounded: + sm: 4px + md: 8px + lg: 12px + full: 9999px +--- + # Design System — gstack -## Product Context +## Overview + - **What this is:** Community website for gstack — a CLI tool that turns Claude Code into a virtual engineering team - **Who it's for:** Developers discovering gstack, existing community members - **Space/industry:** Developer tools (peers: Linear, Raycast, Warp, Zed) - **Project type:** Community dashboard + marketing site -## Aesthetic Direction - **Direction:** Industrial/Utilitarian — function-first, data-dense, monospace as personality font - **Decoration level:** Intentional — subtle noise/grain texture on surfaces for materiality - **Mood:** Serious tool built by someone who cares about craft. Warm, not cold. The CLI heritage IS the brand. - **Reference sites:** formulae.brew.sh (competitor, but ours is live and interactive), Linear (dark + restrained), Warp (warm accents) -## Typography -- **Display/Hero:** Satoshi (Black 900 / Bold 700) — geometric with warmth, distinctive letterforms (the lowercase 'a' and 'g'). Not Inter, not Geist. Loaded from Fontshare CDN. -- **Body:** DM Sans (Regular 400 / Medium 500 / Semibold 600) — clean, readable, slightly friendlier than geometric display. Loaded from Google Fonts. -- **UI/Labels:** DM Sans (same as body) -- **Data/Tables:** JetBrains Mono (Regular 400 / Medium 500) — the personality font. Supports tabular-nums. Monospace should be prominent, not hidden in code blocks. Loaded from Google Fonts. -- **Code:** JetBrains Mono -- **Loading:** Google Fonts for DM Sans + JetBrains Mono, Fontshare for Satoshi. Use `display=swap`. -- **Scale:** - - Hero: 72px / clamp(40px, 6vw, 72px) - - H1: 48px - - H2: 32px - - H3: 24px - - H4: 18px - - Body: 16px - - Small: 14px - - Caption: 13px - - Micro: 12px - - Nano: 11px (JetBrains Mono labels) +## Colors -## Color - **Approach:** Restrained — amber accent is rare and meaningful. Dashboard data gets the color; chrome stays neutral. - **Primary (dark mode):** amber-500 #F59E0B — warm, energetic, reads as "terminal cursor" - **Primary (light mode):** amber-600 #D97706 — darker for contrast against white backgrounds @@ -50,12 +78,28 @@ - **Dark mode:** Default. Near-black base (#0C0C0C), surface cards at #141414, borders at #262626. - **Light mode:** Warm stone base (#FAFAF9), white surface cards, stone borders (#E7E5E4). Amber accent shifts to amber-600 for contrast. -## Spacing -- **Base unit:** 4px -- **Density:** Comfortable — not cramped (not Bloomberg Terminal), not spacious (not a marketing site) -- **Scale:** 2xs(2px) xs(4px) sm(8px) md(16px) lg(24px) xl(32px) 2xl(48px) 3xl(64px) +## Typography + +- **Display/Hero:** Satoshi (Black 900 / Bold 700) — geometric with warmth, distinctive letterforms (the lowercase 'a' and 'g'). Not Inter, not Geist. Loaded from Fontshare CDN. +- **Body:** DM Sans (Regular 400 / Medium 500 / Semibold 600) — clean, readable, slightly friendlier than geometric display. Loaded from Google Fonts. +- **UI/Labels:** DM Sans (same as body) +- **Data/Tables:** JetBrains Mono (Regular 400 / Medium 500) — the personality font. Supports tabular-nums. Monospace should be prominent, not hidden in code blocks. Loaded from Google Fonts. +- **Code:** JetBrains Mono +- **Loading:** Google Fonts for DM Sans + JetBrains Mono, Fontshare for Satoshi. Use `display=swap`. +- **Scale:** + - Hero: 72px / clamp(40px, 6vw, 72px) + - H1: 48px + - H2: 32px + - H3: 24px + - H4: 18px + - Body: 16px + - Small: 14px + - Caption: 13px + - Micro: 12px + - Nano: 11px (JetBrains Mono labels) ## Layout + - **Approach:** Grid-disciplined for dashboard, editorial hero for landing page - **Grid:** 12 columns at lg+, 1 column at mobile - **Max content width:** 1200px (6xl) @@ -65,13 +109,20 @@ - Badges/pills: full (9999px) - Skill bars: sm (4px) +### Spacing +- **Base unit:** 4px +- **Density:** Comfortable — not cramped (not Bloomberg Terminal), not spacious (not a marketing site) +- **Scale:** 2xs(2px) xs(4px) sm(8px) md(16px) lg(24px) xl(32px) 2xl(48px) 3xl(64px) + ## Motion + - **Approach:** Minimal-functional — only transitions that aid comprehension. The dashboard's live feed IS the motion. - **Easing:** enter(ease-out / cubic-bezier(0.16,1,0.3,1)) exit(ease-in) move(ease-in-out) - **Duration:** micro(50-100ms) short(150ms) medium(250ms) long(400ms) - **Animated elements:** live feed dot pulse (2s infinite), skill bar fill (600ms ease-out), hover states (150ms) ## Grain Texture + Apply a subtle noise overlay to the entire page for materiality: - Dark mode: opacity 0.03 - Light mode: opacity 0.02 @@ -79,8 +130,10 @@ Apply a subtle noise overlay to the entire page for materiality: - pointer-events: none, position: fixed, z-index: 9999 ## Decisions Log + | Date | Decision | Rationale | |------|----------|-----------| | 2026-03-21 | Initial design system | Created by /design-consultation. Industrial aesthetic, warm amber accent, Satoshi + DM Sans + JetBrains Mono. | | 2026-03-21 | Light mode amber-600 | amber-500 too bright/washed against white; amber-700 too brown/umber. amber-600 is the sweet spot. | | 2026-03-21 | Grain texture | Adds materiality to flat dark surfaces. Prevents the "generic SaaS template" sameness. | +| 2026-09-08 | Open DESIGN.md format | Converted with `gstack-design-md convert`: tokens in front matter, canonical sections, Motion and Decisions Log kept as extras. DM Sans stays the body face: it sits on the overused-as-display list, and body/UI use on an Operate surface is the allowed exception under the role-scoped rule. | diff --git a/test/design-md.test.ts b/test/design-md.test.ts index c195b3da7..5e4e25e7e 100644 --- a/test/design-md.test.ts +++ b/test/design-md.test.ts @@ -21,7 +21,8 @@ import { updateDesignMd, readDesignConstraints } from '../design/src/memory'; const ROOT = path.join(import.meta.dir, '..'); const BIN = path.join(ROOT, 'bin', 'gstack-design-md.ts'); -const LEGACY = fs.readFileSync(path.join(ROOT, 'DESIGN.md'), 'utf-8'); +// gstack's own DESIGN.md is now in the open format; its pre-conversion form is the legacy fixture. +const LEGACY = fs.readFileSync(path.join(ROOT, 'test', 'fixtures', 'design-md-legacy.md'), 'utf-8'); const SPEC = `--- # gstack: design-md-format=spec @@ -80,10 +81,14 @@ describe('parse + detect', () => { expect(detectFormat(doc)).toEqual({ format: 'spec' }); }); - test("gstack's own DESIGN.md is legacy; a fresh file is unknown; nothing is missing", () => { + test("the legacy fixture is legacy, gstack's own DESIGN.md is spec; a fresh file is unknown; nothing is missing", () => { const doc = parseDesignMd(LEGACY); expect(isLegacyGstackFormat(doc)).toBe(true); expect(detectFormat(doc)).toEqual({ format: 'legacy' }); + const own = parseDesignMd(fs.readFileSync(path.join(ROOT, 'DESIGN.md'), 'utf-8')); + expect(detectFormat(own)).toEqual({ format: 'spec' }); + expect(own.marker).toBe('spec'); + expect(tokensFlat(own.frontmatter).errors).toEqual([]); expect(detectFormat(parseDesignMd('# Hello\n\nJust prose.\n')).format).toBe('unknown'); expect(detectFormat(null)).toEqual({ format: 'missing' }); }); @@ -191,7 +196,7 @@ describe('tokens', () => { }); }); -describe("convertLegacy on gstack's own DESIGN.md", () => { +describe('convertLegacy on the legacy fixture (gstack\'s pre-conversion DESIGN.md)', () => { const converted = convertLegacy(parseDesignMd(LEGACY)); const out = renderDesignMd(converted, { emitFrontmatter: true }); diff --git a/test/fixtures/design-md-legacy.md b/test/fixtures/design-md-legacy.md new file mode 100644 index 000000000..d1f3ce3db --- /dev/null +++ b/test/fixtures/design-md-legacy.md @@ -0,0 +1,86 @@ +# Design System — gstack + +## Product Context +- **What this is:** Community website for gstack — a CLI tool that turns Claude Code into a virtual engineering team +- **Who it's for:** Developers discovering gstack, existing community members +- **Space/industry:** Developer tools (peers: Linear, Raycast, Warp, Zed) +- **Project type:** Community dashboard + marketing site + +## Aesthetic Direction +- **Direction:** Industrial/Utilitarian — function-first, data-dense, monospace as personality font +- **Decoration level:** Intentional — subtle noise/grain texture on surfaces for materiality +- **Mood:** Serious tool built by someone who cares about craft. Warm, not cold. The CLI heritage IS the brand. +- **Reference sites:** formulae.brew.sh (competitor, but ours is live and interactive), Linear (dark + restrained), Warp (warm accents) + +## Typography +- **Display/Hero:** Satoshi (Black 900 / Bold 700) — geometric with warmth, distinctive letterforms (the lowercase 'a' and 'g'). Not Inter, not Geist. Loaded from Fontshare CDN. +- **Body:** DM Sans (Regular 400 / Medium 500 / Semibold 600) — clean, readable, slightly friendlier than geometric display. Loaded from Google Fonts. +- **UI/Labels:** DM Sans (same as body) +- **Data/Tables:** JetBrains Mono (Regular 400 / Medium 500) — the personality font. Supports tabular-nums. Monospace should be prominent, not hidden in code blocks. Loaded from Google Fonts. +- **Code:** JetBrains Mono +- **Loading:** Google Fonts for DM Sans + JetBrains Mono, Fontshare for Satoshi. Use `display=swap`. +- **Scale:** + - Hero: 72px / clamp(40px, 6vw, 72px) + - H1: 48px + - H2: 32px + - H3: 24px + - H4: 18px + - Body: 16px + - Small: 14px + - Caption: 13px + - Micro: 12px + - Nano: 11px (JetBrains Mono labels) + +## Color +- **Approach:** Restrained — amber accent is rare and meaningful. Dashboard data gets the color; chrome stays neutral. +- **Primary (dark mode):** amber-500 #F59E0B — warm, energetic, reads as "terminal cursor" +- **Primary (light mode):** amber-600 #D97706 — darker for contrast against white backgrounds +- **Primary text accent (dark mode):** amber-400 #FBBF24 +- **Primary text accent (light mode):** amber-700 #B45309 +- **Neutrals:** Cool zinc grays + - zinc-50: #FAFAFA (lightest) + - zinc-400: #A1A1AA + - zinc-600: #52525B + - zinc-800: #27272A + - Surface (dark): #141414 + - Base (dark): #0C0C0C + - Surface (light): #FFFFFF + - Base (light): #FAFAF9 +- **Semantic:** success #22C55E, warning #F59E0B, error #EF4444, info #3B82F6 +- **Dark mode:** Default. Near-black base (#0C0C0C), surface cards at #141414, borders at #262626. +- **Light mode:** Warm stone base (#FAFAF9), white surface cards, stone borders (#E7E5E4). Amber accent shifts to amber-600 for contrast. + +## Spacing +- **Base unit:** 4px +- **Density:** Comfortable — not cramped (not Bloomberg Terminal), not spacious (not a marketing site) +- **Scale:** 2xs(2px) xs(4px) sm(8px) md(16px) lg(24px) xl(32px) 2xl(48px) 3xl(64px) + +## Layout +- **Approach:** Grid-disciplined for dashboard, editorial hero for landing page +- **Grid:** 12 columns at lg+, 1 column at mobile +- **Max content width:** 1200px (6xl) +- **Border radius:** sm:4px, md:8px, lg:12px, full:9999px + - Cards/panels: lg (12px) + - Buttons/inputs: md (8px) + - Badges/pills: full (9999px) + - Skill bars: sm (4px) + +## Motion +- **Approach:** Minimal-functional — only transitions that aid comprehension. The dashboard's live feed IS the motion. +- **Easing:** enter(ease-out / cubic-bezier(0.16,1,0.3,1)) exit(ease-in) move(ease-in-out) +- **Duration:** micro(50-100ms) short(150ms) medium(250ms) long(400ms) +- **Animated elements:** live feed dot pulse (2s infinite), skill bar fill (600ms ease-out), hover states (150ms) + +## Grain Texture +Apply a subtle noise overlay to the entire page for materiality: +- Dark mode: opacity 0.03 +- Light mode: opacity 0.02 +- Use SVG feTurbulence filter as a CSS background-image on body::after +- pointer-events: none, position: fixed, z-index: 9999 + +## Decisions Log +| Date | Decision | Rationale | +|------|----------|-----------| +| 2026-03-21 | Initial design system | Created by /design-consultation. Industrial aesthetic, warm amber accent, Satoshi + DM Sans + JetBrains Mono. | +| 2026-03-21 | Light mode amber-600 | amber-500 too bright/washed against white; amber-700 too brown/umber. amber-600 is the sweet spot. | +| 2026-03-21 | Grain texture | Adds materiality to flat dark surfaces. Prevents the "generic SaaS template" sameness. |