feat(design): modes, craft-floor reflexes, calibration, catalog in doctrine

DESIGN_HARD_RULES: the classifier names four visitor modes (Persuade,
Operate, Read, Experience, plus Hybrid per section) and keeps the
MARKETING/LANDING PAGE and APP UI aliases; Read and Experience get three
rules each; a "Reflexes no detector catches" block (browser surfaces, one
authored motion moment, depth has an offset, tinted secondary text, space
above headings, light-or-dark from the use scene) and the three-looks
calibration follow the universal rules. The slop section renders the 11
legacy lines plus the detector rule ids and judgment tells from the catalog;
in design-review, which also renders DESIGN_METHODOLOGY, it becomes a
one-line pointer so the catalog is paid for once. Header counts are
computed, not hardcoded.

DESIGN_METHODOLOGY: category 9 renders the catalog in three registers
(legacy lines verbatim, detector rules that need judgment with bracketed
ids, gstack-only judgment tells as prose, polish-level ids on one line);
categories 5 and 7 carry the browser-surface and one-motion-moment reflexes;
the typography overused-face item points at [overused-font] with the
role-scoped exception. The consultation Codex prompt's anti-slop line reads
from the catalog.

Budget: design-review eager 25.6K -> 27.0K (ceiling 27,984), plan-design-review
unchanged at 17.4K; no carve-guard or context-budget re-baseline needed; ship
goldens unchanged (ship never renders the hard rules).

