diff --git a/CHANGELOG.md b/CHANGELOG.md index f7a1cebf5..e1c6cca99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,9 @@ - **/design-shotgun**: a landing, pricing or campaign screen's variants also differ in page archetype, keep the one primary action identical, and carry their section outline as a caption. - **The archetype the user picks on the /design-shotgun board now reaches /design-html.** Captions name the archetype id (`classic-hero`, `long-form-story`, `minimal-conversion`, `comparison`). On approval, approved.json also gets the chosen `archetype` and its `sections` outline, added with the Edit tool so no free text reaches a shell command. /design-html reads both in Case A, and the craft section uses them as given instead of choosing again or asking. - **Accepted craft exceptions persist.** /design-html's finalized.json records each accepted `CRAFT_CHECK` finding in `craft_exceptions` (`id`, `count`, `reason`) and carries forward earlier ones. A later run passes that file with `--accepted`, so a finding the user already accepted is reported as accepted instead of flagged again. A higher count is still flagged as a new issue. +- **/design-review audits a Persuade page or a redesign for craft** (`sections/craft-audit.md`, read in Phases 1-6; design-review is now carved on every host, so the audit is one lazy section, not inlined text). It runs `bin/gstack-design-craft` on each page's served HTML (or the repo file when no app runs) and files each `CRAFT_CHECK` line as a category 9 finding; checks what the tool cannot see (claims only from the user, realistic sample content, the ship floor, a designed 404, cookie consent where required, the motion rules); files a failed Persuade check (one primary action, named objections, proof beside each claim, a real risk reversal, an index/noindex decision) as High impact; and in Phase 8 breaks impact ties with Elaya's fix order and reads the installed Tailwind major version before touching its config. The audit's rules render from /design-html's craft section, so the build and the audit cannot drift. +- **/plan-design-review**: a Persuade plan gets the same five checks in Pass 1, Pass 4 rejects unsourced proof, and the runner checklist names them. +- **The craft tells are catalog entries.** Placeholder people and brands, lorem, round sample numbers, `[Needs proof:` markers, "Oops" errors, scroll-listener reveals, dead `href="#"`, `alert()`, images without alt, `100vh`, and a missing title, description, favicon, skip link or `aria-current` live in `lib/design-catalog.ts`, so they render into Methodology category 9, `review/design-checklist.md` and the consultation bullets like every other tell. `lib/design-craft.ts` prints only catalog ids, with each entry's `fix` text; `transition: all` now reports under the existing `layout-transition` id instead of `transition-all`. ### Added - **`bin/gstack-design-craft`** (`lib/design-craft.ts`): prints `CRAFT_CHECK: ` per tell and `CRAFT_RESULT: clean` (exit 0) or ` issue(s)` (exit 1); exit 2 on a non-HTML, empty or unreadable file. Copy rules read decoded visible text; `alert()` counts only in scripts; head and navigation rules run only on a full document. Buzzwords report under the catalog's `marketing-buzzword` id. @@ -20,6 +23,10 @@ - `test/design-craft.test.ts`: every tell with its count; a finished page, inlined Pretext, a `100vh` line with its `100dvh` fallback, discount copy and in-page anchors stay clean; look-alike attributes, encoded markers, `!important` and reversed fallbacks; skip links judged by position and target in any language; the craft section's banned words and names all flagged; CLI exit codes. - `design-html-slop-gate` also requires the craft check to run once or twice; the design-html carve guard requires the craft section read. - `test/design-craft.test.ts` also covers accepted exceptions: count coverage and overflow, the finalized.json block in design-html/SKILL.md parsed by the same reader the CLI uses, rejected malformed lists, CLI exit codes with `--accepted`, and the archetype handoff contract (the same four ids in /design-shotgun and the craft section, the approved.json step, Case A and the craft section's no-re-ask rule). +- `test/design-craft.test.ts`: every id the check prints is a grep-detectable catalog entry, and the fix it prints is the catalog's. design-review gets its first carve guard (skeleton, union and parity ratio at their measured sizes). + +### Gate integrity +- Size budgets raised to measured sizes only (Garry 2026-10-25 "Do it" (GSTA-92)): the SKILL.md pins of design-review on 13 hosts (+2,011 Claude, +2,041 external), plan-design-review on 12 external hosts (+1,387) and design-consultation on 9 external hosts (+1,259), and design-consultation's parity ratio from 1.16 to its measured 1.1694. No other threshold, budget, timeout, retry or trial count changed. ## [1.91.218.0] - 2026-10-25 diff --git a/NOTICE.md b/NOTICE.md index 831e0162a..f9291c8a0 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -49,6 +49,12 @@ signature components are not carried over): - `lib/design-craft.ts` and `bin/gstack-design-craft`: the content-realism and ship-floor tells, turned into a free check. - `design-shotgun/SKILL.md.tmpl`: page archetypes as a variant axis. +- `design-review/sections/craft-audit.md.tmpl` (and the generated + `sections/craft-audit.md`): the redesign-skill audit of an existing site, + its fix-priority order and Tailwind version check; `scripts/resolvers/design-craft.ts`: + the Persuade conversion checks, also rendered into + `plan-design-review/sections/review-sections.md`; and the craft-tell entries + in `lib/design-catalog.ts`. ## DESIGN.md specification — Copyright Google LLC — Apache License 2.0 diff --git a/design-consultation/sections/proposal-and-preview.md b/design-consultation/sections/proposal-and-preview.md index 6f9ae8150..951287e69 100644 --- a/design-consultation/sections/proposal-and-preview.md +++ b/design-consultation/sections/proposal-and-preview.md @@ -90,6 +90,18 @@ User asks for a listed face by name: comply, state the tradeoff once. - 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. +- John Doe and Jane Doe in quotes, team rows and demo data. A supplied name, or one that fits the product. +- Acme, Nexus and SmartFlow as the customers in logos, quotes and sample data. A supplied customer, or none. +- Lorem ipsum where a visitor can read it. Write the draft copy. +- 99.9% uptime, 50% faster, 10x, 10,000+ users: round figures nobody measured. The supplied number, or no claim. +- A `[Needs proof: …]` marker still on the page. Supply the proof or cut the claim before it ships. +- Reveals driven by an unthrottled `scroll` listener. IntersectionObserver for reveals; scroll-linked effects read scroll inside requestAnimationFrame. +- `href="#"` on links and buttons that go nowhere. Link it, or render the control disabled. +- `alert()` for errors and confirmations. Show the message inline, next to what it is about. +- An `` with no alt attribute. Describe a meaningful image; `alt=""` on a decorative one. +- A full HTML document with no ``. The tab, the bookmark and the search result all read it. +- A full HTML document with no meta description. Search results and link previews make one up. +- No "Skip to content" link as the first focusable element, targeting `<main>`. Keyboard users tab through the whole header on every page. ### Coherence Validation diff --git a/design-review/SKILL.md b/design-review/SKILL.md index 42e10d81d..9ecb3556b 100644 --- a/design-review/SKILL.md +++ b/design-review/SKILL.md @@ -1250,7 +1250,7 @@ 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** (11 blacklist patterns, 30 detector rules, 16 judgment tells; polish-level ones on the last line) +**9. AI Slop Detection** (11 blacklist patterns, 30 detector rules, 32 judgment tells; polish-level ones on the last line) 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. @@ -1283,8 +1283,20 @@ Judgment tells (no detector rule; you are the detector): - 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. +- John Doe and Jane Doe in quotes, team rows and demo data. A supplied name, or one that fits the product. +- Acme, Nexus and SmartFlow as the customers in logos, quotes and sample data. A supplied customer, or none. +- Lorem ipsum where a visitor can read it. Write the draft copy. +- 99.9% uptime, 50% faster, 10x, 10,000+ users: round figures nobody measured. The supplied number, or no claim. +- A `[Needs proof: …]` marker still on the page. Supply the proof or cut the claim before it ships. +- Reveals driven by an unthrottled `scroll` listener. IntersectionObserver for reveals; scroll-linked effects read scroll inside requestAnimationFrame. +- `href="#"` on links and buttons that go nowhere. Link it, or render the control disabled. +- `alert()` for errors and confirmations. Show the message inline, next to what it is about. +- An `<img>` with no alt attribute. Describe a meaningful image; `alt=""` on a decorative one. +- A full HTML document with no `<title>`. The tab, the bookmark and the search result all read it. +- A full HTML document with no meta description. Search results and link previews make one up. +- No "Skip to content" link as the first focusable element, targeting `<main>`. Keyboard users tab through the whole header on every page. -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. +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, "oops" errors, 100vh sections, missing favicon, unmarked current page. **10. Performance as Design** (6 items) - LCP < 2.0s (web apps), < 1.5s (informational sites) @@ -1562,10 +1574,22 @@ Tie everything to user goals and product objectives. Always suggest specific imp **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. +**AI Slop blacklist:** the 11 legacy patterns, the 30 detector rules, and the 32 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. +## Section index — Read each section when its situation applies + +This skill is a decision-tree skeleton. The steps below point to on-demand +sections. Read a section in full before doing its step; do not work from memory. + +| When | Read this section | +|------|-------------------| +| auditing a Persuade page, or running a redesign or craft audit | `sections/craft-audit.md` | + +> **STOP.** Before auditing a Persuade page, or running a redesign or craft audit, Read `~/.claude/skills/gstack/design-review/sections/craft-audit.md` and execute it +> in full. Do not work from memory — that section is the source of truth for this step. + Record baseline design score and AI slop score at end of Phase 6. --- @@ -1747,7 +1771,7 @@ Detector findings carry their `[rule-id]`; a deferred one ends with its `handoff ## Phase 8: Fix Loop -For each fixable finding, in impact order: +For each fixable finding, in impact order (ties: the craft audit's fix order, when it ran): ### 8a. Locate source diff --git a/design-review/SKILL.md.tmpl b/design-review/SKILL.md.tmpl index 4e7960250..79586d0e1 100644 --- a/design-review/SKILL.md.tmpl +++ b/design-review/SKILL.md.tmpl @@ -119,6 +119,10 @@ Remember `RUN_ID` and restate it literally in later blocks (each bash block is a {{DESIGN_HARD_RULES}} +{{SECTION_INDEX:design-review}} + +{{SECTION:craft-audit}} + Record baseline design score and AI slop score at end of Phase 6. --- @@ -161,7 +165,7 @@ Detector findings carry their `[rule-id]`; a deferred one ends with its `handoff ## Phase 8: Fix Loop -For each fixable finding, in impact order: +For each fixable finding, in impact order (ties: the craft audit's fix order, when it ran): ### 8a. Locate source diff --git a/design-review/sections/craft-audit.md b/design-review/sections/craft-audit.md new file mode 100644 index 000000000..e14d2d1a2 --- /dev/null +++ b/design-review/sections/craft-audit.md @@ -0,0 +1,72 @@ +<!-- AUTO-GENERATED from craft-audit.md.tmpl — do not edit directly --> +<!-- Regenerate: bun run gen:skill-docs --> +## Craft audit: Persuade pages and redesigns + +Run this in Phases 1-6, on every audited page, when a page is Persuade (the classifier in the Design Hard Rules) or the user asked for a redesign or a craft audit. It adds what generated sites ship and a reviewer catches in seconds: invented content, missing ship basics, scroll-listener reveals, `100vh`, and a Persuade page's conversion basics. Its rules render from /design-html's craft section, so a page is audited against the rules it was built to. + +### 1. Run the craft check on each page + +With a URL, save the page in the script that opens it. Aside: add `await fs.writeFile(path.join(pwd, "{page}.page.html"), await pg.content());`. Fallback engine: `_TMP=$(mktemp -d "${TMPDIR:-/tmp}/gstack-craft.XXXXXX"); $B js 'document.documentElement.outerHTML' --out "$_TMP/{page}.page.html" --raw`. With no running app, use the page's HTML file in the repo. Then: + +```bash +~/.claude/skills/gstack/bin/gstack-design-craft <path-to-page.html> +``` + +Each `CRAFT_CHECK: <id> <count> <fix>` line is one category 9 finding under that id; `CRAFT_RESULT: clean` adds none. For a /design-html page with a `finalized.json` beside it, add `--accepted <finalized.json>`: each `CRAFT_ACCEPTED` line is a finding the user already accepted, listed with its reason, not filed again. Exit 2 (framework source, an empty or unreadable file): say the check was unavailable for that page and judge the tells below by eye. A hit the user's real content explains (a customer named Nexus, a supplied 50% figure) is noted, not filed. Saved pages stay out of the report directory; delete them after the check. + +### 2. What the check cannot see + +**Claims come from the user, never from you.** Testimonials, customer names and logos, results, uptime, prices and guarantees appear only when supplied. Missing proof: leave the slot out, or mark it `[Needs proof: …]` in the warning color so it cannot ship by accident. A believable invented claim is worse than an obvious placeholder. + +**Illustrative content looks real.** Sample data in product shots and UI (table rows, chart values, people in a demo inbox): +- No placeholder people or brands (John Doe, Jane Doe, Acme, Nexus, SmartFlow); distinct avatars; varied dates. +- No round sample numbers (99.99%, 50%, 1,000+ customers, 10x). In a claim, a round number signals it was made up: use the supplied figure or drop it. +- No buzzwords: elevate, seamless, effortless, supercharge, streamline, revolutionize, unlock, empower, unleash, next-gen, game-changer, delve, tapestry, "in the world of". +- Sentence-case headings. Errors say what happened and what to do, never "Oops!". No exclamation marks in success messages. + +**Ship floor.** Head tags apply to a full HTML document, not a component or fragment. Every full document has `<title>`, a meta description, a favicon (an inline SVG data URI from the brand mark when no file is supplied), and on a page meant for sharing `og:title` and `og:description`; `og:image` only from a supplied URL. A "Skip to content" link is the first focusable element, hidden until focused, targeting the `id` of `<main>`. Landmarks (`<header>`, `<nav>`, `<main>`, `<footer>`); alt text on meaningful images, `alt=""` on decorative ones. A marketing footer links privacy and terms at supplied URLs. Required fields and email format validate inline, wired with `aria-invalid` and `aria-describedby`. Every link goes somewhere real: no `href="#"`, no made-up path; a control with no target renders disabled. Navigation to other pages marks the current one `aria-current="page"`; in-page section links do not. + +Beyond the document: a made-up path returns a designed 404 with a way back, every page has a way out, and cookie consent appears where the jurisdiction requires it. + +**Motion.** These govern the one authored motion moment and state transitions; they license no more motion. +- Content is visible by default; script adds the hidden start state only after the observer is set up (`.motion-ready` on `<html>`), so a script failure or a crawler sees everything and the CTA never waits on an animation. +- Reveal with `IntersectionObserver` (`threshold: 0`, bottom `rootMargin` about −20%, once). Never an unthrottled `window.addEventListener('scroll')`; scroll-linked effects read scroll inside `requestAnimationFrame`. +- Animate `transform`, `opacity`, `filter`, `clip-path` only; blur 14px or less. +- No object grows from `scale(0)` (start at 0.85 or more); only lines and bars grow from zero. A data bar fills as a left-to-right `clip-path` wipe, 0.7–1.1s. +- Stagger in reading order inside one section (30–80ms), never across the page. +- Curves are named tokens: DESIGN.md's, else `cubic-bezier(0.16, 1, 0.3, 1)` for entrances and `cubic-bezier(0.65, 0, 0.35, 1)` for wipes; `linear` only for constant motion. +- Loops pause off-screen. `prefers-reduced-motion: reduce` shows the final state. Full-height sections use `min-height: 100dvh`, not `100vh`. + +### 3. Persuade pages + +A failed check costs the page its one job: file it as a High-impact finding. + +1. **One primary action**, named; nothing competes with it above the fold, and the closing CTA repeats it. +2. **Named objections**: the visitor's top objections, each answered where it arises (FAQ of real questions, comparison, guarantee). +3. **Proof beside each claim**: a supplied customer, quote or figure next to the claim it supports, never a metric row; an unsupported claim is cut or marked `[Needs proof: …]`. +4. **A risk reversal** the business really offers (free plan, trial, no card, cancel anytime, refund); never an invented one. +5. **An index/noindex decision**: an ad-only or time-bound page is `noindex`; an indexed one has a real title and description, and its FAQ ships as FAQPage JSON-LD. + +**Conversion rules.** +- A page behind an ad repeats the ad's promise and tone in the hero. +- One primary CTA; nothing competes with it above the fold. +- Benefit first and specific: "Cut weekly reporting from 4 hours to 15 minutes", never "save time". +- Show the risk reversal the business offers (free plan, trial, no card, cancel anytime). Never invent one. +- Proof sits beside the claim it supports. A high-friction offer moves the FAQ up. + +### 4. Fix order and stack (Phase 8) + +Within one impact level, take the biggest visible gain at the least risk first: +1. Typeface and type roles +2. Color and surfaces +3. Hover, active and focus states +4. Layout and spacing: grid, max width, the spacing scale, nested radius +5. Motion +6. Generic components +7. Loading, empty and error states +8. Copy +9. Type-scale polish + +Work inside the existing stack: no framework or styling migration, and check the dependency file before importing a library. If the project uses Tailwind, read the installed major version (`tailwindcss` in package.json or the lockfile) before touching its config: v3 configures in `tailwind.config.*`, v4 in CSS (`@import "tailwindcss"` and `@theme`). Write for that version only. + +Adapted from Elaya's [redesign-skill](https://github.com/elayadesign/redesign-skill) and [ai-design-skills](https://github.com/elayadesign/ai-design-skills) (MIT, © 2026 Elaya), rewritten for gstack. See NOTICE.md. diff --git a/design-review/sections/craft-audit.md.tmpl b/design-review/sections/craft-audit.md.tmpl new file mode 100644 index 000000000..da781318e --- /dev/null +++ b/design-review/sections/craft-audit.md.tmpl @@ -0,0 +1,50 @@ +## Craft audit: Persuade pages and redesigns + +Run this in Phases 1-6, on every audited page, when a page is Persuade (the classifier in the Design Hard Rules) or the user asked for a redesign or a craft audit. It adds what generated sites ship and a reviewer catches in seconds: invented content, missing ship basics, scroll-listener reveals, `100vh`, and a Persuade page's conversion basics. Its rules render from /design-html's craft section, so a page is audited against the rules it was built to. + +### 1. Run the craft check on each page + +With a URL, save the page in the script that opens it. Aside: add `await fs.writeFile(path.join(pwd, "{page}.page.html"), await pg.content());`. Fallback engine: `_TMP=$(mktemp -d "${TMPDIR:-/tmp}/gstack-craft.XXXXXX"); $B js 'document.documentElement.outerHTML' --out "$_TMP/{page}.page.html" --raw`. With no running app, use the page's HTML file in the repo. Then: + +```bash +{{BIN_DIR}}/gstack-design-craft <path-to-page.html> +``` + +Each `CRAFT_CHECK: <id> <count> <fix>` line is one category 9 finding under that id; `CRAFT_RESULT: clean` adds none. For a /design-html page with a `finalized.json` beside it, add `--accepted <finalized.json>`: each `CRAFT_ACCEPTED` line is a finding the user already accepted, listed with its reason, not filed again. Exit 2 (framework source, an empty or unreadable file): say the check was unavailable for that page and judge the tells below by eye. A hit the user's real content explains (a customer named Nexus, a supplied 50% figure) is noted, not filed. Saved pages stay out of the report directory; delete them after the check. + +### 2. What the check cannot see + +{{DESIGN_CRAFT_RULE:Claims come from the user}} + +{{DESIGN_CRAFT_RULE:Illustrative content looks real}} + +{{DESIGN_CRAFT_RULE:Ship floor}} + +Beyond the document: a made-up path returns a designed 404 with a way back, every page has a way out, and cookie consent appears where the jurisdiction requires it. + +{{DESIGN_CRAFT_RULE:Motion}} + +### 3. Persuade pages + +A failed check costs the page its one job: file it as a High-impact finding. + +{{DESIGN_PERSUADE_CHECKS}} + +{{DESIGN_CRAFT_RULE:Conversion rules}} + +### 4. Fix order and stack (Phase 8) + +Within one impact level, take the biggest visible gain at the least risk first: +1. Typeface and type roles +2. Color and surfaces +3. Hover, active and focus states +4. Layout and spacing: grid, max width, the spacing scale, nested radius +5. Motion +6. Generic components +7. Loading, empty and error states +8. Copy +9. Type-scale polish + +Work inside the existing stack: no framework or styling migration, and check the dependency file before importing a library. If the project uses Tailwind, read the installed major version (`tailwindcss` in package.json or the lockfile) before touching its config: v3 configures in `tailwind.config.*`, v4 in CSS (`@import "tailwindcss"` and `@theme`). Write for that version only. + +Adapted from Elaya's [redesign-skill](https://github.com/elayadesign/redesign-skill) and [ai-design-skills](https://github.com/elayadesign/ai-design-skills) (MIT, © 2026 Elaya), rewritten for gstack. See NOTICE.md. diff --git a/design-review/sections/manifest.json b/design-review/sections/manifest.json new file mode 100644 index 000000000..e0ae5e73f --- /dev/null +++ b/design-review/sections/manifest.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://gstack.dev/schemas/section-manifest.json", + "skill": "design-review", + "version": 1, + "note": "PASSIVE registry (v2 plan T9 / CM2). Fields are IDs, file paths, human titles, and human-readable trigger text ONLY. The skeleton's decision-tree prose is the ONLY place that decides WHEN to read a section. Carved on every host (CARVED_ON_EVERY_HOST in scripts/resolvers/sections.ts).", + "sections": [ + { + "id": "craft-audit", + "file": "craft-audit.md", + "title": "Craft audit: Persuade pages and redesigns", + "trigger": "auditing a Persuade page, or running a redesign or craft audit" + } + ] +} diff --git a/lib/design-catalog.ts b/lib/design-catalog.ts index da67f8ffb..c905c6d44 100644 --- a/lib/design-catalog.ts +++ b/lib/design-catalog.ts @@ -12,13 +12,14 @@ // │ design-html anti-slop line (catalogEntries) // ├─ scripts/resolvers/design-checklist.ts review/design-checklist.md (generated) // ├─ bin/gstack-design-detect.ts normalizes engine findings by impeccableId +// ├─ lib/design-craft.ts CRAFT_CHECK ids and their `fix` text (gstack-design-craft) // └─ design/src/brief.ts MOCKUP_NEVER_NAMES in the image-generation prompt // // Rule ids. An entry's `impeccableId` is set only when that id exists in // test/fixtures/impeccable-antipatterns.json (test-enforced), and rendered // prose brackets an id only in that case, so a reader never meets a bracketed // id the detector cannot emit. Everything else is a gstack-only tell that the -// LLM pass judges. The four lists this file replaced (constants.ts, the +// LLM pass judges (or, for an entry with a `fix`, the craft check counts). The four lists this file replaced (constants.ts, the // consultation proposal section, design-html's blacklist, and the review // checklist) had drifted apart; they now render from here. @@ -59,6 +60,8 @@ export interface DesignSlopEntry { /** roles the values are banned for; present iff values is */ roles?: FontRole[]; handoff?: Handoff; + /** one-line fix the free craft check prints beside this id (lib/design-craft.ts) */ + fix?: string; source: 'gstack' | 'impeccable' | 'both'; /** the 11 originals; AI_SLOP_BLACKLIST derives from these verbatim */ legacyBlacklist?: true; @@ -299,7 +302,7 @@ export const DESIGN_SLOP_CATALOG: DesignSlopEntry[] = [ prose: '"Seamless", "effortless", "supercharge", "streamline": words that describe nothing. Say what the product does.', category: 'copy', kind: 'slop', detect: ['engine', 'grep'], confidence: 'MEDIUM', tier: 'ask', impact: 'medium', heuristic: 'Grep visible copy for seamless, effortless, supercharge, streamline, revolutionize, unlock, empower, elevate.', - handoff: 'clarify', source: 'impeccable', + handoff: 'clarify', source: 'impeccable', fix: 'say the specific thing', }, { id: 'aphoristic-cadence', impeccableId: 'aphoristic-cadence', name: 'Aphoristic cadence', @@ -459,6 +462,122 @@ export const DESIGN_SLOP_CATALOG: DesignSlopEntry[] = [ handoff: 'harden', source: 'gstack', }, + // ── Craft tells (GSTA-92): grep-checkable; the free craft check (lib/design-craft.ts) + // prints each id with its `fix`. Adapted from Elaya's landing-page-design and + // redesign-skill (MIT); see NOTICE.md. + { + id: 'placeholder-name', name: 'Placeholder people', + prose: 'John Doe and Jane Doe in quotes, team rows and demo data. A supplied name, or one that fits the product.', + category: 'copy', kind: 'slop', detect: ['grep'], confidence: 'HIGH', tier: 'ask', impact: 'medium', + heuristic: 'Grep visible copy for "John Doe" and "Jane Doe".', + handoff: 'clarify', source: 'gstack', fix: 'use a supplied name, or one that fits the product in sample data', + }, + { + id: 'placeholder-brand', name: 'Placeholder brands', + prose: 'Acme, Nexus and SmartFlow as the customers in logos, quotes and sample data. A supplied customer, or none.', + category: 'copy', kind: 'slop', detect: ['grep'], confidence: 'MEDIUM', tier: 'ask', impact: 'medium', + heuristic: 'Grep visible copy for Acme, Nexus, SmartFlow.', + handoff: 'clarify', source: 'gstack', fix: 'use a supplied customer, or a fitting name in sample data', + }, + { + id: 'lorem', name: 'Lorem ipsum', + prose: 'Lorem ipsum where a visitor can read it. Write the draft copy.', + category: 'copy', kind: 'slop', detect: ['grep'], confidence: 'HIGH', tier: 'ask', impact: 'high', + heuristic: 'Grep visible copy for "lorem ipsum".', + handoff: 'clarify', source: 'gstack', fix: 'write real copy', + }, + { + id: 'round-number', name: 'Round sample numbers', + prose: '99.9% uptime, 50% faster, 10x, 10,000+ users: round figures nobody measured. The supplied number, or no claim.', + category: 'copy', kind: 'slop', detect: ['grep'], confidence: 'MEDIUM', tier: 'ask', impact: 'medium', + heuristic: 'Grep copy for 99.9%, a round percentage before faster, more or uptime, 10x, and "<n>0+ users".', + handoff: 'clarify', source: 'gstack', fix: 'use the supplied figure, or drop the claim', + }, + { + id: 'needs-proof', name: 'Unresolved proof marker', + prose: 'A `[Needs proof: …]` marker still on the page. Supply the proof or cut the claim before it ships.', + category: 'copy', kind: 'slop', detect: ['grep'], confidence: 'HIGH', tier: 'ask', impact: 'high', + heuristic: 'Grep for `[Needs proof:`.', + handoff: 'clarify', source: 'gstack', fix: 'supply the proof or remove the claim before publishing', + }, + { + id: 'oops', name: '"Oops" errors', + prose: '"Oops!" in place of what happened and what to do next.', + category: 'copy', kind: 'slop', detect: ['grep'], confidence: 'HIGH', tier: 'ask', impact: 'polish', + heuristic: 'Grep visible copy for "oops".', + handoff: 'clarify', source: 'gstack', fix: 'say what happened and what to do', + }, + { + id: 'scroll-listener', name: 'Scroll-listener reveals', + prose: 'Reveals driven by an unthrottled `scroll` listener. IntersectionObserver for reveals; scroll-linked effects read scroll inside requestAnimationFrame.', + category: 'motion', kind: 'slop', detect: ['grep'], confidence: 'HIGH', tier: 'ask', impact: 'medium', + heuristic: 'Grep scripts for `addEventListener(\'scroll\'`.', + handoff: 'animate', source: 'gstack', fix: 'use IntersectionObserver; read scroll inside requestAnimationFrame for a scroll-linked effect', + }, + { + id: 'dead-link', name: 'Dead links', + prose: '`href="#"` on links and buttons that go nowhere. Link it, or render the control disabled.', + category: 'states', kind: 'slop', detect: ['grep'], confidence: 'HIGH', tier: 'ask', impact: 'medium', + heuristic: 'Grep markup for `href="#"`.', + handoff: 'harden', source: 'gstack', fix: 'link it, or render the control disabled', + }, + { + id: 'alert', name: 'alert() messages', + prose: '`alert()` for errors and confirmations. Show the message inline, next to what it is about.', + category: 'states', kind: 'slop', detect: ['grep'], confidence: 'HIGH', tier: 'ask', impact: 'medium', + heuristic: 'Grep scripts and inline handlers for `alert(`.', + handoff: 'harden', source: 'gstack', fix: 'show the message inline', + }, + { + id: 'img-no-alt', name: 'Image without alt', + prose: 'An `<img>` with no alt attribute. Describe a meaningful image; `alt=""` on a decorative one.', + category: 'imagery', kind: 'slop', detect: ['grep'], confidence: 'HIGH', tier: 'ask', impact: 'medium', + heuristic: 'Grep markup for `<img` tags with no `alt=`.', + handoff: 'harden', source: 'gstack', fix: 'add alt text (alt="" when decorative)', + }, + { + id: 'vh-height', name: '100vh sections', + prose: 'Full-height sections on `100vh`, which jump under mobile browser chrome. `min-height: 100dvh`.', + category: 'layout', kind: 'slop', detect: ['grep'], confidence: 'HIGH', tier: 'ask', impact: 'polish', + heuristic: 'Grep CSS for `height: 100vh` with no later `100dvh`, and for `h-screen`.', + handoff: 'layout', source: 'gstack', fix: 'use min-height: 100dvh (a 100vh line followed by a 100dvh fallback is fine)', + }, + { + id: 'missing-title', name: 'Missing title', + prose: 'A full HTML document with no `<title>`. The tab, the bookmark and the search result all read it.', + category: 'browser-surface', kind: 'slop', detect: ['grep'], confidence: 'HIGH', tier: 'ask', impact: 'medium', + heuristic: 'Grep each full document for a non-empty `<title>`.', + handoff: 'harden', source: 'gstack', fix: 'add a <title>', + }, + { + id: 'missing-meta-description', name: 'Missing meta description', + prose: 'A full HTML document with no meta description. Search results and link previews make one up.', + category: 'browser-surface', kind: 'slop', detect: ['grep'], confidence: 'HIGH', tier: 'ask', impact: 'medium', + heuristic: 'Grep each full document for `<meta name="description"`.', + handoff: 'harden', source: 'gstack', fix: 'add <meta name="description">', + }, + { + id: 'missing-favicon', name: 'Missing favicon', + prose: 'No favicon, so every tab shows the browser default. A mark from the brand; an inline SVG when no file exists.', + category: 'browser-surface', kind: 'slop', detect: ['grep'], confidence: 'HIGH', tier: 'ask', impact: 'polish', + heuristic: 'Grep each full document for `<link rel="icon"`.', + handoff: 'polish', source: 'gstack', fix: 'add a favicon <link rel="icon">', + }, + { + id: 'missing-skip-link', name: 'Missing skip link', + prose: 'No "Skip to content" link as the first focusable element, targeting `<main>`. Keyboard users tab through the whole header on every page.', + category: 'scaffold', kind: 'slop', detect: ['grep'], confidence: 'HIGH', tier: 'ask', impact: 'medium', + heuristic: 'Check that the first focusable element in `<body>` links to the id on `<main>`.', + handoff: 'harden', source: 'gstack', fix: 'add a "Skip to content" link first in <body> whose #id exists on <main>', + }, + { + id: 'missing-aria-current', name: 'Unmarked current page', + prose: 'Route navigation with no `aria-current="page"` on the current link. Mark it and style it; in-page section links stay unmarked.', + category: 'states', kind: 'slop', detect: ['grep'], confidence: 'MEDIUM', tier: 'ask', impact: 'polish', + heuristic: 'Grep `<nav>` blocks with two or more route links for `aria-current="page"`.', + handoff: 'harden', source: 'gstack', fix: 'mark the current nav link aria-current="page"', + }, + // ── Quality rules the detector knows. ── { id: 'organic-clip-path', impeccableId: 'organic-clip-path', name: 'Organic clip-path', @@ -525,7 +644,7 @@ export const DESIGN_SLOP_CATALOG: DesignSlopEntry[] = [ prose: '`transition: all`, or transitions on width, height, top, left. Animate transform and opacity.', category: 'motion', kind: 'quality', detect: ['engine', 'grep'], confidence: 'HIGH', tier: 'auto-fix', impact: 'polish', heuristic: 'Grep `transition` for `all` or layout properties.', - handoff: 'animate', source: 'impeccable', + handoff: 'animate', source: 'impeccable', fix: 'list the transitioned properties', }, { id: 'line-length', impeccableId: 'line-length', name: 'Line length', diff --git a/lib/design-craft.ts b/lib/design-craft.ts index faa5a12dc..0fb7670a1 100644 --- a/lib/design-craft.ts +++ b/lib/design-craft.ts @@ -5,11 +5,14 @@ * tags. Copy checks read the visible text only (script, style and tags * removed); code checks read the source. Head checks run only on a full * document. Adapted from Elaya's landing-page-design and redesign-skill - * (MIT); see NOTICE.md. Buzzwords report under the catalog's - * `marketing-buzzword` id, and BUZZWORDS covers every word its heuristic - * names (test-enforced), so the two lists cannot drift. + * (MIT); see NOTICE.md. Every id it prints is a lib/design-catalog.ts entry + * and the fix beside it is that entry's `fix` (test-enforced): buzzwords + * report under `marketing-buzzword`, whose heuristic words BUZZWORDS covers, + * and `transition: all` under `layout-transition`. */ +import { catalogEntries } from './design-catalog'; + /** The catalog's marketing-buzzword words plus Elaya's AI cliché words. */ export const BUZZWORDS: readonly string[] = [ 'seamless', 'effortless', 'supercharge', 'streamline', 'revolutionize', 'unlock', 'empower', 'elevate', @@ -31,36 +34,45 @@ interface CountRule { id: string; on: 'source' | 'text' | 'script'; pattern: RegExp; - fix: string; } interface PresenceRule { id: string; pattern: RegExp; - fix: string; } const COUNT_RULES: CountRule[] = [ - { id: 'placeholder-name', on: 'text', pattern: /\b(?:John|Jane) Doe\b/gi, fix: 'use a supplied name, or one that fits the product in sample data' }, - { id: 'placeholder-brand', on: 'text', pattern: /\b(?:Acme|Nexus|SmartFlow)\b/g, fix: 'use a supplied customer, or a fitting name in sample data' }, - { id: 'lorem', on: 'text', pattern: /\blorem ipsum\b/gi, fix: 'write real copy' }, - { id: 'marketing-buzzword', on: 'text', pattern: buzzwordPattern(), fix: 'say the specific thing' }, - { id: 'round-number', on: 'text', pattern: /(?<![\d.])(?:99\.9+|[1-9]0|100)%\s+(?:uptime|faster|more|less|fewer|better|growth|increase|reduction|of (?:teams|users|customers|companies))\b|\b(?:\d+0|\d+(?:,\d{3})+)\+?\s+(?:happy |active )?(?:users|customers|teams|companies)\b|(?<![\d.])10x\s+(?:faster|more|better)\b/gi, fix: 'use the supplied figure, or drop the claim' }, - { id: 'needs-proof', on: 'text', pattern: /\[Needs proof:/g, fix: 'supply the proof or remove the claim before publishing' }, - { id: 'oops', on: 'text', pattern: /\boops\b/gi, fix: 'say what happened and what to do' }, - { id: 'scroll-listener', on: 'source', pattern: /addEventListener\(\s*['"`]scroll['"`]/g, fix: 'use IntersectionObserver; read scroll inside requestAnimationFrame for a scroll-linked effect' }, - { id: 'transition-all', on: 'source', pattern: /\btransition\s*:\s*all\b|\btransition-all\b/g, fix: 'list the transitioned properties' }, - { id: 'dead-link', on: 'source', pattern: /\bhref\s*=\s*["']#["']/g, fix: 'link it, or render the control disabled' }, - { id: 'alert', on: 'script', pattern: /(?<![\w.$])(?:window\.)?alert\s*\(/g, fix: 'show the message inline' }, - { id: 'img-no-alt', on: 'source', pattern: /<img\b(?![^>]*(?<![\w-])alt\s*=)[^>]*>/gi, fix: 'add alt text (alt="" when decorative)' }, + { id: 'placeholder-name', on: 'text', pattern: /\b(?:John|Jane) Doe\b/gi }, + { id: 'placeholder-brand', on: 'text', pattern: /\b(?:Acme|Nexus|SmartFlow)\b/g }, + { id: 'lorem', on: 'text', pattern: /\blorem ipsum\b/gi }, + { id: 'marketing-buzzword', on: 'text', pattern: buzzwordPattern() }, + { id: 'round-number', on: 'text', pattern: /(?<![\d.])(?:99\.9+|[1-9]0|100)%\s+(?:uptime|faster|more|less|fewer|better|growth|increase|reduction|of (?:teams|users|customers|companies))\b|\b(?:\d+0|\d+(?:,\d{3})+)\+?\s+(?:happy |active )?(?:users|customers|teams|companies)\b|(?<![\d.])10x\s+(?:faster|more|better)\b/gi }, + { id: 'needs-proof', on: 'text', pattern: /\[Needs proof:/g }, + { id: 'oops', on: 'text', pattern: /\boops\b/gi }, + { id: 'scroll-listener', on: 'source', pattern: /addEventListener\(\s*['"`]scroll['"`]/g }, + { id: 'layout-transition', on: 'source', pattern: /\btransition\s*:\s*all\b|\btransition-all\b/g }, + { id: 'dead-link', on: 'source', pattern: /\bhref\s*=\s*["']#["']/g }, + { id: 'alert', on: 'script', pattern: /(?<![\w.$])(?:window\.)?alert\s*\(/g }, + { id: 'img-no-alt', on: 'source', pattern: /<img\b(?![^>]*(?<![\w-])alt\s*=)[^>]*>/gi }, ]; const HEAD_RULES: PresenceRule[] = [ - { id: 'missing-title', pattern: /<title>\s*[^<\s][^<]*<\/title>/i, fix: 'add a <title>' }, - { id: 'missing-meta-description', pattern: /<meta\b[^>]*\bname\s*=\s*["']description["']/i, fix: 'add <meta name="description">' }, - { id: 'missing-favicon', pattern: /<link\b[^>]*\brel\s*=\s*["'][^"']*\bicon\b/i, fix: 'add a favicon <link rel="icon">' }, + { id: 'missing-title', pattern: /<title>\s*[^<\s][^<]*<\/title>/i }, + { id: 'missing-meta-description', pattern: /<meta\b[^>]*\bname\s*=\s*["']description["']/i }, + { id: 'missing-favicon', pattern: /<link\b[^>]*\brel\s*=\s*["'][^"']*\bicon\b/i }, ]; +/** Every id the check can print, in print order; each is a catalog entry with a fix (test-enforced). */ +export const CRAFT_IDS: readonly string[] = [ + ...COUNT_RULES.map(r => r.id), 'vh-height', ...HEAD_RULES.map(r => r.id), 'missing-skip-link', 'missing-aria-current', +]; + +function finding(id: string, count: number): CraftFinding { + const fix = catalogEntries([id])[0].fix; + if (!fix) throw new Error(`lib/design-catalog.ts: entry "${id}" has no craft-check fix`); + return { id, count, fix }; +} + export function visibleText(html: string): string { return html .replace(/<!--[\s\S]*?-->/g, ' ') @@ -78,15 +90,15 @@ export function checkCraft(html: string): CraftFinding[] { const findings: CraftFinding[] = []; for (const rule of COUNT_RULES) { const count = (rule.on === 'text' ? text : rule.on === 'script' ? scripts : html).match(rule.pattern)?.length ?? 0; - if (count > 0) findings.push({ id: rule.id, count, fix: rule.fix }); + if (count > 0) findings.push(finding(rule.id, count)); } const vh = [...html.matchAll(/\b((?:min-|max-)?height)\s*:\s*100vh\b(\s*!important)?([^}]*)/g)] .filter(m => m[2] || !new RegExp(`(?<![\\w-])${m[1]}\\s*:\\s*100[dsl]vh\\b`).test(m[3])).length + (html.match(/\b(?:min-)?h-screen\b/g)?.length ?? 0); - if (vh > 0) findings.push({ id: 'vh-height', count: vh, fix: 'use min-height: 100dvh (a 100vh line followed by a 100dvh fallback is fine)' }); + if (vh > 0) findings.push(finding('vh-height', vh)); if (!/<head\b/i.test(html)) return findings; for (const rule of HEAD_RULES) { - if (!rule.pattern.test(html)) findings.push({ id: rule.id, count: 1, fix: rule.fix }); + if (!rule.pattern.test(html)) findings.push(finding(rule.id, 1)); } const body = html.slice(html.search(/<body\b/i) + 1); const firstFocusable = body.match(/<(?:a\b[^>]*\bhref|button|input|select|textarea)\b[^>]*>/i)?.[0] ?? ''; @@ -94,11 +106,11 @@ export function checkCraft(html: string): CraftFinding[] { const main = html.match(/<main\b[^>]*(?<![\w-])id\s*=\s*["']([^"']+)["']/i)?.[1] ?? html.match(/<[a-z]+\b[^>]*\brole\s*=\s*["']main["'][^>]*(?<![\w-])id\s*=\s*["']([^"']+)["']/i)?.[1]; if (!skipTarget || skipTarget !== main) { - findings.push({ id: 'missing-skip-link', count: 1, fix: 'add a "Skip to content" link first in <body> whose #id exists on <main>' }); + findings.push(finding('missing-skip-link', 1)); } const routeLinks = html.match(/<nav\b[\s\S]*?<\/nav>/gi)?.join('').match(/<a\b[^>]*\bhref\s*=\s*["'](?!#)[^"']+["']/gi)?.length ?? 0; if (routeLinks >= 2 && !/\baria-current\s*=\s*["']page["']/i.test(html)) { - findings.push({ id: 'missing-aria-current', count: 1, fix: 'mark the current nav link aria-current="page"' }); + findings.push(finding('missing-aria-current', 1)); } return findings; } diff --git a/plan-design-review/sections/checklist.md b/plan-design-review/sections/checklist.md index 57d62d563..77f86b7b0 100644 --- a/plan-design-review/sections/checklist.md +++ b/plan-design-review/sections/checklist.md @@ -9,7 +9,8 @@ Design phase steps and required outputs for `bin/gstack-autoplan` (unattended or 1. Scope is agreed and DESIGN.md (when present) is read; carry prior decisions. 2. Run Passes 1–7 in order, never condensed: information architecture, interaction states, journey and emotional arc, AI slop risk, design system - alignment, responsive and accessibility, unresolved design decisions. + alignment, responsive and accessibility, unresolved design decisions. A + Persuade plan also gets Pass 1's five conversion checks. 3. Post-pass: update generated mockups when any exist. 4. One decision cycle per unresolved finding; then the required outputs below. diff --git a/plan-design-review/sections/checklist.md.tmpl b/plan-design-review/sections/checklist.md.tmpl index d5ae10109..12c0ca59f 100644 --- a/plan-design-review/sections/checklist.md.tmpl +++ b/plan-design-review/sections/checklist.md.tmpl @@ -7,7 +7,8 @@ Design phase steps and required outputs for `bin/gstack-autoplan` (unattended or 1. Scope is agreed and DESIGN.md (when present) is read; carry prior decisions. 2. Run Passes 1–7 in order, never condensed: information architecture, interaction states, journey and emotional arc, AI slop risk, design system - alignment, responsive and accessibility, unresolved design decisions. + alignment, responsive and accessibility, unresolved design decisions. A + Persuade plan also gets Pass 1's five conversion checks. 3. Post-pass: update generated mockups when any exist. 4. One decision cycle per unresolved finding; then the required outputs below. diff --git a/plan-design-review/sections/review-sections.md b/plan-design-review/sections/review-sections.md index df90b2cc7..7f53dfece 100644 --- a/plan-design-review/sections/review-sections.md +++ b/plan-design-review/sections/review-sections.md @@ -80,6 +80,12 @@ A faithful restatement of an existing or already approved contract, such as a wi ### Pass 1: Information Architecture Rate 0-10: Does the plan define what the user sees first, second, third? FIX TO 10: Add information hierarchy to the plan. Include ASCII diagram of screen/page structure and navigation flow. Apply "constraint worship" — if you can only show 3 things, which 3? +**Persuade plan** (a landing, marketing, pricing, waitlist, campaign or comparison page; the Pass 4 classifier): the plan settles each check below, or the gap is a Pass 1 finding. Ask the user for missing proof and risk terms; never invent them. +1. **One primary action**, named; nothing competes with it above the fold, and the closing CTA repeats it. +2. **Named objections**: the visitor's top objections, each answered where it arises (FAQ of real questions, comparison, guarantee). +3. **Proof beside each claim**: a supplied customer, quote or figure next to the claim it supports, never a metric row; an unsupported claim is cut or marked `[Needs proof: …]`. +4. **A risk reversal** the business really offers (free plan, trial, no card, cancel anytime, refund); never an invented one. +5. **An index/noindex decision**: an ad-only or time-bound page is `noindex`; an indexed one has a real title and description, and its FAQ ships as FAQPage JSON-LD. **STOP.** AskUserQuestion once per issue. Do NOT batch. Recommend + WHY. If no issues, say so and move on. Do NOT proceed until user responds. ### Pass 2: Interaction State Coverage @@ -208,7 +214,7 @@ Review these as UI requirements in the plan, approved mockups, and referenced ex 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. +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, placeholder people, placeholder brands, lorem ipsum, round sample numbers, unresolved proof marker, "oops" errors, scroll-listener reveals, dead links, alert() messages, image without alt, 100vh sections, missing title, missing meta description, missing favicon, missing skip link, unmarked current page. 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. @@ -217,6 +223,7 @@ FIX TO 10: Rewrite vague UI descriptions with specific alternatives: - "Hero section" → what makes this hero feel like THIS product? - "Clean, modern UI" → meaningless. Replace with actual design decisions. - "Dashboard with widgets" → what makes this NOT every other dashboard? +- "Trusted by thousands", "99.9% uptime" → on a Persuade plan, supplied proof beside the claim (Pass 1, check 3), or no claim. If visual mockups were generated in Step 0.5, evaluate them against the AI slop blacklist above. Read each mockup image using the Read tool. Does the mockup fall into generic patterns (3-column grid, centered hero, stock-photo feel)? If so, flag it and offer to regenerate with more specific direction via `$D iterate --feedback "$(cat "$FEEDBACK_FILE")"` (the feedback in a private file, never inline); use its printed `outputPath` for any later view, check or board. **STOP.** AskUserQuestion once per issue. Do NOT batch. Recommend + WHY. diff --git a/plan-design-review/sections/review-sections.md.tmpl b/plan-design-review/sections/review-sections.md.tmpl index fb2d7b3e8..f4cb0e50e 100644 --- a/plan-design-review/sections/review-sections.md.tmpl +++ b/plan-design-review/sections/review-sections.md.tmpl @@ -41,6 +41,8 @@ A faithful restatement of an existing or already approved contract, such as a wi ### Pass 1: Information Architecture Rate 0-10: Does the plan define what the user sees first, second, third? FIX TO 10: Add information hierarchy to the plan. Include ASCII diagram of screen/page structure and navigation flow. Apply "constraint worship" — if you can only show 3 things, which 3? +**Persuade plan** (a landing, marketing, pricing, waitlist, campaign or comparison page; the Pass 4 classifier): the plan settles each check below, or the gap is a Pass 1 finding. Ask the user for missing proof and risk terms; never invent them. +{{DESIGN_PERSUADE_CHECKS}} **STOP.** AskUserQuestion once per issue. Do NOT batch. Recommend + WHY. If no issues, say so and move on. Do NOT proceed until user responds. ### Pass 2: Interaction State Coverage @@ -79,6 +81,7 @@ FIX TO 10: Rewrite vague UI descriptions with specific alternatives: - "Hero section" → what makes this hero feel like THIS product? - "Clean, modern UI" → meaningless. Replace with actual design decisions. - "Dashboard with widgets" → what makes this NOT every other dashboard? +- "Trusted by thousands", "99.9% uptime" → on a Persuade plan, supplied proof beside the claim (Pass 1, check 3), or no claim. If visual mockups were generated in Step 0.5, evaluate them against the AI slop blacklist above. Read each mockup image using the Read tool. Does the mockup fall into generic patterns (3-column grid, centered hero, stock-photo feel)? If so, flag it and offer to regenerate with more specific direction via `$D iterate --feedback "$(cat "$FEEDBACK_FILE")"` (the feedback in a private file, never inline); use its printed `outputPath` for any later view, check or board. **STOP.** AskUserQuestion once per issue. Do NOT batch. Recommend + WHY. diff --git a/review/design-checklist.md b/review/design-checklist.md index c2e16b66e..aa9452d3f 100644 --- a/review/design-checklist.md +++ b/review/design-checklist.md @@ -85,7 +85,7 @@ If no frontend files changed: skip silently, no output. ## Categories -### 1. AI Slop Detection (27 items) — highest priority +### 1. AI Slop Detection (43 items) — highest priority These are the telltale signs of AI-generated UI that no designer at a respected studio would ship. @@ -103,6 +103,32 @@ These are the telltale signs of AI-generated UI that no designer at a respected - **[HIGH]** [extreme-negative-tracking] Letter-spacing below -0.04em on display type. Tight tracking is a taste; crushed tracking is a tell. Grep `letter-spacing` for values below -0.04em. +- **[HIGH]** John Doe and Jane Doe in quotes, team rows and demo data. A supplied name, or one that fits the product. Grep visible copy for "John Doe" and "Jane Doe". + +- **[HIGH]** Lorem ipsum where a visitor can read it. Write the draft copy. Grep visible copy for "lorem ipsum". + +- **[HIGH]** A `[Needs proof: …]` marker still on the page. Supply the proof or cut the claim before it ships. Grep for `[Needs proof:`. + +- **[HIGH]** "Oops!" in place of what happened and what to do next. Grep visible copy for "oops". + +- **[HIGH]** Reveals driven by an unthrottled `scroll` listener. IntersectionObserver for reveals; scroll-linked effects read scroll inside requestAnimationFrame. Grep scripts for `addEventListener('scroll'`. + +- **[HIGH]** `href="#"` on links and buttons that go nowhere. Link it, or render the control disabled. Grep markup for `href="#"`. + +- **[HIGH]** `alert()` for errors and confirmations. Show the message inline, next to what it is about. Grep scripts and inline handlers for `alert(`. + +- **[HIGH]** An `<img>` with no alt attribute. Describe a meaningful image; `alt=""` on a decorative one. Grep markup for `<img` tags with no `alt=`. + +- **[HIGH]** Full-height sections on `100vh`, which jump under mobile browser chrome. `min-height: 100dvh`. Grep CSS for `height: 100vh` with no later `100dvh`, and for `h-screen`. + +- **[HIGH]** A full HTML document with no `<title>`. The tab, the bookmark and the search result all read it. Grep each full document for a non-empty `<title>`. + +- **[HIGH]** A full HTML document with no meta description. Search results and link previews make one up. Grep each full document for `<meta name="description"`. + +- **[HIGH]** No favicon, so every tab shows the browser default. A mark from the brand; an inline SVG when no file exists. Grep each full document for `<link rel="icon"`. + +- **[HIGH]** No "Skip to content" link as the first focusable element, targeting `<main>`. Keyboard users tab through the whole header on every page. Check that the first focusable element in `<body>` links to the id on `<main>`. + - **[MEDIUM]** [ai-color-palette] Purple/violet/indigo gradient backgrounds or blue-to-purple color schemes. Look for `linear-gradient` with values in the `#6366f1` to `#8b5cf6` range, or CSS custom properties resolving to purple/violet. - **[MEDIUM]** Centered everything (`text-align: center` on all headings, descriptions, cards). Grep for `text-align: center` density: if more than 60% of text containers center, flag it. @@ -135,6 +161,12 @@ These are the telltale signs of AI-generated UI that no designer at a respected - **[MEDIUM]** Selection color, caret, scrollbars, focus rings, underline offset, tabular numerals left at browser defaults. Theme them from the palette. Grep for `::selection`, `caret-color`, `accent-color`, `scrollbar-color`, `text-underline-offset`, `font-variant-numeric`: none present means none themed. +- **[MEDIUM]** Acme, Nexus and SmartFlow as the customers in logos, quotes and sample data. A supplied customer, or none. Grep visible copy for Acme, Nexus, SmartFlow. + +- **[MEDIUM]** 99.9% uptime, 50% faster, 10x, 10,000+ users: round figures nobody measured. The supplied number, or no claim. Grep copy for 99.9%, a round percentage before faster, more or uptime, 10x, and "<n>0+ users". + +- **[MEDIUM]** Route navigation with no `aria-current="page"` on the current link. Mark it and style it; in-page section links stay unmarked. Grep `<nav>` blocks with two or more route links for `aria-current="page"`. + - **[LOW]** **The 3-column feature grid:** icon-in-colored-circle + bold title + 2-line description, repeated 3x symmetrically. THE most recognizable AI layout. Look for a grid/flex container with exactly 3 children that each contain a circular element + heading + paragraph. - **[LOW]** Icons in colored circles as section decoration (SaaS starter template look). Look for elements with `border-radius: 50%` + a background color used as decorative containers for icons. diff --git a/scripts/resolvers/design-craft.ts b/scripts/resolvers/design-craft.ts new file mode 100644 index 000000000..22bdf5a8f --- /dev/null +++ b/scripts/resolvers/design-craft.ts @@ -0,0 +1,31 @@ +/** + * Craft resolvers shared by /design-review's craft audit and /plan-design-review. + * + * {{DESIGN_CRAFT_RULE:<lead>}} renders one paragraph of design-html's craft + * section verbatim, found by its bold lead (`**Ship floor.**`), with the + * bullets under it, so the audit holds a page to the rules /design-html builds + * it to and the two cannot drift. A renamed lead fails generation. + * + * {{DESIGN_PERSUADE_CHECKS}} is the five conversion checks, phrased to hold for + * a built page and for a plan of one. Adapted from Elaya's landing-page-design + * and redesign-skill (MIT); see NOTICE.md. + */ +import * as fs from 'fs'; +import * as path from 'path'; +import type { ResolverFn } from './types'; + +const CRAFT_SECTION = path.resolve(import.meta.dir, '../../design-html/sections/craft.md.tmpl'); + +export const DESIGN_CRAFT_RULE: ResolverFn = (_ctx, args) => { + const lead = args?.join(':'); + if (!lead) throw new Error('{{DESIGN_CRAFT_RULE:<lead>}} requires the bold lead of a craft paragraph'); + const paragraph = fs.readFileSync(CRAFT_SECTION, 'utf-8').split(/\n{2,}/).find(p => p.startsWith(`**${lead}`)); + if (!paragraph) throw new Error(`{{DESIGN_CRAFT_RULE:${lead}}}: no paragraph starting "**${lead}" in design-html/sections/craft.md.tmpl`); + return paragraph.trimEnd(); +}; + +export const DESIGN_PERSUADE_CHECKS: ResolverFn = () => `1. **One primary action**, named; nothing competes with it above the fold, and the closing CTA repeats it. +2. **Named objections**: the visitor's top objections, each answered where it arises (FAQ of real questions, comparison, guarantee). +3. **Proof beside each claim**: a supplied customer, quote or figure next to the claim it supports, never a metric row; an unsupported claim is cut or marked \`[Needs proof: …]\`. +4. **A risk reversal** the business really offers (free plan, trial, no card, cancel anytime, refund); never an invented one. +5. **An index/noindex decision**: an ad-only or time-bound page is \`noindex\`; an indexed one has a real title and description, and its FAQ ships as FAQPage JSON-LD.`; diff --git a/scripts/resolvers/index.ts b/scripts/resolvers/index.ts index d02bdae93..de019d89a 100644 --- a/scripts/resolvers/index.ts +++ b/scripts/resolvers/index.ts @@ -39,6 +39,7 @@ import { generateDxFramework, generateDxErrorCompact, generateDxErrorItems } fro import { generateGBrainContextLoad, generateGBrainSaveResults, generateBrainPreflight, generateBrainCacheRefresh, generateBrainWriteBack } from './gbrain'; import { generateTasksSectionEmit, generateTasksSectionAggregate } from './tasks-section'; import { SECTION, SECTION_INDEX } from './sections'; +import { DESIGN_CRAFT_RULE, DESIGN_PERSUADE_CHECKS } from './design-craft'; import { generateRedactInvocationBlock } from './redact-doc'; import { generateFreeTextFile } from './free-text-file'; import { FOREGROUND_DISPATCH_NOTE, codexSelect } from './constants'; @@ -109,6 +110,8 @@ export const RESOLVERS: Record<string, ResolverFn> = { DESIGN_DETECTOR: generateDesignDetector, DESIGN_MD_CHECK: generateDesignMdCheck, DESIGN_SLOP_BULLETS: generateDesignSlopBullets, + DESIGN_CRAFT_RULE, + DESIGN_PERSUADE_CHECKS, UX_PRINCIPLES: generateUXPrinciples, DESIGN_OUTSIDE_VOICES: generateDesignOutsideVoices, DESIGN_REVIEW_LITE: generateDesignReviewLite, diff --git a/scripts/resolvers/sections.ts b/scripts/resolvers/sections.ts index b7b15e203..38a274482 100644 --- a/scripts/resolvers/sections.ts +++ b/scripts/resolvers/sections.ts @@ -27,7 +27,7 @@ export const QA_ASSET_BLOCKER = 'If missing or unreadable, report a QA setup blo /** Skills carved on every host: QA (portable assets) and those whose external * render would exceed SKILL_BYTE_CEILING inlined (C4, #2777). */ -const CARVED_ON_EVERY_HOST = ['qa', 'qa-only', 'ship', 'plan-ceo-review', 'office-hours']; +const CARVED_ON_EVERY_HOST = ['qa', 'qa-only', 'ship', 'plan-ceo-review', 'office-hours', 'design-review']; /** Hosts read at most this many UTF-8 bytes of one SKILL.md (~40K tokens). */ export const SKILL_BYTE_CEILING = 160_000; diff --git a/test/design-craft.test.ts b/test/design-craft.test.ts index a1678f9bf..0e790f0ed 100644 --- a/test/design-craft.test.ts +++ b/test/design-craft.test.ts @@ -2,7 +2,7 @@ import { describe, expect, test } from 'bun:test'; import * as fs from 'fs'; import * as os from 'os'; import * as path from 'path'; -import { acceptFindings, BUZZWORDS, checkCraft, formatCraft, parseCraftExceptions, visibleText } from '../lib/design-craft'; +import { acceptFindings, BUZZWORDS, CRAFT_IDS, checkCraft, formatCraft, parseCraftExceptions, visibleText } from '../lib/design-craft'; import { catalogEntry } from '../lib/design-catalog'; const ROOT = path.resolve(import.meta.dir, '..'); @@ -31,7 +31,7 @@ describe('design-craft check', () => { const counts = Object.fromEntries(checkCraft(DIRTY).map(f => [f.id, f.count])); expect(counts).toEqual({ 'placeholder-name': 1, 'placeholder-brand': 1, lorem: 1, 'marketing-buzzword': 2, 'round-number': 3, 'needs-proof': 1, oops: 1, - 'scroll-listener': 1, 'transition-all': 1, 'dead-link': 1, alert: 1, 'img-no-alt': 1, 'vh-height': 1, + 'scroll-listener': 1, 'layout-transition': 1, 'dead-link': 1, alert: 1, 'img-no-alt': 1, 'vh-height': 1, 'missing-title': 1, 'missing-meta-description': 1, 'missing-favicon': 1, 'missing-skip-link': 1, 'missing-aria-current': 1, }); @@ -44,6 +44,19 @@ describe('design-craft check', () => { expect(formatCraft([])).toBe('CRAFT_RESULT: clean'); }); + test('every id the check prints is a grep-detectable catalog entry, and the fix it prints is the catalog fix', () => { + expect(new Set(CRAFT_IDS).size).toBe(CRAFT_IDS.length); + for (const id of CRAFT_IDS) { + const entry = catalogEntry(id); + expect(entry, id).toBeDefined(); + expect(entry!.detect, id).toContain('grep'); + expect(entry!.fix?.length ?? 0, id).toBeGreaterThan(0); + } + const findings = checkCraft(DIRTY); + expect(findings.map(f => f.id)).toEqual([...CRAFT_IDS]); + for (const f of findings) expect(f.fix).toBe(catalogEntry(f.id)!.fix!); + }); + test('BUZZWORDS covers every word the catalog marketing-buzzword heuristic names', () => { const heuristic = catalogEntry('marketing-buzzword')?.heuristic ?? ''; const words = heuristic.replace(/^.*?for /, '').replace(/\.$/, '').split(/,\s*/); diff --git a/test/external-host-carve.test.ts b/test/external-host-carve.test.ts index 3051785b0..fd440186b 100644 --- a/test/external-host-carve.test.ts +++ b/test/external-host-carve.test.ts @@ -68,7 +68,7 @@ describe('C4: external section pointers', () => { describe('C4: ship, plan-ceo-review and office-hours are carved on every external host', () => { for (const config of EXTERNAL) { test(`${config.name}: every pointer in the carved skill resolves to a generated section file`, () => { - for (const skill of ['ship', 'plan-ceo-review', 'office-hours']) { + for (const skill of ['ship', 'plan-ceo-review', 'office-hours', 'design-review']) { expect(usesLazySections(config.name, skill)).toBe(true); const dir = skillDir(config, renders.all, skill); const manifest = JSON.parse(fs.readFileSync(path.join(ROOT, skill, 'sections', 'manifest.json'), 'utf8')) as { sections: Array<{ file: string }> }; diff --git a/test/fixtures/skill-md-size-allowlist.json b/test/fixtures/skill-md-size-allowlist.json index 948752968..33a34b86b 100644 --- a/test/fixtures/skill-md-size-allowlist.json +++ b/test/fixtures/skill-md-size-allowlist.json @@ -3,91 +3,91 @@ "limit": 100000, "pins": { "agy/gstack-autoplan": 130504, - "agy/gstack-design-consultation": 108184, - "agy/gstack-design-review": 143528, + "agy/gstack-design-consultation": 109443, + "agy/gstack-design-review": 145569, "agy/gstack-land-and-deploy": 117722, - "agy/gstack-plan-design-review": 140013, + "agy/gstack-plan-design-review": 141400, "agy/gstack-plan-devex-review": 135760, "agy/gstack-plan-eng-review": 152626, "agy/gstack-review": 124218, - "claude/design-review": 136531, + "claude/design-review": 138542, "codex/gstack-autoplan": 130628, - "codex/gstack-design-consultation": 109452, - "codex/gstack-design-review": 145028, + "codex/gstack-design-consultation": 110711, + "codex/gstack-design-review": 147069, "codex/gstack-land-and-deploy": 122272, "codex/gstack-office-hours": 102339, - "codex/gstack-plan-design-review": 142466, + "codex/gstack-plan-design-review": 143853, "codex/gstack-plan-devex-review": 138649, "codex/gstack-plan-eng-review": 155172, "codex/gstack-review": 125122, "copilot/gstack-autoplan": 131637, "copilot/gstack-codex": 100235, - "copilot/gstack-design-consultation": 109536, - "copilot/gstack-design-review": 144833, + "copilot/gstack-design-consultation": 110795, + "copilot/gstack-design-review": 146874, "copilot/gstack-land-and-deploy": 118977, "copilot/gstack-office-hours": 100495, - "copilot/gstack-plan-design-review": 140884, + "copilot/gstack-plan-design-review": 142271, "copilot/gstack-plan-devex-review": 136912, "copilot/gstack-plan-eng-review": 153351, "copilot/gstack-review": 125489, "cursor/gstack-autoplan": 129278, - "cursor/gstack-design-consultation": 107222, - "cursor/gstack-design-review": 142507, + "cursor/gstack-design-consultation": 108481, + "cursor/gstack-design-review": 144548, "cursor/gstack-land-and-deploy": 116645, - "cursor/gstack-plan-design-review": 138968, + "cursor/gstack-plan-design-review": 140355, "cursor/gstack-plan-devex-review": 134591, "cursor/gstack-plan-eng-review": 151480, "cursor/gstack-review": 140750, "factory/gstack-autoplan": 131562, "factory/gstack-codex": 100655, - "factory/gstack-design-consultation": 108670, - "factory/gstack-design-review": 144297, + "factory/gstack-design-consultation": 109929, + "factory/gstack-design-review": 146338, "factory/gstack-land-and-deploy": 120817, "factory/gstack-office-hours": 101374, - "factory/gstack-plan-design-review": 141749, + "factory/gstack-plan-design-review": 143136, "factory/gstack-plan-devex-review": 137960, "factory/gstack-plan-eng-review": 154594, "factory/gstack-review": 144529, "gbrain/gstack-autoplan": 129815, - "gbrain/gstack-design-review": 132842, + "gbrain/gstack-design-review": 134883, "gbrain/gstack-land-and-deploy": 117167, - "gbrain/gstack-plan-design-review": 127855, + "gbrain/gstack-plan-design-review": 129242, "gbrain/gstack-plan-devex-review": 115129, "gbrain/gstack-plan-eng-review": 134212, "hermes/gstack-autoplan": 129905, - "hermes/gstack-design-review": 132949, + "hermes/gstack-design-review": 134990, "hermes/gstack-land-and-deploy": 117269, - "hermes/gstack-plan-design-review": 127964, + "hermes/gstack-plan-design-review": 129351, "hermes/gstack-plan-devex-review": 115215, "hermes/gstack-plan-eng-review": 134297, "kiro/gstack-autoplan": 129040, - "kiro/gstack-design-consultation": 107064, - "kiro/gstack-design-review": 142321, + "kiro/gstack-design-consultation": 108323, + "kiro/gstack-design-review": 144362, "kiro/gstack-land-and-deploy": 116533, - "kiro/gstack-plan-design-review": 138808, + "kiro/gstack-plan-design-review": 140195, "kiro/gstack-plan-devex-review": 134431, "kiro/gstack-plan-eng-review": 151330, "kiro/gstack-review": 140562, "openclaw/gstack-autoplan": 129520, - "openclaw/gstack-design-review": 131647, + "openclaw/gstack-design-review": 133688, "openclaw/gstack-land-and-deploy": 116757, - "openclaw/gstack-plan-design-review": 126706, + "openclaw/gstack-plan-design-review": 128093, "openclaw/gstack-plan-devex-review": 113968, "openclaw/gstack-plan-eng-review": 132609, "opencode/gstack-autoplan": 131640, - "opencode/gstack-design-consultation": 109357, - "opencode/gstack-design-review": 144705, + "opencode/gstack-design-consultation": 110616, + "opencode/gstack-design-review": 146746, "opencode/gstack-land-and-deploy": 118762, "opencode/gstack-office-hours": 100329, - "opencode/gstack-plan-design-review": 140738, + "opencode/gstack-plan-design-review": 142125, "opencode/gstack-plan-devex-review": 136812, "opencode/gstack-plan-eng-review": 153232, "opencode/gstack-review": 142985, "slate/gstack-autoplan": 129159, - "slate/gstack-design-consultation": 107143, - "slate/gstack-design-review": 142414, + "slate/gstack-design-consultation": 108402, + "slate/gstack-design-review": 144455, "slate/gstack-land-and-deploy": 116589, - "slate/gstack-plan-design-review": 138888, + "slate/gstack-plan-design-review": 140275, "slate/gstack-plan-devex-review": 134511, "slate/gstack-plan-eng-review": 151405, "slate/gstack-review": 140656 diff --git a/test/helpers/carve-guards.ts b/test/helpers/carve-guards.ts index d61dec498..dff8a0191 100644 --- a/test/helpers/carve-guards.ts +++ b/test/helpers/carve-guards.ts @@ -413,7 +413,7 @@ do not launch the downstream skill or open a browser.`, // eureka log, the office-hours lookup and the taste-profile read; measured // 1.0834 (2026-09-30). + named design-direction defaults to avoid in the native // subagent prompt (s02 H4a); measured 1.0911 (2026-10-02). - maxSizeRatio: 1.1600, // + v1.91.19.0 wave: design --version probe (B4), taste-profile load (F3), verdict-form outside voice (INV-1), plus v1.91.17.0's round accounting and board/approval blocks (#3022) merged in; measured 100,446 / 90,375 = 1.1114 (2026-10-03). + Oct 6 fix wave A3/CEO-12: free text reaches commands only through agent-written mktemp files (mktemp block, write rule, path rebuild and refusal at each posting, prompt and brief site); measured 103,384 / 90,375 = 1.1439 (2026-10-06). + Oct 7 wave B6: Aside rules 1, 4, 6 and 7 (offer a target-origin tab before openTab, a second sign-in wall means a tab- or URL-bound session, no early-return hint, fast [ok without the sentinel is an abort); measured 103,576 / 90,375 = 1.1461 (2026-10-07). + Oct 7 wave B3: the Codex probe runs as a command (select-model capture lines, check-sandbox/probe-model/run-with-timeout subcommands) instead of being sourced; measured 103,522 / 90,375 = 1.1455 (2026-10-07). Both merged: measured 103,726 / 90,375 = 1.1477 (2026-10-07). + Oct 7 wave guard (CEO-20): conditional `run_in_background` request plus the background-run recovery at each foreground dispatch site; measured 103,466 / 90,375 = 1.1449 (2026-10-07). Merged with the guard area: measured 103,804 / 90,375 = 1.1486 (2026-10-07). + Honest Work wave merged (W2/W3 gate integrity in review/ship, W4 kernel + DONE line in every tier-2+ skill, W5/W7 in spec/ship): re-measured on the integrated tree (2026-10-10). + maxSizeRatio: 1.1694, // + GSTA-92 Phase 2: the craft tells join the catalog's slop bullets in the proposal section (Garry 2026-10-25 'Do it' (GSTA-92)); measured 105,683 / 90,375 = 1.1694 (2026-10-25), from 104,422 / 90,375 = 1.1554. + v1.91.19.0 wave: design --version probe (B4), taste-profile load (F3), verdict-form outside voice (INV-1), plus v1.91.17.0's round accounting and board/approval blocks (#3022) merged in; measured 100,446 / 90,375 = 1.1114 (2026-10-03). + Oct 6 fix wave A3/CEO-12: free text reaches commands only through agent-written mktemp files (mktemp block, write rule, path rebuild and refusal at each posting, prompt and brief site); measured 103,384 / 90,375 = 1.1439 (2026-10-06). + Oct 7 wave B6: Aside rules 1, 4, 6 and 7 (offer a target-origin tab before openTab, a second sign-in wall means a tab- or URL-bound session, no early-return hint, fast [ok without the sentinel is an abort); measured 103,576 / 90,375 = 1.1461 (2026-10-07). + Oct 7 wave B3: the Codex probe runs as a command (select-model capture lines, check-sandbox/probe-model/run-with-timeout subcommands) instead of being sourced; measured 103,522 / 90,375 = 1.1455 (2026-10-07). Both merged: measured 103,726 / 90,375 = 1.1477 (2026-10-07). + Oct 7 wave guard (CEO-20): conditional `run_in_background` request plus the background-run recovery at each foreground dispatch site; measured 103,466 / 90,375 = 1.1449 (2026-10-07). Merged with the guard area: measured 103,804 / 90,375 = 1.1486 (2026-10-07). + Honest Work wave merged (W2/W3 gate integrity in review/ship, W4 kernel + DONE line in every tier-2+ skill, W5/W7 in spec/ship): re-measured on the integrated tree (2026-10-10). }, cso: { skill: 'cso', @@ -732,6 +732,31 @@ do not launch the downstream skill or open a browser.`, mustContain: ['retrospective', '45-minute gap', 'Ship of the week', 'Praise'], }, + // ── GSTA-92 Phase 2 (2026-10-25): design-review carved on every host ── + // One lazy section, the craft audit, read for a Persuade page or a redesign; + // CARVED_ON_EVERY_HOST in scripts/resolvers/sections.ts keeps it out of the + // external skeletons too (Garry 2026-10-25 'Do it' (GSTA-92), Big decision 2a). + 'design-review': { + skill: 'design-review', + expectedSections: ['craft-audit.md'], + requiredReads: ['craft-audit.md'], + scenario: + 'Walk /design-review in SIMULATION — do not run bash, launch a browser, or edit files. The target is the landing page of a developer-tools SaaS (a Persuade page): treat Setup and Phase 1 as done. Read each pointed section before doing its step, then list the Phases 1-6 checks you would run on that page, including any section-specific check, and stop before Phase 7.', + staticInvariants: { + mustStayInSkeleton: ['## Phases 1-6: Design Audit Baseline', '**9. AI Slop Detection**', '## Phase 7: Triage', '## Phase 8: Fix Loop'], + mustPrecedeStop: ['### Design Hard Rules'], + mustMoveToSection: ['### 4. Fix order and stack (Phase 8)'], + gateAfterStop: undefined, + }, + // No section-loading paid case yet: its touchfile row lives in + // test/helpers/touchfiles-data.ts, which #3413 owns. + behavioral: 'none', + maxSkeletonBytes: 139_202, // measured 139202 (2026-10-25): the craft-audit pointer and its index row, the Phase 8 tie-break, and the craft tells in Methodology category 9 (GSTA-92; Garry 2026-10-25 'Do it' (GSTA-92)). + minUnionBytes: 143_300, // measured union 146511 + mustContain: ['AI Slop Detection', 'Fix Loop', 'gstack-design-craft', 'Proof beside each claim'], + maxSizeRatio: 1.3843, // first parity guard for design-review: measured 146511 / 105,842 = 1.3843 (2026-10-25), the skeleton's growth since v1.64.1.0 plus the craft audit (Garry 2026-10-25 'Do it' (GSTA-92)). + }, + // ── Token-reduction Phase 4 wave 4 (v1.69.x branch): design doctrine carve ── // (D3A: read-on-demand doctrine, requiredReads-guarded + loading eval) 'design-html': { diff --git a/test/qa-lazy-sections.test.ts b/test/qa-lazy-sections.test.ts index 706ec9832..e914dbac5 100644 --- a/test/qa-lazy-sections.test.ts +++ b/test/qa-lazy-sections.test.ts @@ -161,8 +161,8 @@ describe('QA-only cross-host lazy rendering', () => { expect(fs.readFileSync(path.join(rendered, dir, 'SKILL.md'), 'utf8')).not.toContain(body.split('\n').slice(2).join('\n').trim()); } const outside = generated.artifacts.filter(artifact => artifact.host === host.name && artifact.kind === 'section' - && !/^(?:qa|qa-only|ship|plan-ceo-review|office-hours)\//.test(artifact.relativePath) - && !/\/gstack-(?:qa(?:-only)?|ship|plan-ceo-review|office-hours)\//.test(artifact.relativePath)); + && !/^(?:qa|qa-only|ship|plan-ceo-review|office-hours|design-review)\//.test(artifact.relativePath) + && !/\/gstack-(?:qa(?:-only)?|ship|plan-ceo-review|office-hours|design-review)\//.test(artifact.relativePath)); expect(outside.length > 0).toBe(host.name === 'claude'); // C4: ship is carved on every host; external pointers are relative to the installed skill. const ctx = context(host.name, 'ship');