qualia-framework 4.3.0 → 4.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/CLAUDE.md +13 -1
  2. package/README.md +16 -13
  3. package/agents/builder.md +12 -20
  4. package/agents/plan-checker.md +18 -0
  5. package/agents/planner.md +9 -0
  6. package/agents/verifier.md +62 -0
  7. package/bin/agent-runs.js +233 -0
  8. package/bin/cli.js +225 -21
  9. package/bin/install.js +25 -5
  10. package/bin/plan-contract.js +220 -0
  11. package/bin/slop-detect.mjs +357 -0
  12. package/bin/state.js +199 -10
  13. package/docs/agent-runs.md +273 -0
  14. package/docs/erp-contract.md +5 -0
  15. package/docs/plan-contract.md +321 -0
  16. package/hooks/auto-update.js +3 -7
  17. package/hooks/pre-compact.js +22 -11
  18. package/hooks/pre-deploy-gate.js +16 -2
  19. package/hooks/pre-push.js +22 -2
  20. package/hooks/stop-session-log.js +1 -1
  21. package/package.json +8 -2
  22. package/rules/design-brand.md +110 -0
  23. package/rules/design-laws.md +144 -0
  24. package/rules/design-product.md +110 -0
  25. package/rules/design-rubric.md +153 -0
  26. package/skills/qualia-build/SKILL.md +5 -5
  27. package/skills/qualia-flush/SKILL.md +1 -1
  28. package/skills/qualia-new/SKILL.md +40 -3
  29. package/skills/qualia-polish/SKILL.md +180 -136
  30. package/skills/qualia-quick/SKILL.md +1 -1
  31. package/skills/qualia-report/SKILL.md +25 -5
  32. package/skills/qualia-ship/SKILL.md +12 -10
  33. package/skills/zoho-workflow/SKILL.md +64 -0
  34. package/templates/DESIGN.md +229 -435
  35. package/templates/PRODUCT.md +95 -0
  36. package/templates/help.html +13 -7
  37. package/tests/bin.test.sh +6 -3
  38. package/tests/hooks.test.sh +9 -20
  39. package/tests/lib.test.sh +217 -0
  40. package/tests/runner.js +96 -75
  41. package/tests/state.test.sh +4 -3
  42. package/skills/qualia-design/SKILL.md +0 -169
@@ -37,6 +37,23 @@ function readCompactConfig() {
37
37
  }
38
38
  }
39
39
 
40
+ function git(args, opts = {}) {
41
+ return spawnSync("git", args, {
42
+ encoding: "utf8",
43
+ timeout: 3000,
44
+ shell: process.platform === "win32",
45
+ ...opts,
46
+ });
47
+ }
48
+
49
+ function stateHasPendingChanges() {
50
+ const diff = git(["diff", "--name-only", "--", STATE_FILE]);
51
+ if ((diff.stdout || "").split(/\r?\n/).includes(STATE_FILE)) return true;
52
+
53
+ const untracked = git(["ls-files", "--others", "--exclude-standard", "--", STATE_FILE]);
54
+ return (untracked.stdout || "").split(/\r?\n/).includes(STATE_FILE);
55
+ }
56
+
40
57
  let _commitStatus = null;
41
58
  let _commitReason = "no-state-file";
42
59
  let _commitFlags = null;