Derived from pbakaus/impeccable reference/craft-floor.md + new-work.md
(Apache-2.0), rewritten. See NOTICE.md (commit 12).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-09-08 15:43:33 +00:00
co-authored by Claude Fable 5.1
parent 11c2a33ae9
commit 9825bfdbb7
5 changed files with 219 additions and 47 deletions
+1 -1
View File
@@ -862,7 +862,7 @@ codex exec "Given this product context, propose a complete design direction:
- Color system: CSS variables for background, surface, primary text, muted text, accent
- Layout: composition-first, not component-first. First viewport as poster, not document
- Differentiation: 2 deliberate departures from category norms
- Anti-slop: no purple gradients, no 3-column icon grids, no centered everything, no decorative blobs
- Anti-slop: none of purple gradient palette, the 3-column feature grid, centered everything, decorative blobs and dividers, nested cards, kicker above heading, icon tile above every heading, dark-mode glow
Be opinionated. Be specific. Do not hedge. This is YOUR design direction — own it." -C "$_REPO_ROOT" -s read-only -c 'model_reasoning_effort="medium"' -c 'web_search="cached"' < /dev/null 2>"$TMPERR_DESIGN"
```
+67 -23
View File
@@ -1084,7 +1084,7 @@ Apply these at each page. Each finding gets an impact rating (high/medium/polish
- Heading hierarchy: no skipped levels (h1→h3 without h2)
- Weight contrast: >=2 weights used for hierarchy
- No blacklisted fonts (Papyrus, Comic Sans, Lobster, Impact, Jokerman)
- If primary font is Inter/Roboto/Open Sans/Poppins → flag as potentially generic
- Display face on the overused list (Inter, Roboto, Arial, Helvetica, Open Sans, Lato, ...) → flag `[overused-font]`; as body/UI on an Operate or Read surface it passes when DESIGN.md says so
- `text-wrap: balance` or `text-pretty` on headings (check via `await pg.evaluate(() => getComputedStyle(document.querySelector("h1")).textWrap)`)
- Curly quotes used, not straight quotes
- Ellipsis character (`…`) not three dots (`...`)
@@ -1119,7 +1119,7 @@ Apply these at each page. Each finding gets an impact rating (high/medium/polish
- Flex/grid used for layout (not JS measurement)
- Breakpoints: mobile (375), tablet (768), desktop (1024), wide (1440)
**5. Interaction States** (10 items)
**5. Interaction States** (12 items)
- Hover state on all interactive elements
- `focus-visible` ring present (never `outline: none` without replacement)
- Active/pressed state with depth effect or color shift
@@ -1131,6 +1131,7 @@ Apply these at each page. Each finding gets an impact rating (high/medium/polish
- Touch targets >= 44px on all interactive elements
- `cursor: pointer` on all clickable elements
- Mindless choice audit: every decision point (button, link, dropdown, modal choice) is a mindless click (obvious what happens). If a click requires thought about whether it's the right choice, flag as HIGH.
- Browser surfaces themed from the palette: `::selection`, caret, scrollbars, focus ring, underline offset, tabular numerals. Left at defaults, the page reads as assembled, not designed
**6. Responsive Design** (8 items)
- Mobile layout makes *design* sense (not just stacked desktop columns)
@@ -1142,13 +1143,14 @@ Apply these at each page. Each finding gets an impact rating (high/medium/polish
- Forms usable on mobile (correct input types, no autoFocus on mobile)
- No `user-scalable=no` or `maximum-scale=1` in viewport meta
**7. Motion & Animation** (6 items)
**7. Motion & Animation** (7 items)
- Easing: ease-out for entering, ease-in for exiting, ease-in-out for moving
- Duration: 50-700ms range (nothing slower unless page transition)
- Purpose: every animation communicates something (state change, attention, spatial relationship)
- `prefers-reduced-motion` respected (check: `await pg.evaluate(() => matchMedia("(prefers-reduced-motion: reduce)").matches)`)
- No `transition: all` — properties listed explicitly
- Only `transform` and `opacity` animated (not layout properties like width, height, top, left)
- One authored motion moment per page: not the same entrance on every section, not a hover effect on everything. Ease-out from an already-visible default; content never hides behind animation timing
**8. Content & Microcopy** (8 items)
- Empty states designed with warmth (message + action + illustration/icon)
@@ -1163,9 +1165,9 @@ Apply these at each page. Each finding gets an impact rating (high/medium/polish
- Instructions detection: any visible instructions longer than one sentence. If users need to read instructions, the design has failed. Flag the instructions AND the interaction they're compensating for.
- Happy talk word count: count total visible words on the page. Classify each text block as "useful content" vs "happy talk" (welcome paragraphs, self-congratulatory text, instructions nobody reads). Report: "This page has X words. Y (Z%) are happy talk."
**9. AI Slop Detection** (10 anti-patterns — the blacklist)
**9. AI Slop Detection** (11 blacklist patterns, 19 detector rules, 14 judgment tells)
The test: would a human designer at a respected studio ever ship this?
The test: would a human designer at a respected studio ever ship this? A `[rule-id]` is the detector's name for the same pattern; a scan hit and a judgment hit on one element are one finding.
- Purple/violet/indigo gradient backgrounds or blue-to-purple color schemes
- **The 3-column feature grid:** icon-in-colored-circle + bold title + 2-line description, repeated 3x symmetrically. THE most recognizable AI layout.
@@ -1179,6 +1181,39 @@ The test: would a human designer at a respected studio ever ship this?
- Cookie-cutter section rhythm (hero → 3 features → testimonials → pricing → CTA, every section same height)
- system-ui or `-apple-system` as the PRIMARY display/body font — the "I gave up on typography" signal. Pick a real typeface.
Detector rules that still need your judgment (the id is what the scan prints):
- [overused-font] A training-data default as the display voice means you stopped looking. As body or UI on an Operate or Read surface, several of these are fine. Say which and why.
- [flat-type-hierarchy] Headings within a step of body size. Pick a scale and let the levels differ by more than a weight.
- [cream-palette] Cream ground, serif display, terracotta accent: look number one. Fine when the brief asked for it; a default when it did not.
- [nested-cards] A card inside a card is always wrong. Cards are the lazy container; nesting them is the lazy container squared.
- [shape-assembled-illustration] An illustration built from CSS shapes standing in for an asset. Produce the asset or ship nothing.
- [marquee] An infinitely scrolling logo strip. If the logos matter, show them still; if they do not, cut them.
- [icon-tile-stack] The rounded-square icon above every heading. Try side by side, or drop the container.
- [italic-serif-display] Look three: the italic display serif reaching for editorial credibility. Earn it with the content or set the display upright.
- [hero-eyebrow-chip] A pill-shaped label floating above the hero headline. The headline carries its own weight; cut the chip.
- [kicker-above-heading] A kicker above a heading is the strongest default there is: the heading carries its own weight, so delete the label. If the user wants it anyway, comply and say the tradeoff once.
- [aphoristic-cadence] Short. Punchy. Fragments. Every sentence a slogan. Write like a person explaining something.
Mechanical detector rules, confirm in the render and move on: [border-accent-on-rounded] border accent on a rounded card; [gradient-text] gradient text; [dark-glow] dark-mode glow; [radial-halo] radial halo; [radial-spotlight-glow] radial spotlight glow; [marketing-buzzword] marketing buzzwords; [oversized-h1] oversized h1; [theater-slop-phrase] theater phrases.
Judgment tells (no detector rule; you are the detector):
- Gradient buttons as the primary call to action. One solid color the palette owns.
- A generic stock-photo hero, or a gray placeholder div standing in for one. Show the product or show nothing.
- Rounded cards with drop shadows as the container for everything. App UI made of stacked cards is not layout.
- A testimonial row with avatars, five stars, and quotes nobody said. Real names with real claims, or cut it.
- The cookie-cutter hero: headline left, screenshot right, two buttons. The first template every generator reaches for.
- "Get Started" and "Learn More" as the only calls to action. Name the outcome the click buys.
- Three big numbers with tiny labels under the hero ("10k+ users", "99.9%"). The template counts, not the product.
- A grid of cards with the same shape, the same icon slot, the same two lines. Content of unequal weight given equal boxes.
- Frosted-glass panels with blurred backdrops as the default surface. One translucent layer where it explains depth, not everywhere.
- Generated SVG doodles and mascots in place of art direction. Commission or license an asset, or ship none.
- Every secondary action in a modal. Inline, a side panel, or a new page usually costs the user less.
- Sparklines, progress rings, and fake avatars filling space where content should be. Real data or an honest empty state.
- Dark because it is a dev tool, light because it is health. Light or dark comes from the use scene: who, where, under what light.
- Only the happy path is designed. Empty, loading, error, and long-content states are part of the component.
Polish-level tells, note but do not grade: [monotonous-spacing], [bounce-easing], [pulsing-dot], [blinking-cursor], [numbered-section-labels], [em-dash-overuse], [extreme-negative-tracking], [gpt-thin-border-wide-shadow], [repeating-stripes-gradient], [codex-grid-background], [image-hover-transform], monospace as costume, unthemed browser surfaces.
**10. Performance as Design** (6 items)
- LCP < 2.0s (web apps), < 1.5s (informational sites)
- CLS < 0.1 (no visible layout shifts during load)
@@ -1364,10 +1399,12 @@ Tie everything to user goals and product objectives. Always suggest specific imp
### Design Hard Rules
**Classifier — determine rule set before evaluating:**
- **MARKETING/LANDING PAGE** (hero-driven, brand-forward, conversion-focused) → apply Landing Page Rules
- **APP UI** (workspace-driven, data-dense, task-focused: dashboards, admin, settings) → apply App UI Rules
- **HYBRID** (marketing shell with app-like sections) → apply Landing Page Rules to hero/marketing sections, App UI Rules to functional sections
**Classifier: name the mode before you judge a pixel.** The mode is what the visitor's win looks like on THIS surface, not what the product is. A dev tool's landing page is Persuade. A fashion house's docs are Read.
- **PERSUADE** (MARKETING/LANDING PAGE: hero-driven, brand-forward, pricing, campaigns) → they decide and act. Design IS the product. Apply Landing Page Rules.
- **OPERATE** (APP UI: dashboards, admin, settings, editors, tools) → they finish a task. Scanability and native expectations beat expression; the brand lives in the details. Apply App UI Rules.
- **READ** (docs, articles, guides, changelogs) → they understand something. Structure for comprehension, then make staying worth it. Apply Read Rules.
- **EXPERIENCE** (portfolios, galleries, showcases) → they are inside the work. The artifact owns the first viewport; the interface gets out of the way. Apply Experience Rules.
- **HYBRID** (marketing shell with app-like sections) → classify per section, not per page.
**Hard rejection criteria** (instant-fail patterns — flag if ANY apply):
1. Generic SaaS card grid as first impression
@@ -1387,7 +1424,7 @@ Tie everything to user goals and product objectives. Always suggest specific imp
6. Does motion improve hierarchy or atmosphere?
7. Would design feel premium with all decorative shadows removed?
**Landing page rules** (apply when classifier = MARKETING/LANDING):
**Landing page rules** (apply when classifier = PERSUADE / MARKETING/LANDING):
- First viewport reads as one composition, not a dashboard
- Brand-first hierarchy: brand > headline > body > CTA
- Typography: expressive, purposeful — no default stacks (Inter, Roboto, Arial, system)
@@ -1401,7 +1438,7 @@ Tie everything to user goals and product objectives. Always suggest specific imp
- Copy: product language not design commentary. "If deleting 30% improves it, keep deleting"
- Beautiful defaults: composition-first, brand as loudest text, two typefaces max, cardless by default, first viewport as poster not document
**App UI rules** (apply when classifier = APP UI):
**App UI rules** (apply when classifier = OPERATE / APP UI):
- Calm surface hierarchy, strong typography, few colors
- Dense but readable, minimal chrome
- Organize: primary workspace, navigation, secondary context, one accent
@@ -1410,6 +1447,16 @@ Tie everything to user goals and product objectives. Always suggest specific imp
- Cards only when card IS the interaction
- Section headings state what area is or what user can do ("Selected KPIs", "Plan status")
**Read rules** (apply when classifier = READ):
- Measure 65-75ch, one reading column, headings closer to what follows than to what precedes
- Wayfinding is a feature: where am I, what is next, where do I search
- A docs index is Read, not Persuade: no hero, no CTA theater
**Experience rules** (apply when classifier = EXPERIENCE):
- The work fills the first viewport; chrome earns every pixel
- One authored transition, not a scroll-jacked tour
- Never crop the artifact to fit a template
**Universal rules** (apply to ALL types):
- Define CSS variables for color system
- No default font stacks (Inter, Roboto, Arial, system)
@@ -1421,18 +1468,15 @@ Tie everything to user goals and product objectives. Always suggest specific imp
- ALWAYS preserve visited vs unvisited link distinction (visited links must have a different color)
- NEVER float headings between paragraphs (heading must be visually closer to the section it introduces than to the preceding section)
**AI Slop blacklist** (the 10 patterns that scream "AI-generated"):
1. Purple/violet/indigo gradient backgrounds or blue-to-purple color schemes
2. **The 3-column feature grid:** icon-in-colored-circle + bold title + 2-line description, repeated 3x symmetrically. THE most recognizable AI layout.
3. Icons in colored circles as section decoration (SaaS starter template look)
4. Centered everything (`text-align: center` on all headings, descriptions, cards)
5. Uniform bubbly border-radius on every element (same large radius on everything)
6. Decorative blobs, floating circles, wavy SVG dividers (if a section feels empty, it needs better content, not decoration)
7. Emoji as design elements (rockets in headings, emoji as bullet points)
8. Colored left-border on cards (`border-left: 3px solid <accent>`)
9. Generic hero copy ("Welcome to [X]", "Unlock the power of...", "Your all-in-one solution for...")
10. Cookie-cutter section rhythm (hero → 3 features → testimonials → pricing → CTA, every section same height)
11. system-ui or `-apple-system` as the PRIMARY display/body font — the "I gave up on typography" signal. Pick a real typeface.
**Reflexes no detector catches** (check by hand, every time):
- **Depth has an offset.** Shadows are offset plus soft blur. A zero-offset colored halo is decoration, not depth.
- **Secondary text on a colored surface is tinted from that hue.** Never gray.
- **More space above a heading than below it.** Read the computed values.
- **Light or dark comes from the use scene.** Who, where, under what light: one sentence. Never from the category.
**Calibration: the three looks.** AI-built interfaces land in one of three looks no matter what the product is: (1) cream ground, high-contrast serif display, terracotta or signal-red accent; (2) near-black, one neon accent, glowing edges; (3) broadsheet hairlines, italic display serif, tiny tracked mono labels. Each is fine when the brief asks for it. If the brief left the look open and you landed in one anyway, you stopped looking. The test: could someone guess your look from the category alone? From "the category, but avoiding the obvious"? Either way, start over. "It's about books, so cream and a serif" fails this test. Book cloth and jackets come in every saturated color there is.
**AI Slop blacklist:** the 11 legacy patterns, the 30 detector rules, and the 16 judgment tells are Methodology category 9. Grade against that list; do not re-derive it here.
Source: [OpenAI "Designing Delightful Frontends with GPT-5.4"](https://developers.openai.com/blog/designing-delightful-frontends-with-gpt-5-4) (Mar 2026) + gstack design methodology.
+32 -7
View File
@@ -79,10 +79,12 @@ FIX TO 10: Rewrite vague UI descriptions with specific alternatives.
### Design Hard Rules
**Classifier — determine rule set before evaluating:**
- **MARKETING/LANDING PAGE** (hero-driven, brand-forward, conversion-focused) → apply Landing Page Rules
- **APP UI** (workspace-driven, data-dense, task-focused: dashboards, admin, settings) → apply App UI Rules
- **HYBRID** (marketing shell with app-like sections) → apply Landing Page Rules to hero/marketing sections, App UI Rules to functional sections
**Classifier: name the mode before you judge a pixel.** The mode is what the visitor's win looks like on THIS surface, not what the product is. A dev tool's landing page is Persuade. A fashion house's docs are Read.
- **PERSUADE** (MARKETING/LANDING PAGE: hero-driven, brand-forward, pricing, campaigns) → they decide and act. Design IS the product. Apply Landing Page Rules.
- **OPERATE** (APP UI: dashboards, admin, settings, editors, tools) → they finish a task. Scanability and native expectations beat expression; the brand lives in the details. Apply App UI Rules.
- **READ** (docs, articles, guides, changelogs) → they understand something. Structure for comprehension, then make staying worth it. Apply Read Rules.
- **EXPERIENCE** (portfolios, galleries, showcases) → they are inside the work. The artifact owns the first viewport; the interface gets out of the way. Apply Experience Rules.
- **HYBRID** (marketing shell with app-like sections) → classify per section, not per page.
**Hard rejection criteria** (instant-fail patterns — flag if ANY apply):
1. Generic SaaS card grid as first impression
@@ -102,7 +104,7 @@ FIX TO 10: Rewrite vague UI descriptions with specific alternatives.
6. Does motion improve hierarchy or atmosphere?
7. Would design feel premium with all decorative shadows removed?
**Landing page rules** (apply when classifier = MARKETING/LANDING):
**Landing page rules** (apply when classifier = PERSUADE / MARKETING/LANDING):
- First viewport reads as one composition, not a dashboard
- Brand-first hierarchy: brand > headline > body > CTA
- Typography: expressive, purposeful — no default stacks (Inter, Roboto, Arial, system)
@@ -116,7 +118,7 @@ FIX TO 10: Rewrite vague UI descriptions with specific alternatives.
- Copy: product language not design commentary. "If deleting 30% improves it, keep deleting"
- Beautiful defaults: composition-first, brand as loudest text, two typefaces max, cardless by default, first viewport as poster not document
**App UI rules** (apply when classifier = APP UI):
**App UI rules** (apply when classifier = OPERATE / APP UI):
- Calm surface hierarchy, strong typography, few colors
- Dense but readable, minimal chrome
- Organize: primary workspace, navigation, secondary context, one accent
@@ -125,6 +127,16 @@ FIX TO 10: Rewrite vague UI descriptions with specific alternatives.
- Cards only when card IS the interaction
- Section headings state what area is or what user can do ("Selected KPIs", "Plan status")
**Read rules** (apply when classifier = READ):
- Measure 65-75ch, one reading column, headings closer to what follows than to what precedes
- Wayfinding is a feature: where am I, what is next, where do I search
- A docs index is Read, not Persuade: no hero, no CTA theater
**Experience rules** (apply when classifier = EXPERIENCE):
- The work fills the first viewport; chrome earns every pixel
- One authored transition, not a scroll-jacked tour
- Never crop the artifact to fit a template
**Universal rules** (apply to ALL types):
- Define CSS variables for color system
- No default font stacks (Inter, Roboto, Arial, system)
@@ -136,7 +148,17 @@ FIX TO 10: Rewrite vague UI descriptions with specific alternatives.
- ALWAYS preserve visited vs unvisited link distinction (visited links must have a different color)
- NEVER float headings between paragraphs (heading must be visually closer to the section it introduces than to the preceding section)
**AI Slop blacklist** (the 10 patterns that scream "AI-generated"):
**Reflexes no detector catches** (check by hand, every time):
- **Browser surfaces carry the design.** Selection color, caret, scrollbars, focus rings, underline offset, tabular numerals all ship with browser defaults that belong to no design system. Theme them from the palette. Cheapest tell that a page was designed rather than assembled, and the one models skip most.
- **One authored motion moment.** Not the same entrance on every section, not a hover effect on everything. Exponential ease-out from an already-visible default. Content never hides behind animation timing.
- **Depth has an offset.** Shadows are offset plus soft blur. A zero-offset colored halo is decoration, not depth.
- **Secondary text on a colored surface is tinted from that hue.** Never gray.
- **More space above a heading than below it.** Read the computed values.
- **Light or dark comes from the use scene.** Who, where, under what light: one sentence. Never from the category.
**Calibration: the three looks.** AI-built interfaces land in one of three looks no matter what the product is: (1) cream ground, high-contrast serif display, terracotta or signal-red accent; (2) near-black, one neon accent, glowing edges; (3) broadsheet hairlines, italic display serif, tiny tracked mono labels. Each is fine when the brief asks for it. If the brief left the look open and you landed in one anyway, you stopped looking. The test: could someone guess your look from the category alone? From "the category, but avoiding the obvious"? Either way, start over. "It's about books, so cream and a serif" fails this test. Book cloth and jackets come in every saturated color there is.
**AI Slop blacklist** (the 11 patterns that scream "AI-generated"):
1. Purple/violet/indigo gradient backgrounds or blue-to-purple color schemes
2. **The 3-column feature grid:** icon-in-colored-circle + bold title + 2-line description, repeated 3x symmetrically. THE most recognizable AI layout.
3. Icons in colored circles as section decoration (SaaS starter template look)
@@ -149,6 +171,9 @@ FIX TO 10: Rewrite vague UI descriptions with specific alternatives.
10. Cookie-cutter section rhythm (hero → 3 features → testimonials → pricing → CTA, every section same height)
11. system-ui or `-apple-system` as the PRIMARY display/body font — the "I gave up on typography" signal. Pick a real typeface.
Detector rule ids for the rest of the catalog (a `[rule-id]` in a finding is one of these): border-accent-on-rounded: Border accent on a rounded card; overused-font: Overused display font; flat-type-hierarchy: Flat type hierarchy; gradient-text: Gradient text; cream-palette: Cream default palette; nested-cards: Nested cards; monotonous-spacing: Monotonous spacing; bounce-easing: Bounce easing; pulsing-dot: Pulsing status dot; blinking-cursor: Blinking cursor effect; shape-assembled-illustration: Shape-assembled illustration; dark-glow: Dark-mode glow; radial-halo: Radial halo; radial-spotlight-glow: Radial spotlight glow; marquee: Logo marquee; icon-tile-stack: Icon tile above every heading; italic-serif-display: Italic serif display; hero-eyebrow-chip: Hero eyebrow chip; kicker-above-heading: Kicker above heading; numbered-section-labels: Numbered section labels; em-dash-overuse: Em-dash overuse; marketing-buzzword: Marketing buzzwords; aphoristic-cadence: Aphoristic cadence; oversized-h1: Oversized h1; extreme-negative-tracking: Extreme negative tracking; gpt-thin-border-wide-shadow: Thin border plus wide shadow; repeating-stripes-gradient: Repeating stripes gradient; codex-grid-background: Grid-paper background; theater-slop-phrase: Theater phrases; image-hover-transform: Image hover zoom.
Judgment tells with no detector rule: gradient cta button, stock-photo hero, cards as the default component, generic testimonial section, left-text right-image hero, generic cta labels, hero metric template, identical card grids, glassmorphism, hand-drawn svg illustration, modal by default, monospace as costume, content stand-ins, mode picked by category, unthemed browser surfaces, missing states.
Source: [OpenAI "Designing Delightful Frontends with GPT-5.4"](https://developers.openai.com/blog/designing-delightful-frontends-with-gpt-5-4) (Mar 2026) + gstack design methodology.
- "Cards with icons" → what differentiates these from every SaaS template?
- "Hero section" → what makes this hero feel like THIS product?
+78 -16
View File
@@ -1,5 +1,6 @@
import { type TemplateContext, toShellPath } from './types';
import { AI_SLOP_BLACKLIST, OPENAI_HARD_REJECTIONS, OPENAI_LITMUS_CHECKS, CODEX_WEB_SEARCH_FLAG, CC_BACKGROUND_DEFAULT_SINCE } from './constants';
import { DESIGN_SLOP_CATALOG, OVERUSED_FONTS_DISPLAY, selectCatalog, catalogEntry } from '../../lib/design-catalog';
export function generateDesignReviewLite(ctx: TemplateContext): string {
const litmusList = OPENAI_LITMUS_CHECKS.map((item, i) => `${i + 1}. ${item}`).join(' ');
@@ -68,6 +69,14 @@ Substitute: TIMESTAMP = ISO 8601 datetime, STATUS = "clean" if 0 findings or "is
// from lib/design-catalog.ts, the same catalog category 9 below renders. Edit the
// catalog, never the checklist; gen-skill-docs rewrites it.
export function generateDesignMethodology(_ctx: TemplateContext): string {
// Category 9 renders the catalog in three registers: the 11 legacy lines verbatim,
// detector-known slop with bracketed ids (impact above polish), and the gstack-only
// judgment tells as prose. Polish-level slop is one compact line so the category
// stays inside design-review's eager budget.
const slop = selectCatalog({ kind: 'slop' });
const detectorSlop = slop.filter(e => e.impeccableId && !e.legacyBlacklist && e.impact !== 'polish');
const judgmentTells = slop.filter(e => !e.impeccableId && !e.legacyBlacklist && e.impact !== 'polish');
const polishTells = slop.filter(e => !e.legacyBlacklist && e.impact === 'polish');
return `## Modes
### Full (default)
@@ -231,7 +240,7 @@ Apply these at each page. Each finding gets an impact rating (high/medium/polish
- Heading hierarchy: no skipped levels (h1→h3 without h2)
- Weight contrast: >=2 weights used for hierarchy
- No blacklisted fonts (Papyrus, Comic Sans, Lobster, Impact, Jokerman)
- If primary font is Inter/Roboto/Open Sans/Poppins → flag as potentially generic
- Display face on the overused list (${OVERUSED_FONTS_DISPLAY.slice(0, 6).join(', ')}, ...) → flag \`[overused-font]\`; as body/UI on an Operate or Read surface it passes when DESIGN.md says so
- \`text-wrap: balance\` or \`text-pretty\` on headings (check via \`await pg.evaluate(() => getComputedStyle(document.querySelector("h1")).textWrap)\`)
- Curly quotes used, not straight quotes
- Ellipsis character (\`\`) not three dots (\`...\`)
@@ -266,7 +275,7 @@ Apply these at each page. Each finding gets an impact rating (high/medium/polish
- Flex/grid used for layout (not JS measurement)
- Breakpoints: mobile (375), tablet (768), desktop (1024), wide (1440)
**5. Interaction States** (10 items)
**5. Interaction States** (12 items)
- Hover state on all interactive elements
- \`focus-visible\` ring present (never \`outline: none\` without replacement)
- Active/pressed state with depth effect or color shift
@@ -278,6 +287,7 @@ Apply these at each page. Each finding gets an impact rating (high/medium/polish
- Touch targets >= 44px on all interactive elements
- \`cursor: pointer\` on all clickable elements
- Mindless choice audit: every decision point (button, link, dropdown, modal choice) is a mindless click (obvious what happens). If a click requires thought about whether it's the right choice, flag as HIGH.
- Browser surfaces themed from the palette: \`::selection\`, caret, scrollbars, focus ring, underline offset, tabular numerals. Left at defaults, the page reads as assembled, not designed
**6. Responsive Design** (8 items)
- Mobile layout makes *design* sense (not just stacked desktop columns)
@@ -289,13 +299,14 @@ Apply these at each page. Each finding gets an impact rating (high/medium/polish
- Forms usable on mobile (correct input types, no autoFocus on mobile)
- No \`user-scalable=no\` or \`maximum-scale=1\` in viewport meta
**7. Motion & Animation** (6 items)
**7. Motion & Animation** (7 items)
- Easing: ease-out for entering, ease-in for exiting, ease-in-out for moving
- Duration: 50-700ms range (nothing slower unless page transition)
- Purpose: every animation communicates something (state change, attention, spatial relationship)
- \`prefers-reduced-motion\` respected (check: \`await pg.evaluate(() => matchMedia("(prefers-reduced-motion: reduce)").matches)\`)
- No \`transition: all\` — properties listed explicitly
- Only \`transform\` and \`opacity\` animated (not layout properties like width, height, top, left)
- One authored motion moment per page: not the same entrance on every section, not a hover effect on everything. Ease-out from an already-visible default; content never hides behind animation timing
**8. Content & Microcopy** (8 items)
- Empty states designed with warmth (message + action + illustration/icon)
@@ -310,12 +321,22 @@ Apply these at each page. Each finding gets an impact rating (high/medium/polish
- Instructions detection: any visible instructions longer than one sentence. If users need to read instructions, the design has failed. Flag the instructions AND the interaction they're compensating for.
- Happy talk word count: count total visible words on the page. Classify each text block as "useful content" vs "happy talk" (welcome paragraphs, self-congratulatory text, instructions nobody reads). Report: "This page has X words. Y (Z%) are happy talk."
**9. AI Slop Detection** (10 anti-patterns — the blacklist)
**9. AI Slop Detection** (${AI_SLOP_BLACKLIST.length} blacklist patterns, ${detectorSlop.length} detector rules, ${judgmentTells.length} judgment tells)
The test: would a human designer at a respected studio ever ship this?
The test: would a human designer at a respected studio ever ship this? A \`[rule-id]\` is the detector's name for the same pattern; a scan hit and a judgment hit on one element are one finding.
${AI_SLOP_BLACKLIST.map(item => `- ${item}`).join('\n')}
Detector rules that still need your judgment (the id is what the scan prints):
${detectorSlop.filter(e => e.detect.includes('llm')).map(e => `- [${e.impeccableId}] ${e.prose}`).join('\n')}
Mechanical detector rules, confirm in the render and move on: ${detectorSlop.filter(e => !e.detect.includes('llm')).map(e => `[${e.impeccableId}] ${e.name.toLowerCase()}`).join('; ')}.
Judgment tells (no detector rule; you are the detector):
${judgmentTells.map(e => `- ${e.prose}`).join('\n')}
Polish-level tells, note but do not grade: ${polishTells.map(e => (e.impeccableId ? `[${e.impeccableId}]` : e.name.toLowerCase())).join(', ')}.
**10. Performance as Design** (6 items)
- LCP < 2.0s (web apps), < 1.5s (informational sites)
- CLS < 0.1 (no visible layout shifts during load)
@@ -678,7 +699,7 @@ For each finding: what's wrong, severity (critical/high/medium), and the file:li
- Color system: CSS variables for background, surface, primary text, muted text, accent
- Layout: composition-first, not component-first. First viewport as poster, not document
- Differentiation: 2 deliberate departures from category norms
- Anti-slop: no purple gradients, no 3-column icon grids, no centered everything, no decorative blobs
- Anti-slop: none of ${['ai-color-palette', 'feature-grid-3col', 'centered-everything', 'decorative-blobs', 'nested-cards', 'kicker-above-heading', 'icon-tile-stack', 'dark-glow'].map(id => catalogEntry(id)!.name.toLowerCase()).join(', ')}
Be opinionated. Be specific. Do not hedge. This is YOUR design direction — own it.`;
@@ -787,18 +808,45 @@ ${ctx.paths.binDir}/gstack-review-log '{"skill":"design-outside-voices","timesta
Replace STATUS with "clean" or "issues_found", SOURCE with "codex+subagent", "codex-only", "subagent-only", or "unavailable".`;
}
// ─── Design Hard Rules (OpenAI framework + gstack slop blacklist) ───
export function generateDesignHardRules(_ctx: TemplateContext): string {
// ─── Design Hard Rules (OpenAI framework + gstack slop catalog) ───
// Modes (Persuade/Operate/Read/Experience), the craft-floor reflexes, and the
// three-looks calibration are derived from pbakaus/impeccable reference/craft-floor.md
// + new-work.md (Apache-2.0), rewritten in gstack's voice. See NOTICE.md.
export function generateDesignHardRules(ctx: TemplateContext): string {
const slopItems = AI_SLOP_BLACKLIST.map((item, i) => `${i + 1}. ${item}`).join('\n');
const rejectionItems = OPENAI_HARD_REJECTIONS.map((item, i) => `${i + 1}. ${item}`).join('\n');
const litmusItems = OPENAI_LITMUS_CHECKS.map((item, i) => `${i + 1}. ${item}`).join('\n');
const detectorSlop = DESIGN_SLOP_CATALOG.filter(e => e.kind === 'slop' && e.impeccableId && !e.legacyBlacklist);
const judgmentTells = DESIGN_SLOP_CATALOG.filter(e => e.kind === 'slop' && !e.impeccableId && !e.legacyBlacklist);
// design-review renders DESIGN_METHODOLOGY too, whose category 9 carries the
// full catalog with ids; there the slop section is a pointer, not a second copy.
const slopSection = ctx.skillName === 'design-review'
? `**AI Slop blacklist:** the ${AI_SLOP_BLACKLIST.length} legacy patterns, the ${detectorSlop.length} detector rules, and the ${judgmentTells.length} judgment tells are Methodology category 9. Grade against that list; do not re-derive it here.`
: `**AI Slop blacklist** (the ${AI_SLOP_BLACKLIST.length} patterns that scream "AI-generated"):
${slopItems}
Detector rule ids for the rest of the catalog (a \`[rule-id]\` in a finding is one of these): ${detectorSlop.map(e => `${e.impeccableId}: ${e.name}`).join('; ')}.
Judgment tells with no detector rule: ${judgmentTells.map(e => e.name.toLowerCase()).join(', ')}.`;
const reflexes = [
'- **Browser surfaces carry the design.** Selection color, caret, scrollbars, focus rings, underline offset, tabular numerals all ship with browser defaults that belong to no design system. Theme them from the palette. Cheapest tell that a page was designed rather than assembled, and the one models skip most.',
'- **One authored motion moment.** Not the same entrance on every section, not a hover effect on everything. Exponential ease-out from an already-visible default. Content never hides behind animation timing.',
'- **Depth has an offset.** Shadows are offset plus soft blur. A zero-offset colored halo is decoration, not depth.',
'- **Secondary text on a colored surface is tinted from that hue.** Never gray.',
'- **More space above a heading than below it.** Read the computed values.',
'- **Light or dark comes from the use scene.** Who, where, under what light: one sentence. Never from the category.',
];
// design-review's Methodology categories 5 and 7 already carry the first two.
const reflexBlock = (ctx.skillName === 'design-review' ? reflexes.slice(2) : reflexes).join('\n');
return `### Design Hard Rules
**Classifier — determine rule set before evaluating:**
- **MARKETING/LANDING PAGE** (hero-driven, brand-forward, conversion-focused) → apply Landing Page Rules
- **APP UI** (workspace-driven, data-dense, task-focused: dashboards, admin, settings) → apply App UI Rules
- **HYBRID** (marketing shell with app-like sections) → apply Landing Page Rules to hero/marketing sections, App UI Rules to functional sections
**Classifier: name the mode before you judge a pixel.** The mode is what the visitor's win looks like on THIS surface, not what the product is. A dev tool's landing page is Persuade. A fashion house's docs are Read.
- **PERSUADE** (MARKETING/LANDING PAGE: hero-driven, brand-forward, pricing, campaigns) → they decide and act. Design IS the product. Apply Landing Page Rules.
- **OPERATE** (APP UI: dashboards, admin, settings, editors, tools) → they finish a task. Scanability and native expectations beat expression; the brand lives in the details. Apply App UI Rules.
- **READ** (docs, articles, guides, changelogs) → they understand something. Structure for comprehension, then make staying worth it. Apply Read Rules.
- **EXPERIENCE** (portfolios, galleries, showcases) → they are inside the work. The artifact owns the first viewport; the interface gets out of the way. Apply Experience Rules.
- **HYBRID** (marketing shell with app-like sections) → classify per section, not per page.
**Hard rejection criteria** (instant-fail patterns — flag if ANY apply):
${rejectionItems}
@@ -806,7 +854,7 @@ ${rejectionItems}
**Litmus checks** (answer YES/NO for each — used for cross-model consensus scoring):
${litmusItems}
**Landing page rules** (apply when classifier = MARKETING/LANDING):
**Landing page rules** (apply when classifier = PERSUADE / MARKETING/LANDING):
- First viewport reads as one composition, not a dashboard
- Brand-first hierarchy: brand > headline > body > CTA
- Typography: expressive, purposeful — no default stacks (Inter, Roboto, Arial, system)
@@ -820,7 +868,7 @@ ${litmusItems}
- Copy: product language not design commentary. "If deleting 30% improves it, keep deleting"
- Beautiful defaults: composition-first, brand as loudest text, two typefaces max, cardless by default, first viewport as poster not document
**App UI rules** (apply when classifier = APP UI):
**App UI rules** (apply when classifier = OPERATE / APP UI):
- Calm surface hierarchy, strong typography, few colors
- Dense but readable, minimal chrome
- Organize: primary workspace, navigation, secondary context, one accent
@@ -829,6 +877,16 @@ ${litmusItems}
- Cards only when card IS the interaction
- Section headings state what area is or what user can do ("Selected KPIs", "Plan status")
**Read rules** (apply when classifier = READ):
- Measure 65-75ch, one reading column, headings closer to what follows than to what precedes
- Wayfinding is a feature: where am I, what is next, where do I search
- A docs index is Read, not Persuade: no hero, no CTA theater
**Experience rules** (apply when classifier = EXPERIENCE):
- The work fills the first viewport; chrome earns every pixel
- One authored transition, not a scroll-jacked tour
- Never crop the artifact to fit a template
**Universal rules** (apply to ALL types):
- Define CSS variables for color system
- No default font stacks (Inter, Roboto, Arial, system)
@@ -840,8 +898,12 @@ ${litmusItems}
- ALWAYS preserve visited vs unvisited link distinction (visited links must have a different color)
- NEVER float headings between paragraphs (heading must be visually closer to the section it introduces than to the preceding section)
**AI Slop blacklist** (the 10 patterns that scream "AI-generated"):
${slopItems}
**Reflexes no detector catches** (check by hand, every time):
${reflexBlock}
**Calibration: the three looks.** AI-built interfaces land in one of three looks no matter what the product is: (1) cream ground, high-contrast serif display, terracotta or signal-red accent; (2) near-black, one neon accent, glowing edges; (3) broadsheet hairlines, italic display serif, tiny tracked mono labels. Each is fine when the brief asks for it. If the brief left the look open and you landed in one anyway, you stopped looking. The test: could someone guess your look from the category alone? From "the category, but avoiding the obvious"? Either way, start over. "It's about books, so cream and a serif" fails this test. Book cloth and jackets come in every saturated color there is.
${slopSection}
Source: [OpenAI "Designing Delightful Frontends with GPT-5.4"](https://developers.openai.com/blog/designing-delightful-frontends-with-gpt-5-4) (Mar 2026) + gstack design methodology.`;
}
+41
View File
@@ -1789,6 +1789,47 @@ describe('DESIGN_HARD_RULES resolver', () => {
expect(content).toContain('Universal rules');
});
test('classifier names the four visitor modes and keeps the legacy aliases', () => {
const content = readSkillUnion('plan-design-review');
for (const mode of ['PERSUADE', 'OPERATE', 'READ', 'EXPERIENCE', 'HYBRID']) expect(content).toContain(`**${mode}**`);
expect(content).toContain('Read rules');
expect(content).toContain('Experience rules');
expect(content).toContain('classify per section, not per page');
});
test('carries the craft-floor reflexes and the three-looks calibration', () => {
const content = readSkillUnion('plan-design-review');
expect(content).toContain('Reflexes no detector catches');
expect(content).toContain('Browser surfaces carry the design');
expect(content).toContain('One authored motion moment');
expect(content).toContain('Depth has an offset');
expect(content).toContain('Light or dark comes from the use scene');
expect(content).toContain('Calibration: the three looks');
});
test('slop section lists detector rule ids and judgment tells outside design-review', () => {
const content = readSkillUnion('plan-design-review');
expect(content).toContain('Detector rule ids for the rest of the catalog');
expect(content).toContain('nested-cards: Nested cards');
expect(content).toContain('Judgment tells with no detector rule');
// Never a bracketed gstack-only id.
expect(content).not.toContain('[hero-metrics]');
});
test('design-review renders the catalog once: Methodology category 9 carries it, Hard Rules points at it', () => {
const content = fs.readFileSync(path.join(ROOT, 'design-review', 'SKILL.md'), 'utf-8');
expect(content.split('### Design Hard Rules').length - 1).toBe(1);
// Category 9 lists the rule once; Typography points at the same id from its overused-face item.
expect(content.split('- [overused-font] ').length - 1).toBe(1);
expect(content.split('[overused-font]').length - 1).toBe(2);
expect(content).toContain('are Methodology category 9');
expect(content).toContain('**9. AI Slop Detection**');
expect(content).toContain('- [nested-cards] ');
expect(content).toContain('Judgment tells (no detector rule');
// The legacy blacklist is not repeated as a numbered list in design-review.
expect(content).not.toMatch(/^1\. Purple\/violet\/indigo/m);
});
test('references shared AI slop blacklist items', () => {
const content = readSkillUnion('plan-design-review');
expect(content).toContain('3-column feature grid');