@@ -45,17 +62,9 @@ try {
45
62
  if (fs.existsSync(STATE_FILE)) {
46
63
  console.log("QUALIA: Saving state before compaction...");
47
64
  _commitReason = "state-clean";
48
- // Check if STATE.md has uncommitted changes
49
- const diff = spawnSync("git", ["diff", "--name-only", STATE_FILE], {
50
- encoding: "utf8",
51
- timeout: 3000,
52
- shell: process.platform === "win32",
53
- });
54
- if ((diff.stdout || "").includes("STATE.md")) {
55
- const addRes = spawnSync("git", ["add", STATE_FILE], {
56
- timeout: 3000,
57
- shell: process.platform === "win32",
58
- });
65
+ // Check if STATE.md has tracked or untracked changes.
66
+ if (stateHasPendingChanges()) {
67
+ const addRes = git(["add", STATE_FILE]);
59
68
  const cfg = readCompactConfig();
60
69
  const commitArgs = ["commit"];
61
70
  if (!cfg.respect_user_hooks) commitArgs.push("--no-verify");
@@ -76,6 +85,8 @@ try {
76
85
  _commitReason = addRes.status === 0 && commitRes.status === 0
77
86
  ? "committed"
78
87
  : "commit-failed";
88
+ } else {
89
+ _commitReason = "state-clean";
79
90
  }
80
91
  }
81
92
  } catch {
@@ -31,7 +31,8 @@ function _trace(hookName, result, extra) {
31
31
 
32
32
  function runGate(label, cmd, args, { required = true } = {}) {
33
33
  const r = spawnSync(cmd, args, {
34
- stdio: "ignore",
34
+ stdio: ["ignore", "pipe", "pipe"],
35
+ encoding: "utf8",
35
36
  timeout: 180000,
36
37
  shell: process.platform === "win32",
37
38
  });
@@ -41,7 +42,20 @@ function runGate(label, cmd, args, { required = true } = {}) {
41
42
  }
42
43
  if (required) {
43
44
  console.error(`BLOCKED: ${label} errors. Fix before deploying.`);
44
- _trace("pre-deploy-gate", "block", { gate: label });
45
+ const output = `${r.stdout || ""}${r.stderr || ""}`.trim();
46
+ if (output) {
47
+ const lines = output.split(/\r?\n/).filter(Boolean).slice(-20);
48
+ console.error(`Last ${lines.length} output line${lines.length === 1 ? "" : "s"}:`);
49
+ for (const line of lines) console.error(` ${line}`);
50
+ } else if (r.error) {
51
+ console.error(` ${r.error.message}`);
52
+ }
53
+ _trace("pre-deploy-gate", "block", {
54
+ gate: label,
55
+ status: r.status,
56
+ signal: r.signal || undefined,
57
+ error: r.error ? r.error.message : undefined,
58
+ });
45
59
  process.exit(2);
46
60
  }
47
61
  return false;
package/hooks/pre-push.js CHANGED
@@ -102,6 +102,15 @@ function commitStamp() {
102
102
  return { committed: true, sha: lastCommit, ts: now };
103
103
  }
104
104
 
105
+ function shouldBlock(result) {
106
+ const hardSkips = new Set([
107
+ "tracking-unreadable",
108
+ "git-add-failed",
109
+ "git-commit-failed",
110
+ ]);
111
+ return result && hardSkips.has(result.skipped);
112
+ }
113
+
105
114
  function _trace(result, extra) {
106
115
  try {
107
116
  const traceDir = path.join(HOME, ".claude", ".qualia-traces");
@@ -120,10 +129,21 @@ function _trace(result, extra) {
120
129
 
121
130
  try {
122
131
  const result = commitStamp();
132
+ if (shouldBlock(result)) {
133
+ const detail = result.error ? ` ${String(result.error).slice(0, 500)}` : "";
134
+ const msg = `BLOCKED: tracking.json sync failed (${result.skipped}). Fix before pushing.${detail}`;
135
+ console.error(msg);
136
+ console.log(msg);
137
+ _trace("block", result);
138
+ process.exit(2);
139
+ }
123
140
  _trace("allow", result);
124
141
  } catch (err) {
125
- // Never block a push log and exit clean.
126
- _trace("allow", { error: err.message });
142
+ const msg = `BLOCKED: tracking.json sync failed (${err.message}). Fix before pushing.`;
143
+ console.error(msg);
144
+ console.log(msg);
145
+ _trace("block", { error: err.message });
146
+ process.exit(2);
127
147
  }
128
148
 
129
149
  process.exit(0);
@@ -103,7 +103,7 @@ try {
103
103
  const tracking = readJson(path.join(repoRoot, ".planning", "tracking.json"));
104
104
  if (tracking) {
105
105
  const p = tracking.phase || 0;
106
- const pt = tracking.phase_total || 0;
106
+ const pt = tracking.total_phases || tracking.phase_total || 0;
107
107
  if (pt > 0) phase = `${p}/${pt}`;
108
108
  const td = tracking.tasks_done || 0;
109
109
  const tt = tracking.tasks_total || 0;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "qualia-framework",
3
- "version": "4.3.0",
3
+ "version": "4.5.0",
4
4
  "description": "Claude Code workflow framework by Qualia Solutions. Plan, build, verify, ship.",
5
5
  "bin": {
6
6
  "qualia-framework": "./bin/cli.js"
@@ -23,7 +23,13 @@
23
23
  },
24
24
  "homepage": "https://github.com/Qualiasolutions/qualia-framework#readme",
25
25
  "scripts": {
26
- "test": "node --test tests/runner.js"
26
+ "test": "npm run test:shell",
27
+ "test:state": "bash tests/state.test.sh",
28
+ "test:hooks": "bash tests/hooks.test.sh",
29
+ "test:bin": "bash tests/bin.test.sh",
30
+ "test:lib": "bash tests/lib.test.sh",
31
+ "test:statusline": "bash tests/statusline.test.sh",
32
+ "test:shell": "bash tests/statusline.test.sh && bash tests/state.test.sh && bash tests/hooks.test.sh && bash tests/bin.test.sh && bash tests/lib.test.sh"
27
33
  },
28
34
  "files": [
29
35
  "bin/",
@@ -0,0 +1,110 @@
1
+ # Design — Brand register
2
+
3
+ **When this register applies:** marketing pages, landing pages, campaign sites, portfolios, brand microsites, anything where the design IS the product.
4
+
5
+ **The bar:** distinctiveness. Memorable beats safe. If a visitor can't remember what your site looked like an hour later, you failed.
6
+
7
+ This register inherits all of `design-laws.md`. The rules below add what changes when distinctiveness, not familiarity, is the goal.
8
+
9
+ ## The brand brief (what the agent commits to first)
10
+
11
+ Before any code, the agent writes a 4-line brief and waits for user confirmation:
12
+
13
+ ```
14
+ Aesthetic direction: [editorial · brutalist · luxury · maximalist · retro-futuristic · organic · terminal-native · sci-fi · pastoral · industrial · ...]
15
+ Color strategy: [Restrained · Committed · Full palette · Drenched]
16
+ Scene sentence: [single concrete sentence — who, where, ambient light, mood]
17
+ Differentiation: [what someone will remember 24 hours later]
18
+ ```
19
+
20
+ This is mandatory. Skipping it produces generic output that ignores the brand.
21
+
22
+ ## Reflex-reject aesthetic lanes
23
+
24
+ The currently-saturated AI-design families. Reject by default unless the user explicitly asks for one and you confirmed they understand it's a saturated lane.
25
+
26
+ | Lane | Tells | Reject because |
27
+ |---|---|---|
28
+ | **Generic SaaS-cream** | Off-white bg, soft shadows, rounded cards, slate text, one-color accent | Notion clone aesthetic. Every AI tool builds this first |
29
+ | **Vercel-terminal-native** | Dark + monospace + green/blue accents + grain | The 2024-2025 v0 aesthetic. Saturated |
30
+ | **Crypto-neon** | Pure black + electric accent + glow + grid | Web3 cliché |
31
+ | **Healthcare-clinical** | White + teal + rounded sans + soft icons | Every health app |
32
+ | **Fintech-navy-gold** | Navy + gold accent + serif headings | Every bank, every wealth tool |
33
+ | **Sleek-corporate** | Inter + blue + gradient mesh + isometric illustrations | Every B2B SaaS landing 2020-2024 |
34
+
35
+ If the project lands in one of these by default, the slop test failed at second-order. Rework.
36
+
37
+ ## Brand register — additional laws
38
+
39
+ ### Typography
40
+
41
+ - Pick a **distinctive display face**. Editorial serifs (Fraunces, Reckless, Ogg, Editorial New, Mazius). Geometric grotesques (Migra, Söhne, Gambarino). Variable display fonts (Authentic Sans, Departure Mono).
42
+ - Pair the display with a **refined body**. JetBrains Mono, Geist, Inter Display (NOT Inter), Söhne, Untitled Sans, Mona Sans, Switzer.
43
+ - **Banned display fonts:** Inter, Roboto, Arial, system-ui, Space Grotesk, Montserrat, Poppins, Lato, Open Sans. These are the AI defaults — and the worst signal of "designer didn't choose this."
44
+ - **Variable fonts encouraged.** A single variable font with axis-driven weight/optical-size is more interesting than three static cuts.
45
+
46
+ ### Color
47
+
48
+ - Brand register defaults to **Committed** color strategy. One saturated color carries identity.
49
+ - **Drenched** is a strong choice for hero sections. Don't be afraid.
50
+ - Accent should be **sharp** — not a desaturated pastel-of-the-brand. The accent is the loudest element on the page.
51
+
52
+ ### Layout
53
+
54
+ - **Asymmetry > symmetry.** Real design has tension. Centered-everything is the AI default.
55
+ - **Diagonal flow, overlap, grid-breaking elements** when they serve the design.
56
+ - **Scale contrast.** Hero text at clamp(4rem, 9vw, 8rem) next to body at 1rem creates drama. Hero at 2.5rem next to body at 1rem is timid.
57
+ - **Negative space is content.** Generous whitespace > dense layouts for marketing.
58
+
59
+ ### Motion
60
+
61
+ - One **signature motion** that gives the site personality:
62
+ - Parallax scroll-triggered reveal
63
+ - Magnetic buttons
64
+ - Cursor-following element
65
+ - Letter-by-letter text reveal on hero
66
+ - Scroll-driven scrubbed scene
67
+ - Morphing shapes between sections
68
+ - Subtle elsewhere. Restraint everywhere except the signature.
69
+ - Use the Web Animations API or Framer Motion for orchestrated sequences. CSS keyframes for one-offs.
70
+
71
+ ### Backgrounds and visual details
72
+
73
+ This is where Brand earns its register. Allowed and encouraged:
74
+
75
+ - Gradient meshes (CSS or SVG)
76
+ - Noise textures (subtle, 2-5% opacity)
77
+ - Geometric patterns (SVG, not images)
78
+ - Layered transparencies
79
+ - Dramatic shadows (with brand-tinted color, not gray)
80
+ - Decorative borders (sparingly)
81
+ - Custom cursors (page-specific, not site-wide)
82
+ - Grain overlays on images
83
+ - Scroll-progress indicators (interesting ones, not generic top bars)
84
+
85
+ Don't use all of these on one page. Pick 1-2 that serve the brief.
86
+
87
+ ## Brand-specific anti-patterns
88
+
89
+ In addition to `design-laws.md` absolute bans:
90
+
91
+ - **Hero pattern: text-center + gradient bg + 2 CTAs + screenshot below.** The single most overused brand pattern of 2020-2025.
92
+ - **Three-column feature grid in section 2.** The default AI brand layout.
93
+ - **"Trusted by" logo strip in faded gray.** Lazy social proof. If the logos matter, design with them. If they don't, omit.
94
+ - **Parallax scroll on every section.** Heavy-handed. Use it for the signature, nowhere else.
95
+ - **Animated dots / pulsing orbs / blinking status indicators.** Decorative noise.
96
+ - **Generic "Get Started" / "Learn More" CTA pair.** Always specify the action.
97
+ - **Stock photography of diverse smiling people in offices.** Use illustration, abstract imagery, product screenshots, or no imagery at all.
98
+
99
+ ## Brand register success criteria
100
+
101
+ If you can answer YES to all of these, the brand register passed:
102
+
103
+ - [ ] A designer would call this **distinctive** — not "polished" but **specific**
104
+ - [ ] The aesthetic direction is identifiable in 3 seconds
105
+ - [ ] The color strategy is one of the four (and reads that way)
106
+ - [ ] The scene sentence and the page agree
107
+ - [ ] One signature motion, executed well
108
+ - [ ] No reflex-reject lane (first or second order)
109
+ - [ ] All anti-references avoided
110
+ - [ ] Implementation matches vision (effort proportional to ambition)
@@ -0,0 +1,144 @@
1
+ # Design Laws
2
+
3
+ The non-negotiable rules every Qualia frontend honors. Both registers (Brand and Product) inherit from this file. Skip nothing.
4
+
5
+ These laws exist because AI-generated UI has identifiable defaults — Inter font, purple gradients, identical card grids, gray-on-gray, modal-as-first-thought — and shipping the defaults is shipping AI slop. Each rule below has a specific failure mode it prevents.
6
+
7
+ ## 1. Color: OKLCH only
8
+
9
+ Use the OKLCH color space for every color in the codebase. RGB / hex / HSL are translated values, not source values.
10
+
11
+ ```css
12
+ /* yes */
13
+ --bg: oklch(0.16 0.012 220);
14
+ --accent: oklch(0.78 0.14 196);
15
+
16
+ /* no */
17
+ --bg: #1a1d22;
18
+ --accent: #00ced1;
19
+ ```
20
+
21
+ **Why:** OKLCH is perceptually uniform. Equal lightness numbers look equally light to the eye, regardless of hue. Hex doesn't give you that — `#777777` and `#7777ff` are nominally equal lightness but the blue reads darker.
22
+
23
+ **Reduce chroma at the extremes.** As lightness approaches 0 or 100, high chroma reads garish. Cap chroma at 0.04 below L=0.20 and above L=0.92.
24
+
25
+ **Never use `#000` or `#fff`.** Tint every neutral toward the brand hue with chroma 0.005–0.01. Pure black/white reads as untuned and generic — the dead giveaway of "designer didn't pick this color, the framework did."
26
+
27
+ ```css
28
+ /* tinted neutrals */
29
+ --text: oklch(0.94 0.006 220); /* 220 is the brand hue */
30
+ --bg: oklch(0.16 0.012 220);
31
+ --muted: oklch(0.62 0.010 220);
32
+ ```
33
+
34
+ ## 2. Color strategy: pick one, commit to it
35
+
36
+ Before picking colors, pick a strategy. Four steps on a commitment axis:
37
+
38
+ | Strategy | Rule | When |
39
+ |---|---|---|
40
+ | **Restrained** | Tinted neutrals + one accent at ≤10% surface coverage | Product default; brand minimalism |
41
+ | **Committed** | One saturated color carries 30–60% of the surface | Brand default for identity-driven pages |
42
+ | **Full palette** | 3–4 named roles, each used deliberately | Brand campaigns; product data viz |
43
+ | **Drenched** | The surface IS the color | Brand heroes; campaign pages |
44
+
45
+ The "≤10% accent" rule is **Restrained only**. Committed / Full palette / Drenched exceed it on purpose. Don't collapse every design to Restrained by reflex.
46
+
47
+ ## 3. Theme: write a scene sentence
48
+
49
+ Dark vs light is never a default. Not dark "because tools look cool dark." Not light "to be safe." Before choosing, write **one sentence of physical scene**: who uses this, where, under what ambient light, in what mood.
50
+
51
+ | Vague (no answer) | Concrete (forces an answer) |
52
+ |---|---|
53
+ | "Observability dashboard" | "SRE glancing at incident severity on a 27-inch monitor at 2am in a dim room" |
54
+ | "Customer support tool" | "Agent at a coffee-lit desk juggling 4 chats while a coworker asks a question" |
55
+ | "Marketing site" | "Founder showing the page to an investor on an iPad in a sunlit lobby" |
56
+
57
+ Run the sentence, not the category. If the sentence doesn't force an answer, add detail until it does.
58
+
59
+ ## 4. Typography
60
+
61
+ - Body line length capped at 65–75ch
62
+ - Hierarchy through scale + weight contrast (≥1.25 ratio between adjacent steps)
63
+ - Avoid flat scales — if h1 is 32px and h2 is 28px, the hierarchy is invisible
64
+ - Letter-spacing as a semantic signal:
65
+ - Display headlines: tight (-0.02em to -0.04em)
66
+ - Body: neutral (0)
67
+ - Labels and badges: open (+0.04em to +0.08em)
68
+ - Category tags: wide (+0.08em to +0.14em)
69
+
70
+ ## 5. Layout
71
+
72
+ - **Vary spacing for rhythm.** Same padding everywhere is monotony. Tight within groups, generous between sections.
73
+ - **Cards are the lazy answer.** Use them only when they're truly the best affordance. Nested cards are always wrong.
74
+ - **Container depth max 2.** Card → content. Not card → panel → pill → content.
75
+ - **Don't wrap everything in a container.** Most things don't need one.
76
+ - **Full-width with fluid padding.** No hardcoded `max-width: 1200px` or `max-w-7xl`. Use `clamp(1rem, 5vw, 4rem)` for horizontal padding.
77
+
78
+ ## 6. Motion
79
+
80
+ - Don't animate CSS layout properties (`width`, `height`, `top`, `left`, `margin`, `padding`). Animate `transform` and `opacity`.
81
+ - Ease out with exponential curves: `cubic-bezier(0.22, 1, 0.36, 1)` (out-quart) or `cubic-bezier(0.16, 1, 0.3, 1)` (out-expo). No bounce. No elastic.
82
+ - One signature motion per page > scattered micro-interactions
83
+ - Always respect `prefers-reduced-motion: reduce`
84
+
85
+ ## 7. Copy
86
+
87
+ - Every word earns its place
88
+ - No restated headings
89
+ - No intros that repeat the title
90
+ - **No em dashes.** Use commas, colons, semicolons, periods, or parentheses. Also not `--`.
91
+ - CTAs name the action. "Download invoice" not "Get Started". "Continue setup" not "Learn More".
92
+
93
+ ## 8. Absolute bans (match-and-refuse)
94
+
95
+ If you're about to write any of these, rewrite the element with different structure. No exceptions.
96
+
97
+ | Banned pattern | Why | Rewrite with |
98
+ |---|---|---|
99
+ | **Side-stripe borders** — `border-left: 4px` colored as accent on cards / list items / callouts | Decorative noise, never semantic | Full borders, background tints, leading numbers/icons, or nothing |
100
+ | **Gradient text** — `background-clip: text` with a gradient | Decorative, never meaningful | Single solid color. Emphasis via weight or size |
101
+ | **Glassmorphism by default** — blur + glass cards used everywhere | Trendy slop from 2023-2024 | Rare and purposeful, or nothing |
102
+ | **Hero-metric template** — big number, small label, three supporting stats, gradient accent | SaaS cliché | Vary layout per metric. Don't grid them |
103
+ | **Identical card grids** — same-sized cards with icon + heading + text repeated 3+ times | The default AI hero pattern | Vary card sizes, layouts, content shapes |
104
+ | **Modal as first thought** — every action goes through a modal | Lazy interaction design | Inline expansion, progressive disclosure, dedicated route |
105
+
106
+ ## 9. The AI slop test (run at two altitudes)
107
+
108
+ If someone could look at this interface and say "AI made that" without doubt, it failed.
109
+
110
+ **First-order check.** If someone could guess the theme + palette from the category alone, the first reflex won:
111
+ - Observability → dark blue
112
+ - Healthcare → white + teal
113
+ - Finance → navy + gold
114
+ - Crypto → neon on black
115
+
116
+ If yes, rework the scene sentence and color strategy until the answer isn't obvious from the domain.
117
+
118
+ **Second-order check.** If someone could guess the aesthetic family from category-plus-anti-references, the trap one tier deeper won:
119
+ - AI workflow tool that's not SaaS-cream → editorial-typographic
120
+ - Fintech that's not navy-and-gold → terminal-native dark mode
121
+ - Health app that's not white-and-teal → soft-pastel
122
+
123
+ The first reflex was avoided; the second wasn't. Rework until both answers are not obvious. The Brand register's reflex-reject aesthetic lanes catch the currently-saturated families.
124
+
125
+ ## 10. Single-icon-family rule
126
+
127
+ Pick one icon family per project and enforce it. Lucide OR Heroicons OR Phosphor OR Radix Icons. Never mixed. Mixing icon families is the visual equivalent of mixing fonts.
128
+
129
+ ## 11. Anti-references are mandatory
130
+
131
+ Every PRODUCT.md must list 3–5 sites the project should NOT look like. Anti-references are more useful than positive references because they pin down what the design is reacting against.
132
+
133
+ ```
134
+ Anti-references:
135
+ - generic SaaS-cream (Notion clones)
136
+ - terminal-nostalgia (vercel/v0 dark)
137
+ - corporate-navy fintech (most banks)
138
+ ```
139
+
140
+ ## 12. The implementation must match the vision
141
+
142
+ Maximalist designs need elaborate code. Minimalist designs need restraint, precision, and careful attention to spacing and typography. The failure mode is mismatch: elaborate animation on a "clean minimal" claim, or flat execution of an ambitious concept.
143
+
144
+ If you wrote 200 lines of CSS to render a "minimal" landing page, the page is not minimal. If your "bold and maximalist" page uses 4 colors and `text-center`, it's not bold.
@@ -0,0 +1,110 @@
1
+ # Design — Product register
2
+
3
+ **When this register applies:** app UI, admin consoles, dashboards, internal tools, settings pages, anything where the design SERVES the product.
4
+
5
+ **The bar:** earned familiarity. Fluent users of Linear, Figma, Notion, Stripe, Raycast should trust the interface on first glance. Distinctiveness is allowed but never at the cost of usability.
6
+
7
+ This register inherits all of `design-laws.md`. The rules below add what changes when familiarity, not distinctiveness, is the goal.
8
+
9
+ ## The product brief (what the agent commits to first)
10
+
11
+ Before any code, the agent writes a 4-line brief and waits for user confirmation:
12
+
13
+ ```
14
+ Reference apps: [Linear · Stripe · Notion · Figma · Raycast · Vercel · Plaid Dashboard · Pylon · ...]
15
+ Color strategy: [Restrained — almost always]
16
+ Scene sentence: [physical scene — focus on context of use, not aesthetic mood]
17
+ Density: [comfortable · standard · compact · ultra-compact]
18
+ ```
19
+
20
+ Reference apps anchor the visual language. Pick 2-3 that share the user mental model and the density.
21
+
22
+ ## Density target
23
+
24
+ Product UIs have to choose density. State it explicitly.
25
+
26
+ | Density | Examples | Use when |
27
+ |---|---|---|
28
+ | **Comfortable** | Notion, Linear (default), Mailchimp | Knowledge work, low-frequency |
29
+ | **Standard** | Stripe Dashboard, Vercel | Mainstream SaaS |
30
+ | **Compact** | Linear (compact mode), Figma left rail | Power users, workflow tools |
31
+ | **Ultra-compact** | Bloomberg Terminal, Trader UIs | Information-dense, professional users |
32
+
33
+ Higher density = smaller font sizes (down to 12px), tighter padding, more rows per viewport. Don't pretend the app is comfortable when it's compact, or vice versa. Match the user's actual workflow.
34
+
35
+ ## Product register — additional laws
36
+
37
+ ### Typography
38
+
39
+ - **One typeface family is fine** for product. Variable fonts shine here (Inter Display, Geist, Switzer, Mona Sans, Söhne, Untitled Sans).
40
+ - Banned (per laws): Inter (regular cut), Roboto, Arial, system-ui, Space Grotesk
41
+ - Body: 13-15px depending on density (comfortable=15, standard=14, compact=13, ultra-compact=12)
42
+ - Hierarchy through weight (400 body, 500 labels, 600 headings) more than size
43
+ - Tabular numerals for any numeric data: `font-feature-settings: "tnum" 1`
44
+ - Truncate long strings with ellipsis + tooltip — never let layout collapse on a long username
45
+
46
+ ### Color
47
+
48
+ - **Restrained is the default.** Tinted neutrals + one accent.
49
+ - **Semantic colors required:** success (green) / warning (amber) / error (red) / info (blue). Always paired with non-color signal (icon, label, pattern).
50
+ - Status indicators must work for color-blind users. Run the design through a deuteranopia simulator.
51
+ - Background depth: 3 surface levels max (`bg`, `surface`, `elevated`). Subtle differences (oklch L delta of 0.03-0.05).
52
+
53
+ ### Layout
54
+
55
+ - **Density first, aesthetics second.** A beautiful UI a user can't navigate quickly is a failed product UI.
56
+ - **Predictable layout.** Sidebar left, main center, optional right rail. Don't reinvent navigation patterns.
57
+ - **Sticky headers** for long-scroll views (tables, lists)
58
+ - **Keyboard-first.** Every action accessible via keyboard. Power users will memorize shortcuts; expose them.
59
+ - **Fitts's Law.** Frequently-used controls go to screen edges. Action buttons in tight clusters.
60
+
61
+ ### Components
62
+
63
+ | Pattern | Product register rule |
64
+ |---|---|
65
+ | **Buttons** | 3 variants max: primary / secondary / ghost. Destructive variant where needed. No "outline + filled + ghost + soft + subtle" proliferation. |
66
+ | **Inputs** | Always have visible labels (not placeholder-only). Validation inline, on blur. Error messages specific and actionable. |
67
+ | **Tables** | Tabular numerals. Right-align numbers. Sort indicators on hover, persistent on active. Sticky headers. |
68
+ | **Empty states** | Always include: icon, single sentence of context, primary action |
69
+ | **Loading** | Skeleton (not spinner) for known-shape content. Spinner only for unknown duration. |
70
+ | **Empty data** | Never "No results." Always "No invoices yet — try [action]." |
71
+ | **Toasts** | Auto-dismiss at 5s minimum. Always dismissible. Errors don't auto-dismiss. |
72
+ | **Modals** | Last resort (per laws). When unavoidable: trap focus, ESC to close, restore focus on close. |
73
+
74
+ ### Motion
75
+
76
+ - Restrained. Product UI motion exists to communicate state changes, not to delight.
77
+ - Hover transitions: 100-150ms ease-out
78
+ - Panel slides: 200ms ease-out
79
+ - Page transitions: 0ms (yes, zero) for navigation between similar views. Motion when it would disorient should be skipped.
80
+ - Skeleton shimmer: 1500ms loop, subtle
81
+ - No bounce. No elastic. No overshoot.
82
+
83
+ ### States (every interactive element)
84
+
85
+ - Default
86
+ - Hover (within 100ms)
87
+ - Focus (visible ring, 2px+ offset, contrasting color)
88
+ - Active/pressed (subtle scale or color shift)
89
+ - Disabled (opacity 0.5, `cursor: not-allowed`, `aria-disabled="true"`)
90
+ - Loading (per-element spinner or skeleton)
91
+ - Error (inline message + `aria-describedby`)
92
+
93
+ Every state on every interactive element. No exceptions.
94
+
95
+ ## Product register success criteria
96
+
97
+ If you can answer YES to all of these, the product register passed:
98
+
99
+ - [ ] A user fluent in the reference apps would feel oriented in 5 seconds
100
+ - [ ] Density is consistent across the app (one chosen value, applied)
101
+ - [ ] Keyboard navigation reaches every action
102
+ - [ ] Empty / loading / error states present on every async surface
103
+ - [ ] Color is never the sole information signal
104
+ - [ ] Tabular numerals on numeric columns
105
+ - [ ] No reinvented navigation pattern
106
+ - [ ] Implementation matches vision (precision and restraint, not flourish)
107
+
108
+ ## When in doubt
109
+
110
+ Ask: "Would Linear ship this?" If the answer is no, rework. Linear is not the only standard, but it is the highest commonly-cited bar for product design in 2025-2026 — fluency, restraint, density, motion all dialed in. Most product work will land within ±10% of Linear's bar; be honest about which side.