From 77c7d8e0fcc72b260c108852776817b0e74ea62e Mon Sep 17 00:00:00 2001 From: Paul Bakaus Date: Fri, 17 Jul 2026 16:10:13 -0700 Subject: [PATCH] Refine product and visual work lifecycle --- AGENTS.md | 4 +- README.md | 4 +- README.npm.md | 4 +- cli/engine/detect-antipatterns-browser.js | 11 - cli/engine/engines/regex/detect-text.mjs | 22 +- cli/engine/registry/antipatterns.mjs | 11 - cli/engine/rules/checks.mjs | 52 ++- scripts/test-suites.mjs | 6 +- site/components/SlopPatternPreview.astro | 8 +- site/content/skills/live.md | 4 +- site/pages/index.astro | 2 +- site/pages/slop/index.astro | 13 - skill/SKILL.src.md | 72 ++-- skill/reference/android.md | 2 +- skill/reference/animate.md | 227 +++--------- skill/reference/bolder.md | 4 +- skill/reference/clarify.md | 312 ++++------------- skill/reference/codex.md | 2 +- skill/reference/colorize.md | 273 +++------------ skill/reference/craft.md | 6 +- skill/reference/delight.md | 326 +++--------------- skill/reference/document.md | 89 +++-- skill/reference/init.md | 237 +++---------- skill/reference/ios.md | 2 +- skill/reference/layout.md | 4 +- skill/reference/live.md | 9 +- skill/reference/new-work.md | 175 +++++++--- skill/reference/polish.md | 273 ++++----------- skill/reference/quieter.md | 2 +- skill/reference/routing.md | 2 +- skill/reference/shape.md | 64 +++- skill/reference/typeset.md | 8 +- skill/scripts/command-metadata.json | 2 +- skill/scripts/concept-seed.mjs | 59 +++- skill/scripts/context-signals.mjs | 2 +- skill/scripts/context.mjs | 110 ++++-- skill/scripts/critique-storage.mjs | 43 +-- skill/scripts/lib/surface-briefs.mjs | 151 ++++++++ skill/scripts/lib/target-slug.mjs | 33 ++ skill/scripts/surface-brief.mjs | 74 ++++ tests/concept-seed.test.mjs | 36 ++ tests/context.test.mjs | 140 ++++++-- tests/detect-antipatterns-fixtures.test.mjs | 15 +- tests/detect-antipatterns.test.js | 19 +- tests/docs-integrity.test.js | 34 +- .../antipatterns/hero-eyebrow-chip.html | 18 +- tests/live-reference.test.mjs | 5 +- tests/skill-behavior/README.md | 30 +- tests/skill-behavior/harness.mjs | 42 ++- tests/skill-behavior/providers.mjs | 30 +- tests/skill-behavior/scenarios.test.mjs | 37 +- .../skill-behavior/workflow-contract.test.mjs | 32 +- tests/surface-brief.test.mjs | 83 +++++ 53 files changed, 1464 insertions(+), 1761 deletions(-) create mode 100644 skill/scripts/lib/surface-briefs.mjs create mode 100644 skill/scripts/lib/target-slug.mjs create mode 100644 skill/scripts/surface-brief.mjs create mode 100644 tests/concept-seed.test.mjs create mode 100644 tests/surface-brief.test.mjs diff --git a/AGENTS.md b/AGENTS.md index 47ac85aef..855903cce 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -14,7 +14,7 @@ - `bun test tests/build.test.js` - run a focused Bun test. - `bun run test` - run the full Bun + Node test suite. - `bun run test:live-e2e` - opt-in live-mode E2E against framework fixtures (~2 min; needs `npx playwright install chromium` once). -- `bun run test:skill-behavior` - opt-in LLM-backed checks that the SKILL.md Setup flow actually drives the agent (~5 min; runs claude-sonnet-4-6 / gpt-5.5 / gemini-3.1-flash-lite, roughly $0.50-1.50 per run on the production-tier models, needs `.env` with provider keys). +- `bun run test:skill-behavior` - opt-in LLM-backed checks that the SKILL.md Setup flow actually drives the agent (runs claude-sonnet-5 / gpt-5.6-luna / gemini-3.5-flash / deepseek-v4-flash; needs `.env` with provider keys). - `bun run build:browser` / `bun run build:extension` - rebuild browser-specific bundles. Run `bun run build` after changing anything in `skill/`, transformer code, or user-facing counts. It validates the generated distribution under `dist/` without touching tracked root harness outputs. Use `bun run build:release` only when intentionally refreshing generated provider permutations for release/main-sync or build-system work. @@ -46,7 +46,7 @@ For changes to `skill/scripts/live-*.{mjs,js}` or `skill/scripts/live/**`, also Set `IMPECCABLE_E2E_AGENT=llm` to swap the deterministic fake agent for an API-backed one (`tests/live-e2e/agents/llm-agent.mjs`). Claude Haiku 4.5 is the primary path whenever `ANTHROPIC_API_KEY` is set. DeepSeek V4 Flash is the secondary cheap fallback when only `DEEPSEEK_API_KEY` is set, and can be forced with `IMPECCABLE_E2E_LLM_PROVIDER=deepseek` or `bun run test:live-e2e -- --llm-provider=deepseek`; override either model via `IMPECCABLE_E2E_LLM_MODEL` or `--llm-model=`. Tests skip cleanly when the selected provider key is unset. This path hits the API — use it for verification, not CI. -For changes to `skill/SKILL.src.md`'s Setup section, `skill/scripts/context.mjs`, or any Setup-touching reference file (`init.md`, `document.md`, `brand.md`, `product.md`, sub-command refs), also run `bun run test:skill-behavior`. The suite spawns real LLMs (claude-sonnet-4-6, gpt-5.5, gemini-3.1-flash-lite, all three, every run) with the source SKILL.md inlined as system prompt and a workspace-scoped tool set, then asserts on the tool-call trace. Provider keys live in repo-root `.env`; missing keys skip cleanly. Scope to one provider with `IMPECCABLE_SKILL_BEHAVIOR_MODELS=`; add `IMPECCABLE_SKILL_BEHAVIOR_VERBOSE=1` to dump per-scenario traces. Baseline (21-22/24) and per-scenario assertions live in `tests/skill-behavior/README.md`. +For changes to `skill/SKILL.src.md`'s Setup section, `skill/scripts/context.mjs`, or any Setup-touching reference file (`init.md`, `document.md`, `brand.md`, `product.md`, sub-command refs), also run `bun run test:skill-behavior`. The suite spawns current real models (claude-sonnet-5, gpt-5.6-luna, gemini-3.5-flash, deepseek-v4-flash) with the source SKILL.md inlined as system prompt and a workspace-scoped tool set, then asserts on the tool-call trace. Provider keys live in repo-root `.env`; missing keys skip cleanly. Scope to one provider with `IMPECCABLE_SKILL_BEHAVIOR_MODELS=`; add `IMPECCABLE_SKILL_BEHAVIOR_VERBOSE=1` to dump per-scenario traces. Baseline and per-scenario assertions live in `tests/skill-behavior/README.md`. ## Anti-pattern detection rules diff --git a/README.md b/README.md index e1af0319b..8e076dd57 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Impeccable -Design guidance for AI coding agents. 1 skill, 23 commands, live browser iteration, and 59 deterministic detector rules for AI-generated frontend design. +Design guidance for AI coding agents. 1 skill, 23 commands, live browser iteration, and 58 deterministic detector rules for AI-generated frontend design. > **Quick start:** From your project root, run `npx impeccable install`, then run `/impeccable init` inside your AI coding tool. Full docs: [impeccable.style](https://impeccable.style). @@ -13,7 +13,7 @@ Every model trained on the same SaaS templates. Skip the guidance and you get th Impeccable adds: - **One setup flow.** `/impeccable init` writes `PRODUCT.md` and offers `DESIGN.md`, so later commands know the audience, brand/product lane, voice, anti-references, colors, type, and components. - **23 commands.** A shared design vocabulary with your AI: `polish`, `audit`, `critique`, `distill`, `animate`, `bolder`, `quieter`, and more. -- **59 deterministic detector rules** plus LLM-only critique checks. The CLI and browser extension run the deterministic rules with no LLM and no API key. +- **58 deterministic detector rules** plus LLM-only critique checks. The CLI and browser extension run the deterministic rules with no LLM and no API key. ## What's Included diff --git a/README.npm.md b/README.npm.md index ec00a1d2a..ddb4deb65 100644 --- a/README.npm.md +++ b/README.npm.md @@ -1,6 +1,6 @@ # Impeccable CLI -Detect UI anti-patterns and design quality issues from the command line. Scans HTML, CSS, JSX, TSX, Vue, and Svelte files for 59 deterministic rules, including AI-generated UI tells, accessibility violations, and general design quality problems. +Detect UI anti-patterns and design quality issues from the command line. Scans HTML, CSS, JSX, TSX, Vue, and Svelte files for 58 deterministic rules, including AI-generated UI tells, accessibility violations, and general design quality problems. ## Quick Start @@ -56,7 +56,7 @@ npx impeccable detect --fast src/ **Quality**: tiny body text, cramped padding, long line lengths, small touch targets -59 deterministic detector rules in total. See the full catalog at [impeccable.style/slop](https://impeccable.style/slop). +58 deterministic detector rules in total. See the full catalog at [impeccable.style/slop](https://impeccable.style/slop). ## Exit Codes diff --git a/cli/engine/detect-antipatterns-browser.js b/cli/engine/detect-antipatterns-browser.js index 033573e36..76bf4ddeb 100644 --- a/cli/engine/detect-antipatterns-browser.js +++ b/cli/engine/detect-antipatterns-browser.js @@ -312,17 +312,6 @@ const ANTIPATTERNS = [ skillSection: 'Layout & Space', skillGuideline: 'numbered section markers', }, - { - id: 'numbered-section-markers', - category: 'slop', - scopes: ['layout'], - severity: 'advisory', - name: 'Numbered section markers (01 / 02 / 03)', - description: - 'Numbered display markers as section labels (01, 02, 03) are the AI editorial scaffold one tier deeper than tracked eyebrow chips. If you find yourself reaching for them, choose a different section cadence.', - skillSection: 'Layout & Space', - skillGuideline: 'numbered section markers', - }, { id: 'em-dash-overuse', category: 'slop', diff --git a/cli/engine/engines/regex/detect-text.mjs b/cli/engine/engines/regex/detect-text.mjs index 81c30ab99..f641d5917 100644 --- a/cli/engine/engines/regex/detect-text.mjs +++ b/cli/engine/engines/regex/detect-text.mjs @@ -280,22 +280,6 @@ const REGEX_ANALYZERS = [ if (count === 0) return []; return [finding('marketing-buzzword', filePath, `${count} buzzword phrase${count === 1 ? '' : 's'}: "${firstSample}"`)]; }, - // Numbered section markers (01 / 02 / 03 ...) - (content, filePath) => { - const text = stripHtmlToText(content); - const re = /\b(0[1-9]|1[0-2])\b/g; - const seen = new Set(); - let m; - while ((m = re.exec(text)) !== null) seen.add(m[1]); - if (seen.size < 3) return []; - const sorted = [...seen].sort(); - let sequential = 0; - for (let i = 1; i < sorted.length; i++) { - if (parseInt(sorted[i], 10) === parseInt(sorted[i - 1], 10) + 1) sequential++; - } - if (sequential < 2) return []; - return [finding('numbered-section-markers', filePath, `Sequence: ${sorted.slice(0, 6).join(', ')}`)]; - }, // Aphoristic cadence: manufactured-contrast + short-rebuttal (content, filePath) => { const text = stripHtmlToText(content); @@ -430,21 +414,20 @@ function runRegexMatchers(lines, filePath, lineOffset = 0, blockContext = null, } /** Page-level analyzers that scan rendered text content (em-dash use, - * buzzword phrases, numbered section markers, aphoristic cadence). + * buzzword phrases, aphoristic cadence). * These are detector-agnostic — they work on any HTML/text source * and don't need a parsed DOM. Exported so detectHtml can call them * for `.html` files (which otherwise skip the regex engine). */ const TEXT_CONTENT_ANALYZER_IDS = [ 'em-dash-overuse', 'marketing-buzzword', - 'numbered-section-markers', 'aphoristic-cadence', ]; function runTextContentAnalyzers(content, filePath, options = {}) { const profile = options?.profile; if (!shouldRunPageAnalyzers(content, filePath)) return []; - // The 4 text-content analyzers are at indices 3-6 in REGEX_ANALYZERS. + // The 3 text-content analyzers are at indices 3-5 in REGEX_ANALYZERS. const findings = []; for (let i = 0; i < TEXT_CONTENT_ANALYZER_IDS.length; i++) { const analyzer = REGEX_ANALYZERS[3 + i]; @@ -535,7 +518,6 @@ function detectText(content, filePath, options = {}) { 'monotonous-spacing', 'em-dash-overuse', 'marketing-buzzword', - 'numbered-section-markers', 'aphoristic-cadence', 'dark-glow', ]; diff --git a/cli/engine/registry/antipatterns.mjs b/cli/engine/registry/antipatterns.mjs index b85f7e62f..84c69b0b8 100644 --- a/cli/engine/registry/antipatterns.mjs +++ b/cli/engine/registry/antipatterns.mjs @@ -210,17 +210,6 @@ const ANTIPATTERNS = [ skillSection: 'Layout & Space', skillGuideline: 'numbered section markers', }, - { - id: 'numbered-section-markers', - category: 'slop', - scopes: ['layout'], - severity: 'advisory', - name: 'Numbered section markers (01 / 02 / 03)', - description: - 'Numbered display markers as section labels (01, 02, 03) are the AI editorial scaffold one tier deeper than tracked eyebrow chips. If you find yourself reaching for them, choose a different section cadence.', - skillSection: 'Layout & Space', - skillGuideline: 'numbered section markers', - }, { id: 'em-dash-overuse', category: 'slop', diff --git a/cli/engine/rules/checks.mjs b/cli/engine/rules/checks.mjs index 3fdd6b2dd..9bace11b3 100644 --- a/cli/engine/rules/checks.mjs +++ b/cli/engine/rules/checks.mjs @@ -352,27 +352,57 @@ function isAccentColor(cssColor) { return false; } +function resolveHeroHeadingSizePx(value) { + const input = String(value || '').trim().toLowerCase(); + if (!input) return 0; + + const simpleLengthPx = (token) => { + const match = /^(-?\d*\.?\d+)\s*(px|rem|em|%)?$/.exec(String(token || '').trim()); + if (!match) return null; + const amount = Number(match[1]); + if (!Number.isFinite(amount)) return null; + if (match[2] === 'rem' || match[2] === 'em') return amount * 16; + if (match[2] === '%') return amount * 0.16; + return amount; + }; + + const direct = simpleLengthPx(input); + if (direct !== null) return direct; + + // Static CSS engines cannot resolve viewport units, but clamp's min/max + // bounds still tell us whether the heading can ever reach hero scale. + const clamp = /^clamp\((.*)\)$/.exec(input); + if (clamp) { + const parts = clamp[1].split(','); + if (parts.length === 3) { + const bounds = [simpleLengthPx(parts[0]), simpleLengthPx(parts[2])] + .filter((candidate) => candidate !== null); + if (bounds.length > 0) return Math.max(...bounds); + } + } + + return 0; +} + // Sibling-relationship rule. Anchor on a hero-scale h1, look at the // previousElementSibling, and gate on EITHER the classic tracked- // uppercase eyebrow OR the modern accent-colored bold eyebrow. function checkHeroEyebrow(opts) { const { headingTag, headingText, headingFontSize, + headingInApplicationContext, siblingTag, siblingText, siblingTextTransform, siblingFontSize, siblingLetterSpacing, siblingFontWeight, siblingColor, siblingHasAccentDashPseudo, } = opts; if (headingTag !== 'h1') return []; - // We previously gated on headingFontSize >= 48 to anchor "hero scale". - // But modern hero h1s use clamp() / vw / var(--text-*), none of which - // jsdom can resolve — the computed value comes back as "2em" or - // "var(--text-9xl)" and parseFloat returns 2 or NaN. The gate fails - // on virtually every Tailwind v4 / framework build. The other gates - // (sibling text 2-60 chars, font-size ≤ 14px, accent-bold OR - // tracked-caps) are tight enough to avoid false positives on non- - // hero h1s — a tiny tan label directly above any h1 is the - // antipattern regardless of how big the h1 ends up. + // This is specifically a marketing-hero cliché, not a ban on compact + // context labels in product UI (for example, a station name inside a tab + // panel). Browser-computed sizes are reliable; the static adapter also + // resolves ordinary px/rem/em and clamp() bounds before reaching here. + if (headingInApplicationContext) return []; + if (!(headingFontSize >= 48)) return []; if (!siblingTag) return []; // An h2 above an h1 is a different anti-pattern (heading hierarchy / dual // headings) — never an eyebrow. @@ -1938,6 +1968,7 @@ function checkElementHeroEyebrowDOM(el) { headingTag: tag, headingText: el.textContent || '', headingFontSize: parseFloat(headStyle.fontSize) || 0, + headingInApplicationContext: !!el.closest('[role="tabpanel"], [role="dialog"], [role="application"], dialog'), siblingTag: sibling.tagName.toLowerCase(), siblingText: sibling.textContent || '', siblingTextTransform: sibStyle.textTransform || '', @@ -3367,7 +3398,8 @@ function checkElementHeroEyebrow(el, style, tag, window, customPropMap) { return checkHeroEyebrow({ headingTag: tag, headingText: el.textContent || '', - headingFontSize: parseFloat(headingFontSizeRaw) || 0, + headingFontSize: resolveHeroHeadingSizePx(headingFontSizeRaw), + headingInApplicationContext: !!el.closest?.('[role="tabpanel"], [role="dialog"], [role="application"], dialog'), siblingTag: sibling.tagName.toLowerCase(), siblingText: sibling.textContent || '', siblingTextTransform: sibStyle.textTransform || '', diff --git a/scripts/test-suites.mjs b/scripts/test-suites.mjs index c989d9ef2..dc0d4f415 100644 --- a/scripts/test-suites.mjs +++ b/scripts/test-suites.mjs @@ -25,11 +25,11 @@ export const SUITES = { triggers: [ ...COMMON_INFRA_PATTERNS, /^scripts\/(?!benchmark-detector|build-browser-detector|build-extension)/, - /^skill\/(SKILL\.src\.md|agents\/|reference\/|scripts\/(cleanup-deprecated|context|context-signals|critique-storage|design-parser|hook|impeccable-paths|is-generated|lib\/provider|pin))/, + /^skill\/(SKILL\.src\.md|agents\/|reference\/|scripts\/(cleanup-deprecated|concept-seed|context|context-signals|critique-storage|design-parser|hook|impeccable-paths|is-generated|lib\/(provider|surface-briefs|target-slug)|pin|surface-brief))/, /^site\/(pages|content|components|layouts)\//, /^README(\.npm)?\.md$/, /^cli\/bin\//, - /^tests\/(build|cleanup-deprecated|cli-ignores|context|context-signals|critique-storage|design-parser|docs-integrity|github-sheriff|hook|hook-build|impeccable-paths|openai-plugin|pin|shiki-theme|skills-cli|slop-catalog|target-args|test-suites|theme|windows-path-fix|zip)\.test\.(js|mjs)$/, + /^tests\/(build|cleanup-deprecated|cli-ignores|concept-seed|context|context-signals|critique-storage|design-parser|docs-integrity|github-sheriff|hook|hook-build|impeccable-paths|openai-plugin|pin|shiki-theme|skills-cli|slop-catalog|surface-brief|target-args|test-suites|theme|windows-path-fix|zip)\.test\.(js|mjs)$/, /^tests\/lib\//, ], commands: [ @@ -54,6 +54,7 @@ export const SUITES = { runner: 'node', files: [ 'tests/ci-test-plan.test.mjs', + 'tests/concept-seed.test.mjs', 'tests/context.test.mjs', 'tests/context-signals.test.mjs', 'tests/critique-storage.test.mjs', @@ -67,6 +68,7 @@ export const SUITES = { 'tests/target-args.test.mjs', 'tests/shiki-theme.test.mjs', 'tests/slop-catalog.test.mjs', + 'tests/surface-brief.test.mjs', 'tests/test-suites.test.mjs', 'tests/theme.test.mjs', 'tests/zip.test.mjs', diff --git a/site/components/SlopPatternPreview.astro b/site/components/SlopPatternPreview.astro index df9aff68c..b70719bd8 100644 --- a/site/components/SlopPatternPreview.astro +++ b/site/components/SlopPatternPreview.astro @@ -9,7 +9,7 @@

Detects and removes AI slop.

- 59 checks catch the defaults agents keep reaching for before they become your design. + 58 checks catch the defaults agents keep reaching for before they become your design.

@@ -18,7 +18,7 @@ Detector on - Explore all 59 + Explore all 58
  • CLI for CI - npx impeccable detect src/ in a PR check. 59 deterministic rules. JSON output, exit codes for build gates. + npx impeccable detect src/ in a PR check. 58 deterministic rules. JSON output, exit codes for build gates. View on npm →
  • diff --git a/site/pages/slop/index.astro b/site/pages/slop/index.astro index a6390a346..53e26e396 100644 --- a/site/pages/slop/index.astro +++ b/site/pages/slop/index.astro @@ -701,19 +701,6 @@ const catalogRules = Object.fromEntries(
  • -
    - -
    -
    - AI slop - CLI -
    -

    Numbered section markers (01 / 02 / 03)

    -

    Numbered display markers as section labels are the AI editorial scaffold one tier deeper than tracked eyebrow chips. Numbers earn their place only when the section actually is a sequence.

    - See in /impeccable -
    -
    -
    diff --git a/skill/SKILL.src.md b/skill/SKILL.src.md index 5f145d8d7..a22a15152 100644 --- a/skill/SKILL.src.md +++ b/skill/SKILL.src.md @@ -9,68 +9,65 @@ allowed-tools: license: Apache 2.0 --- -Designs and iterates production-grade frontend interfaces. Real working code, committed design choices, exceptional craft. +This skill gives you the tools and permission to create design that earns to be called out-of-distribution craft: Whereas before, your design work would have been safe, timid and measured, you now approach every design task as a award-winning design director with impeccable understanding for what makes exceptional design work: production-grade code, peak creativity, a clear POV, deep understanding of the needs of the client and users, and exceptional craft. -Approach every design task as the design lead at a small studio known for giving every client a visual identity that could not be mistaken for anyone else's. The client has already rejected work that felt templated; they are paying for a point of view. {{model}} is capable of extraordinary work. Don't hold back. +Core principles: +- Go all out. No hedging, no shortcuts. The deliverable must be complete (except assets the user must provide). +- Dream big and bold. Distinct, beautiful, outstanding and highly inspiring work. +- Iterate with tools available to you (e.g. visual understanding, browser screenshots) until you think this meets the bar. ## Setup -1. Run `node {{scripts_path}}/context.mjs` once per session (if the runtime shows this skill's loaded base directory, run `node /scripts/context.mjs`; keep cwd at the user's project). It prints the project's context and its directives; follow what it prints. Once its output is in the conversation, never rerun it on a later turn. -2. `craft` and `shape` are build-path exceptions: resolve the init gate below first, then read **`reference/new-work.md`** for the shared task discovery and surface-concept choice. `craft` continues through its contract, build, and finish; `shape` also reads **`reference/shape.md`**, produces the planning artifact, and stops before code. For any other invoked sub-command (`audit`, `polish`, `live`, ...), immediately read **`reference/.md`** after `context.mjs` (the `.native` variant from the Commands table when the platform is `ios`/`android`/`adaptive`) and follow it. This read is a hard gate: do not inspect the target, run command-specific scripts, or edit files until the reference is loaded. -3. Read at least one project file (CSS / tokens / theme / a representative component) to learn what world you're in. If PRODUCT.md's `## Platform` is `ios` or `android`, also read `reference/.md` (`adaptive` reads both). +1. Run `node {{scripts_path}}/context.mjs` once per session (if the runtime shows this skill's loaded base directory, run `node /scripts/context.mjs`; keep cwd at the user's project). Pass a named source file or route as `--target `. It loads PRODUCT.md, DESIGN.md, the matching surface brief, and native-platform guidance when applicable; follow its directives and do not rerun it. +2. Before acting, load the one playbook that owns the request: the Commands table's reference for an explicit or clearly implied sub-command, or [reference/new-work.md](reference/new-work.md) for a new surface or replacement visual world. Then inspect the target and at least one representative source of incumbent visual truth (tokens, theme, CSS, component, or asset) before editing. ## How to design -**The brief wins.** Where the brief pins down a direction (a named aesthetic, an era, a place, a material, a specific font or palette), follow it exactly, including when it asks for a look this skill warns is saturated. Redirecting a pinned direction toward your own taste is a failure, not a save. - -**Refinement preserves; redesign replaces.** A refinement (`polish`, `bolder`, `quieter`, `distill`, or another scoped improvement) works inside the incumbent world: preserve its identity, functioning behavior, and everything outside the named scope. A redesign or rebrand is explicit authorization to stop treating the old visual system as authority. Keep product truth, real content, working functionality, native affordances, and technical constraints unless the brief changes them, but use the old look only as evidence and anti-reference; run init's visual-world choice and replace DESIGN.md before designing. Do not split the difference into contemporary polish on the old boring page. - -**New worlds are initialized with the user.** When no committed identity exists, or the user asks for a redesign, rebrand, or replacement look, load [reference/init.md](reference/init.md) and finish its interview and visual-world choice before making design decisions. Init writes the durable product inputs to PRODUCT.md and the chosen visual world to DESIGN.md. A structured simulated-user tool counts as a user; a bare prompt does not. Missing DESIGN.md alone does not prove the world is blank: for refinement, code, tokens, chosen type, components, and assets remain incumbent design authority and init documents rather than erases them. After the gate, [reference/new-work.md](reference/new-work.md) creates a novel task-scoped composition inside the newly committed world. +- **The brief wins.** Honor pinned aesthetics, eras, materials, fonts, and palettes even when they conflict with a saturated-pattern warning. Redirecting a clear brief toward your taste is failure. +- **Refinement preserves; redesign replaces.** Refinement keeps the incumbent identity, behavior, copy, and everything outside scope. Ask before replacing factual copy or adding claims. Redesign keeps product truth, content, function, native affordances, and constraints, but treats the old look as evidence and anti-reference; choose a replacement world in new-work and replace DESIGN.md. Never split the difference into polish on the discarded look. +- **Visual authority is evidence, not a filename.** Missing DESIGN.md alone does not make a project greenfield; new-work decides whether to preserve, expand, or replace the incumbent world. ## Modes -Name the visitor's mode before designing; the page's grammar follows from it, and most ruined pages are one mode wearing another mode's grammar. **The mode belongs to the requested surface, not the product**: a landing page for a dense technical tool is still Persuade, with Persuade's full permission to be striking; a docs page for a fashion house is still Read. Decide it from the brief and surface on every task; do not persist a brand/product classification in PRODUCT.md. Depth beyond the paragraphs below: [reference/init.md](reference/init.md) when establishing or replacing identity, [reference/new-work.md](reference/new-work.md) when crafting or planning a new surface inside it, and [reference/operate.md](reference/operate.md) for substantial Operate and Read work. +Choose the mode from the requested surface, not the product, and persist it only in that surface brief. A tool's landing page is still Persuade; a fashion house's documentation is still Read. See [new-work.md](reference/new-work.md) for new surfaces and [operate.md](reference/operate.md) for deeper Operate/Read guidance. -**Persuade** (the surface exists to win someone over; design IS the product). The deliverable is an impression that stops the scroll, earns the click, converts. Spans every genre; don't collapse them into one look. On new surfaces, briefs that imply imagery must ship real, verified imagery; a colored rectangle where a photo belongs reads as incomplete. Type, palette, and material language come from the committed DESIGN.md world, not from category habit. - -**Operate** (the surface is a tool someone works in; design SERVES the task). A person getting something done: scanability and consistency outrank expressiveness. These surfaces earn trust by feeling native to their platform: system font stacks and workhorse UI faces are legitimate and often correct here (the Persuade reject list does not apply). The brand lives in the details: focus states, empty states, microcopy, one owned accent. The usage scene is part of the spec: an interface read outdoors, in motion, or at a glance must survive its real ambient light, and the theme follows the scene, not the category's habit. - -**Read** (the surface exists to be understood; long-form, reference, guidance). The deliverable is comprehension, and comprehension is earned twice: a structure the reader can hold in their head with nothing standing between them and the answer, and a reading experience good enough to stay in, through typographic quality and whatever visual or interactive support genuinely helps the reader follow. The brand lives in type, spacing, and small accents. - -**Experience** (the surface presents a body of work; the page IS the work). The artifact leads, the interface recedes, and the visitor meets the work itself in the first viewport at every screen size. Boldness here means trusting the work. +- **Persuade:** win someone over; design is the product. Earn attention and action. Ship real imagery when the brief needs it; follow the committed world, not category habit. +- **Operate:** help someone do work. Scanability, consistency, native expectations, and the real usage scene outrank expression. Brand lives in precise details. +- **Read:** make something understood. Structure for comprehension, then make the reading experience worth staying in. +- **Experience:** present a body of work. Let the artifact lead from the first viewport; the interface recedes. ## Craft floor -Build to this floor without announcing it. The design detector (the project hook, `node {{scripts_path}}/detect.mjs --json `, or `audit`) verifies most of it mechanically; resolve every finding before finalizing. Fix real defects, but use context judgment rather than distorting intentional design to appease a false positive. Classify any intentional exception explicitly and use the hook system's narrowest appropriate waiver when it must persist. +Build to this floor without announcing it. Run the detector before finishing; fix real defects and narrowly waive intentional exceptions rather than designing for the scanner. -- Contrast: body text ≥4.5:1 against its background (placeholders too); large text ≥3:1. Gray text on a colored background looks washed out: use a darker shade of the background's own hue, or a transparency of the text color. -- Shadows describe real light: an offset and a soft blur. A zero-offset colored halo is decoration announcing itself. -- Spacing has rhythm: generous separations, tight groupings; cramped padding reads as broken; the space above a heading exceeds the space below it. Verify computed spacing, not intended spacing. -- Type: body line length 65-75ch; display clamp() max ≤6rem; letter-spacing ≥-0.04em; `text-wrap: balance` on headings; modular scale ≥1.25 between steps; light-on-dark adds 0.05-0.1 line-height. Pair faces on a contrast axis, never two similar-but-not-identical ones; one family with committed weight contrast beats a timid pair. Test headings at every breakpoint; overflow means reduce the clamp or rewrite the copy. -- Motion is part of the build: one orchestrated moment beats scattered effects; ease-out exponential curves; reveals enhance an already-visible default (content gated on a class-triggered transition ships blank in hidden tabs and headless renderers). Responsive down to mobile and visible keyboard focus are part of the floor. -- Ship real content (no placeholders, dead links, or fake controls) and cover the interaction states people will actually hit (hover, focus, disabled, loading, error, empty). -- Copy is design material: name things the way the page's own people speak, make every control say what it does, and make every error say what happened and what to do next. -- Before finishing, re-read the brief: every requirement it names must exist on the page, findable in seconds. A beautiful page missing an asked-for feature is unfinished. +- **Contrast:** body and placeholder text ≥4.5:1; large text ≥3:1. On colored surfaces, tint secondary text from that hue or the foreground instead of using gray. +- **Depth:** shadows describe light with offset and soft blur; zero-offset colored halos are decoration. +- **Spacing:** tight groups, generous separation, no cramped containers; space above a heading exceeds space below. Verify computed values. +- **Type:** body measure 65–75ch; display max 6rem and tracking floor -0.04em; balance headings; use clear scale/weight contrast; test overflow at every breakpoint. +- **Motion:** author one coherent moment instead of scattered effects. Use exponential ease-out and an already-visible default. Premium motion is not transform/opacity alone: it may add focus, depth, masks, light, or material change through blur/filter, backdrop-filter, clip-path/masks, or shadow when smooth. Always provide reduced motion. +- **Shipping:** real content, working controls, responsive composition, keyboard focus, and the states users hit: hover, disabled, loading, error, and empty. +- **Copy:** use the product's language; controls name their action, errors name the problem and recovery. +- **Coverage:** every brief requirement must exist and be findable within seconds. Calibration for this provider: -- Display letter-spacing floor is -0.04em; -0.02 to -0.03em is plenty for tight grotesque display. Your default runs tighter and the letters touch. -- An element declares its elevation once: a border or a shadow, chosen deliberately, never both as decoration. Corner radius is a brand decision made once; containers keep it modest, and full rounding belongs to small controls. -- Illustration is real or absent; a sketched stand-in reads as filler. Backgrounds are surfaces, not decoration; texture appears only when the subject's world supplies it. Copy makes the specific claim instead of staging a concept to react to. +- Display tracking stops at -0.04em; -0.02 to -0.03em is usually enough. +- Declare elevation once: border or shadow, not both as decoration. Keep container radii modest; reserve pills for small controls. +- Use real illustration or none. Treat backgrounds as surfaces, add texture only from the subject's world, and make specific claims without meta-commentary. -**Gemini-specific defect: hard ban.** Never animate `` elements on hover, including Tailwind `.group:hover` scale/rotate/translate patterns that animate a child image via a parent hover. It adds no information and reads as "AI animated this because it could". If a card needs hover feedback, animate the card's background, border, or shadow. Never the image, never via the image's parent. +Never animate `` elements on hover, directly or through a parent. Give the card itself feedback instead. ## Commands | Command | Category | Description | Reference | |---|---|---|---| -| `craft [feature]` | Build | The standard build flow with attended checkpoints | [reference/new-work.md](reference/new-work.md) | +| `craft [feature]` | Build | Deprecated alias for an ordinary new-work request | [reference/craft.md](reference/craft.md) | | `shape [feature]` | Build | Plan UX/UI before writing code | [reference/shape.md](reference/shape.md) | -| `init` | Build | Set up project context: PRODUCT.md, DESIGN.md, live config, next steps | [reference/init.md](reference/init.md) | +| `init` | Build | Capture durable product context in PRODUCT.md | [reference/init.md](reference/init.md) | | `document` | Build | Generate DESIGN.md from existing project code | [reference/document.md](reference/document.md) | | `extract [target]` | Build | Pull reusable tokens and components into design system | [reference/extract.md](reference/extract.md) | | `critique [target]` | Evaluate | UX design review with heuristic scoring | [reference/critique.md](reference/critique.md) | @@ -92,7 +89,14 @@ Calibration for this provider: | `optimize [target]` | Fix | Diagnose and fix UI performance | [reference/optimize.md](reference/optimize.md) | | `live` | Iterate | Visual variant mode: pick elements in the browser, generate alternatives | [reference/live.md](reference/live.md) | -Routing: **no argument** → read [reference/routing.md](reference/routing.md) and present the context-aware menu (never auto-run a command). **First word matches a command** (or `pin` / `unpin` / `hooks`) → load its reference (native variant on native platforms) and follow it; everything after the command name is the target. **Intent clearly maps to one command** ("fix the spacing" → `layout`, "rewrite this error" → `clarify`) → same; if two fit, ask once. **Otherwise** → general design invocation: apply Setup and this file's guidance; new builds and redesigns resolve init first, then use the new-work playbook. `teach` routes to `init`; `craft` routes to new-work; `shape` shares new-work's discovery and concept choice, then returns the planning-only brief from shape. If setup diverted into `init`, finish it, use the PRODUCT.md and DESIGN.md just written, then resume without rerunning `context.mjs`. +Routing: + +- **No argument:** read [routing.md](reference/routing.md) and present its context-aware menu; never auto-run a command. +- **Explicit or clearly implied command:** load its reference (native variant on native platforms) and follow it. Ask once if two commands fit. +- **Otherwise:** treat the request as general design work. Missing PRODUCT.md routes through init; new surfaces and replacement worlds use new-work. +- `teach` aliases `init`. `craft` is a deprecated alias for ordinary new-work and adds nothing. `shape` owns task discovery, then enters new-work only for visual-world and surface-concept decisions. + +After init writes PRODUCT.md, resume without rerunning `context.mjs`. **Pin / Unpin:** `node {{scripts_path}}/pin.mjs ` creates or removes a standalone `{{command_prefix}}` shortcut. Report the script's result concisely; relay stderr verbatim on error. diff --git a/skill/reference/android.md b/skill/reference/android.md index 440a89651..ae154cba1 100644 --- a/skill/reference/android.md +++ b/skill/reference/android.md @@ -2,7 +2,7 @@ For native Android apps: Jetpack Compose, Android Views, React Native, Expo, Flutter shipping to Android hardware. -On native, register narrows. Material Design 3 governs structure, navigation, and interaction whatever the register; brand expresses through Material's theming (color roles, type scale, shape, motion). A Material-everywhere cross-platform app that also ships to iPhone still owes iOS its OS guarantees on that hardware: safe-area insets, Reduce Motion, edge-swipe back. +On native, the visitor mode narrows what expression may override. Material Design 3 governs structure, navigation, and interaction in every mode; brand expresses through Material's theming (color roles, type scale, shape, motion). A Material-everywhere cross-platform app that also ships to iPhone still owes iOS its OS guarantees on that hardware: safe-area insets, Reduce Motion, edge-swipe back. ## The Android slop test diff --git a/skill/reference/animate.md b/skill/reference/animate.md index 1aa36f2e5..85a4151ef 100644 --- a/skill/reference/animate.md +++ b/skill/reference/animate.md @@ -1,203 +1,88 @@ > **Additional context needed**: performance constraints. -Add motion that conveys state, gives feedback, and clarifies hierarchy. Cut motion that exists only for decoration. Animation fatigue is a real cost; spend the budget on the moments that need it. +Use motion to explain state, relationship, and hierarchy, or to create one authored moment the surface has earned. Decoration without purpose is animation debt. --- -## Register +## Visitor mode -Persuade + Experience: motion is part of the voice; one well-rehearsed entrance beats scattered micro-interactions. The saturated AI default is fade-and-rise reveals on every scrolled section; that's a tell, not a choreography. Reserve scroll-triggered motion for moments that earn it. +- **Persuade + Experience:** motion may carry the voice. Prefer one rehearsed focal sequence to repeated section reveals. +- **Operate + Read:** motion serves feedback, state, and continuity. Keep routine transitions fast and do not make users wait through page-load choreography. +- **Native (`ios` / `android` / `adaptive`):** follow the Motion section of [ios.md](ios.md) or [android.md](android.md), including the platform's Reduce Motion behavior. Do not apply the web tooling below. -Operate + Read: 150–250 ms on most transitions. Motion conveys state: feedback, reveal, loading, transitions between views. No page-load choreography; users are in a task and won't wait for it. +## Find the job -Native (`ios` / `android` / `adaptive`): implementation follows the Motion section of [ios.md](ios.md) / [android.md](android.md) (read it first if Setup hasn't already): system transitions and OS Reduce Motion, never the web tooling below. +Inspect the existing motion language, interaction states, target devices, and performance budget. Find only the places where motion would: ---- +- acknowledge an action; +- make a state change or spatial relationship legible; +- preserve continuity through navigation or layout change; +- direct attention at a meaningful moment; +- embody the selected visual world. -## Assess Animation Opportunities +Ask only when a material constraint cannot be inferred. Do not animate a static area merely because it exists. -Analyze where motion would improve the experience: +## Set the motion thesis -1. **Identify static areas**: - - **Missing feedback**: Actions without visual acknowledgment (button clicks, form submission, etc.) - - **Jarring transitions**: Instant state changes that feel abrupt (show/hide, page loads, route changes) - - **Unclear relationships**: Spatial or hierarchical relationships that aren't obvious - - **Lack of delight**: Functional but joyless interactions - - **Missed guidance**: Opportunities to direct attention or explain behavior +Write a short plan before implementation: -2. **Understand the context**: - - What's the personality? (Playful vs serious, energetic vs calm) - - What's the performance budget? (Mobile-first? Complex page?) - - Who's the audience? (Motion-sensitive users? Power users who want speed?) - - What matters most? (One hero animation vs many micro-interactions?) +- **Focal moment:** the one sequence or interaction that deserves authorship, if any. +- **Continuity:** the state, layout, or navigation changes that need explanation. +- **Feedback:** the controls and outcomes that need acknowledgment. +- **Budget:** which effects may be expensive, how often they run, and the reduced-motion equivalent. -If any of these are unclear from the codebase, {{ask_instruction}} +The focal moment must come from this product and surface concept. A generic fade-and-rise, hover lift, parallax layer, or scroll reveal is not a thesis. -**CRITICAL**: Respect `prefers-reduced-motion`. Always provide non-animated alternatives for users who need them. +## Choose material by meaning -## Plan Animation Strategy +Transform and opacity are reliable foundations, not the entire palette. Choose properties for what the transition communicates: -Create a purposeful animation plan: +- **Continuity and relationship:** shared-element motion, FLIP-style transforms, view transitions, or deliberate spatial movement. +- **Focus and depth:** bounded blur, filter, backdrop, light, or shadow changes. +- **Reveal and composition:** masks, clip paths, cropping, or controlled occlusion. +- **Material and energy:** color, gradient position, texture, distortion, or shader effects when the world and runtime support them. +- **State and feedback:** the smallest change that makes cause and result unmistakable. -- **Feedback layer**: Which interactions need acknowledgment? -- **Transition layer**: Which state changes need smoothing? -- **Entrance moment**: The ONE entrance worth rehearsing, where the mode invites it. Not every surface wants one. -- **Delight layer**: Where can we surprise and delight? +Do not stack techniques for spectacle. One strong material idea, carried through the focal sequence and quiet supporting states, is usually enough. -**IMPORTANT**: One well-orchestrated experience beats scattered animations everywhere. Focus on high-impact moments. +Sibling stagger is appropriate when a list appears as a list. Cap the total delay, and never reinterpret every scrolled section as a staggered list. -## Implement Animations +## Timing and easing -Add motion systematically across these categories: +Timing should express distance and consequence: -### Micro-interactions -- **Button feedback**: - - Hover: Subtle scale (1.02-1.05), color shift, shadow increase - - Click: Quick scale down then up (0.95 → 1), ripple effect - - Loading: Spinner or pulse state -- **Form interactions**: - - Input focus: Border color transition, slight scale or glow - - Validation: Shake on error, check mark on success, smooth color transitions -- **Toggle switches**: Smooth slide + color transition (200-300ms) -- **Checkboxes/radio**: Check mark animation, ripple effect -- **Like/favorite**: Scale + rotation, particle effects, color transition +| Duration | Typical use | +|---|---| +| 100–150 ms | immediate feedback | +| 150–300 ms | routine state change | +| 300–500 ms | layout, overlay, or view transition | +| 500–800 ms | a deliberately authored focal entrance | -### State Transitions -- **Show/hide**: Fade + slide (not instant), appropriate timing (200-300ms) -- **Expand/collapse**: Height transition with overflow handling, icon rotation -- **Loading states**: Skeleton screen fades, spinner animations, progress bars -- **Success/error**: Color transitions, icon animations, gentle scale pulse -- **Enable/disable**: Opacity transitions, cursor changes +Exit faster than entrance. Use natural deceleration such as `cubic-bezier(0.16, 1, 0.3, 1)` for confident arrivals; do not use bounce or elastic curves by reflex. Long feedback feels like latency. -### Entrance Animations -- **The one entrance moment** (where the mode invites it): a committed entrance for primary content (scale, parallax, or a creative effect) -- **Modal/drawer entry**: Smooth slide + fade, backdrop fade, focus management -- **List rhythm**: Sibling stagger is legitimate for cards-in-a-grid or list-items-appearing. Whole-section fade-on-scroll is not a list and is not legitimate. Cap total stagger time: 10 items at 50ms each = 500ms total. For more items, reduce per-item delay or cap the staggered count. +## Implement to the runtime - Use CSS custom properties for clean stagger: `animation-delay: calc(var(--i, 0) * 50ms)` with `style="--i: 0"`, `style="--i: 1"`, etc. on each item. +- Use CSS transitions and keyframes for declarative state and bounded sequences. +- Use Web Animations API or the project's existing motion library for interruption, sequencing, and dynamic values. +- Use View Transitions or shared-element techniques when continuity across states is the point. +- Use scroll-driven motion only when the scroll relationship itself carries meaning, with a robust fallback. +- Do not add a dependency for an effect the existing stack can express cleanly. -### Navigation & Flow -- **Page transitions**: Crossfade between routes, shared element transitions -- **Tab switching**: Slide indicator, content fade/slide -- **Carousel/slider**: Smooth transforms, snap points, momentum -- **Scroll effects**: Parallax layers, sticky headers with state changes, scroll progress indicators +Keep content visible in the default state so failed scripts do not hide the page. Avoid casually animating layout-driving properties such as `width`, `height`, `top`, `left`, and margins; use FLIP, transforms, or grid techniques when appropriate. Bound blur, filter, shadow, canvas, and shader work to isolated regions. Apply `will-change` only during known animation. Measure on target viewports and devices rather than assuming transform means fast. -### Feedback & Guidance -- **Hover hints**: Tooltip fade-ins, cursor changes, element highlights -- **Drag & drop**: Lift effect (shadow + scale), drop zone highlights, smooth repositioning -- **Copy/paste**: Brief highlight flash on paste, "copied" confirmation -- **Focus flow**: Highlight path through form or workflow +## Accessibility and control -### Delight Moments -- **Empty states**: Subtle floating animations on illustrations -- **Completed actions**: Confetti, check mark flourish, success celebrations -- **Easter eggs**: Hidden interactions for discovery -- **Contextual animation**: Weather effects, time-of-day themes, seasonal touches +Honor `prefers-reduced-motion` with an intentional alternative. Preserve state change and hierarchy while removing travel, parallax, flashing, or prolonged sequences. Do not replace every animation globally with `0.01ms` if that destroys useful feedback. Motion must never block focus, interaction, reading, or task completion. -## Technical Implementation +Respect autoplay and sound preferences. Any nonessential loop must stop when offscreen or hidden. -Use appropriate techniques for each animation: +## Verify -### Timing & Easing +- The focal motion is specific to the selected world and surface. +- Every supporting animation explains feedback, state, or relationship. +- Interruption and repeated use behave correctly. +- Desktop, mobile, keyboard, and reduced-motion paths remain usable. +- Expensive effects stay smooth on the target device. +- Removing an animation would lose meaning or authored character, not merely decoration. -**Duration: the 100/300/500 rule.** Timing matters more than easing for "feels right": - -| Duration | Use Case | Examples | -|----------|----------|----------| -| **100–150ms** | Instant feedback | Button press, toggle, color change | -| **200–300ms** | State changes | Menu open, tooltip, hover state | -| **300–500ms** | Layout changes | Accordion, modal, drawer | -| **500–800ms** | Entrance animations | Page load, hero reveal | - -**Easing curves (use these, not CSS defaults):** -```css -/* Recommended: natural deceleration */ ---ease-out-quart: cubic-bezier(0.25, 1, 0.5, 1); /* Smooth */ ---ease-out-quint: cubic-bezier(0.22, 1, 0.36, 1); /* Slightly snappier */ ---ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1); /* Confident, decisive */ - -/* AVOID: feel dated and tacky */ -/* bounce: cubic-bezier(0.34, 1.56, 0.64, 1); */ -/* elastic: cubic-bezier(0.68, -0.6, 0.32, 1.6); */ -``` - -**Exit animations are faster than entrances.** Use ~75% of enter duration. - -### CSS Animations -```css -/* Prefer for simple, declarative animations */ -- transitions for state changes -- @keyframes for complex sequences -- transform and opacity for reliable movement -- blur, filters, masks, clip paths, shadows, and color shifts for premium atmospheric effects when verified smooth -``` - -### JavaScript Animation -```javascript -/* Use for complex, interactive animations */ -- Web Animations API for programmatic control -- Framer Motion for React -- GSAP for complex sequences -``` - -### Motion Materials - -Transform and opacity are reliable defaults, not the whole palette. Premium interfaces often need atmospheric properties. Match material to effect: - -- **Transform / opacity**: movement, press feedback, simple reveals, list choreography -- **Blur / filter / backdrop-filter**: focus pulls, depth, glass or lens effects, softened entrances -- **Clip-path / masks**: wipes, reveals, editorial cropping, product-like transitions -- **Shadow / glow / color filters**: energy, affordance, focus, warmth, active state -- **Grid-template-rows or FLIP-style transforms**: expanding and reflowing layout without animating `height` directly - -The hard rule isn't "transform and opacity only." It's: avoid animating layout-driving properties casually (`width`, `height`, `top`, `left`, margins), keep expensive effects bounded to small or isolated areas, and verify smoothness in-browser on target viewports. - -### Performance -- **Layout safety**: Avoid casual animation of layout-driving properties (`width`, `height`, `top`, `left`, margins) -- **will-change**: Add sparingly for known expensive animations only (e.g. on `:hover` or an `.animating` class), never preemptively across the whole page -- **Scroll triggers**: Use Intersection Observer instead of scroll event listeners; unobserve after the animation fires once -- **Bound expensive effects**: Keep blur/filter/shadow areas small or isolated, use `contain` where appropriate -- **Monitor FPS**: Ensure 60fps on target devices - -### Perceived Performance - -Nobody cares how fast your site *is*, only how fast it feels. The 80ms threshold: anything under ~80ms feels instant because our brains buffer sensory input for that long to synchronize perception. Target this for micro-interactions. - -- **Preemptive start**: Begin transitions immediately while loading (iOS app zoom, skeleton UI). Users perceive work happening. -- **Early completion**: Show content progressively, don't wait for everything (progressive images, streaming HTML, skeleton fade-ins). -- **Optimistic UI**: Update the interface immediately, handle failures gracefully. Use for low-stakes actions (likes, follows). Avoid for payments or destructive operations. -- **Easing affects perceived duration**: Ease-in (accelerating toward completion) makes tasks feel shorter because the peak-end effect weights final moments heavily. Ease-out feels satisfying for entrances. -- **Caution**: Too-fast responses can decrease perceived value for complex operations (search, analysis). Sometimes a brief delay signals "real work" is happening. - -### Accessibility -```css -@media (prefers-reduced-motion: reduce) { - * { - animation-duration: 0.01ms !important; - animation-iteration-count: 1 !important; - transition-duration: 0.01ms !important; - } -} -``` - -**NEVER**: -- Use bounce or elastic easing curves; they feel dated and draw attention to the animation itself -- Animate layout properties casually (`width`, `height`, `top`, `left`, margins) when transform, FLIP, or grid-based techniques would work -- Use durations over 500ms for feedback (it feels laggy) -- Animate without purpose (every animation needs a reason) -- Ignore `prefers-reduced-motion` (this is an accessibility violation) -- Animate everything (animation fatigue makes interfaces feel exhausting) -- Block interaction during animations unless intentional - -## Verify Quality - -Test animations thoroughly: - -- **Smooth at 60fps**: No jank on target devices -- **Feels natural**: Easing curves feel organic, not robotic -- **Appropriate timing**: Not too fast (jarring) or too slow (laggy) -- **Reduced motion works**: Animations disabled or simplified appropriately -- **Doesn't block**: Users can interact during/after animations -- **Adds value**: Makes interface clearer or more delightful - -When the motion clarifies state instead of decorating it, hand off to `{{command_prefix}}impeccable polish` for the final pass. +When motion earns its place, hand off to `{{command_prefix}}impeccable polish` for the final pass. diff --git a/skill/reference/bolder.md b/skill/reference/bolder.md index c0918ae72..ec773a9dd 100644 --- a/skill/reference/bolder.md +++ b/skill/reference/bolder.md @@ -8,12 +8,12 @@ ## Why it reads flat -A section usually reads flat for reasons its neighbors have already solved. Look at what the rest of the page does that this section does not: the display type at full strength, the structural devices that carry meaning, the signature motif, the density and pacing, the specificity of the copy. A flat section is typically one that quietly opts out of the system's own strongest moves. The most reliable bolder pass brings the target up to the expressive level its neighbors already reach, in the system's own vocabulary rather than a new one. +A section usually reads flat for reasons its neighbors have already solved. Look at what the rest of the page does that this section does not: the display type at full strength, the structural devices that carry meaning, the signature motif, the density and pacing. A flat section is typically one that quietly opts out of the system's own strongest moves. The most reliable bolder pass brings the target up to the expressive level its neighbors already reach, in the system's own vocabulary rather than a new one. ## The amplification - **Amplify what the system already owns.** Reuse its motif and its type scale at full strength, turned up for this section rather than invented for it. The bolder version should look more like the same brand, not less. -- **Let the content carry the weight.** Flat copy makes a flat design, and no amount of size fixes a generic claim. Make every word specific enough to earn its place, and let the section's real evidence do the work that a decorative addition would otherwise fake. +- **Keep content true.** Existing claims are part of the scope: preserve them unless the user supplies replacements. If real evidence is essential to the direction but absent, ask for it. - **Commit, then clarify.** Half-measures read as noise. Make the one decisive move completely, then quiet everything around it so the move is legible. If every element got louder, the section got flatter. - **Give it its own rhythm.** The target should read as a peak in the scroll, a shift in density or pace from what surrounds it, not simply more of the same. diff --git a/skill/reference/clarify.md b/skill/reference/clarify.md index a4895a5d4..309a05679 100644 --- a/skill/reference/clarify.md +++ b/skill/reference/clarify.md @@ -1,288 +1,94 @@ -> **Additional context needed**: audience technical level and users' mental state in context. +> **Additional context needed**: audience knowledge and emotional state. -Find the unclear, confusing, or poorly written interface text and rewrite it. Vague copy creates support tickets and abandonment; specific copy gets users through the task. +Rewrite unclear interface text so users understand what happened, what matters, and what to do next. Preserve factual meaning, product terminology, and brand voice. +## Audit the language ---- +Read the entire interaction path, not isolated strings. Identify: -## Assess Current Copy +- ambiguous nouns, verbs, and actions; +- internal jargon or assumed knowledge; +- vague labels, outcomes, and system states; +- missing consequences, recovery, or timing; +- inconsistent terminology and capitalization; +- redundant headings, intros, helper text, and confirmations; +- text that breaks at realistic widths or in translation; +- tone that ignores stress, risk, success, or urgency. -Identify what makes the text unclear or ineffective: +Infer audience and task from product context and surrounding UI. Ask before changing factual claims, legal meaning, or a term that may be domain-specific. -1. **Find clarity problems**: - - **Jargon**: Technical terms users won't understand - - **Ambiguity**: Multiple interpretations possible - - **Passive voice**: "Your file has been uploaded" vs "We uploaded your file" - - **Length**: Too wordy or too terse - - **Assumptions**: Assuming user knowledge they don't have - - **Missing context**: Users don't know what to do or why - - **Tone mismatch**: Too formal, too casual, or inappropriate for situation +## Set the message hierarchy -2. **Understand the context**: - - Who's the audience? (Technical? General? First-time users?) - - What's the user's mental state? (Stressed during error? Confident during success?) - - What's the action? (What do we want users to do?) - - What's the constraint? (Character limits? Space limitations?) +For each state, decide: -**CRITICAL**: Clear copy helps users succeed. Unclear copy creates frustration, errors, and support tickets. +1. the one fact the user needs now; +2. the action available next; +3. supporting context that changes the decision; +4. the appropriate tone for this moment. -## Plan Copy Improvements +Say each idea once. If the heading already explains the state, the introduction should add new information or disappear. -Create a strategy for clearer communication: +## Rewrite by function -- **Primary message**: What's the ONE thing users need to know? -- **Action needed**: What should users do next (if anything)? -- **Tone**: How should this feel? (Helpful? Apologetic? Encouraging?) -- **Constraints**: Length limits, brand voice, localization considerations +### Actions and navigation -**IMPORTANT**: Good UX writing is invisible. Users should understand immediately without noticing the words. +Use a specific verb and object when the outcome is not already obvious. Labels should describe what will happen, not the gesture used to trigger it. Keep the same noun and verb for the same concept throughout the product. -## Improve Copy Systematically +For destructive actions, name the object and consequence. Prefer undo over confirmation when recovery is safe. When confirmation is necessary, name the action on both the message and button instead of using `Yes`, `No`, `OK`, or `Submit`. -Refine text across these common areas: +### Forms -### Error Messages -**Bad**: "Error 403: Forbidden" -**Good**: "You don't have permission to view this page. Contact your admin for access." +Use persistent labels; placeholders are examples, not labels. Put format and eligibility requirements before submission. Explain why information is requested only when it is not obvious. Required and optional treatment should be consistent. -**Bad**: "Invalid input" -**Good**: "Email addresses need an @ symbol. Try: name@example.com" +Validation says what needs attention and how to correct it without blaming the user. Keep related instructions near the field and announce errors accessibly. -**Principles**: -- Explain what went wrong in plain language -- Suggest how to fix it -- Don't blame the user -- Include examples when helpful -- Link to help/support if applicable - -### Form Labels & Instructions -**Bad**: "DOB (MM/DD/YYYY)" -**Good**: "Date of birth" (with placeholder showing format) - -**Bad**: "Enter value here" -**Good**: "Your email address" or "Company name" - -**Principles**: -- Use clear, specific labels (not generic placeholders) -- Show format expectations with examples -- Explain why you're asking (when not obvious) -- Put instructions before the field, not after -- Keep required field indicators clear - -### Button & CTA Text -**Bad**: "Click here" | "Submit" | "OK" -**Good**: "Create account" | "Save changes" | "Got it, thanks" +### Errors and permissions -**Principles**: -- Describe the action specifically -- Use active voice (verb + noun) -- Match user's mental model -- Be specific ("Save" is better than "OK") +An actionable error answers: -### Help Text & Tooltips -**Bad**: "This is the username field" -**Good**: "Choose a username. You can change this later in Settings." +1. what failed; +2. why, when known and useful; +3. how to recover or what alternative remains. -**Principles**: -- Add value (don't just repeat the label) -- Answer the implicit question ("What is this?" or "Why do you need this?") -- Keep it brief but complete -- Link to detailed docs if needed +Do not expose internal codes as the primary message. Do not promise a cause or resolution the system cannot know. Treat privacy, payment, deletion, access loss, and blocked work seriously; warmth is welcome, jokes are not. -### Empty States -**Bad**: "No items" -**Good**: "No projects yet. Create your first project to get started." +### Loading, empty, and success states -**Principles**: -- Explain why it's empty (if not obvious) -- Show next action clearly -- Make it welcoming, not dead-end +Loading text names the real operation and sets an honest expectation when the wait is meaningful. Show determinate progress when available; never invent progress. -### Success Messages -**Bad**: "Success" -**Good**: "Settings saved! Your changes will take effect immediately." +An empty state distinguishes first use, no results, filters, permissions, and failure. Explain the state and provide the next useful action. -**Principles**: -- Confirm what happened -- Explain what happens next (if relevant) -- Be brief but complete -- Match the user's emotional moment (celebrate big wins) - -### Loading States -**Bad**: "Loading..." (for 30+ seconds) -**Good**: "Analyzing your data... this usually takes 30-60 seconds" - -**Principles**: -- Set expectations (how long?) -- Explain what's happening (when it's not obvious) -- Show progress when possible -- Offer escape hatch if appropriate ("Cancel") +Success confirms the completed outcome and mentions the next consequence only when it changes what the user should do. Routine success should be brief. -### Confirmation Dialogs -**Bad**: "Are you sure?" -**Good**: "Delete 'Project Alpha'? This can't be undone." +### Help and instructional text -**Principles**: -- State the specific action -- Explain consequences (especially for destructive actions) -- Use clear button labels ("Delete project" not "Yes") -- Don't overuse confirmations (only for risky actions) +Helper text answers an implicit question instead of restating the control. Use progressive disclosure for uncommon detail. Link text must make sense out of context; icon-only controls need accessible names. -### Navigation & Wayfinding -**Bad**: Generic labels like "Items" | "Things" | "Stuff" -**Good**: Specific labels like "Your projects" | "Team members" | "Settings" +## Voice, accessibility, and localization -**Principles**: -- Be specific and descriptive -- Use language users understand (not internal jargon) -- Make hierarchy clear -- Consider information scent (breadcrumbs, current location) +Voice stays consistent; tone adapts to the moment. Use plain language without flattening terminology the audience genuinely knows. -## Apply Clarity Principles +- Write complete translatable messages rather than concatenated fragments. +- Keep variables and numbers structured so translators can reorder them. +- Allow expansion instead of abbreviating prematurely. +- Make alt text convey the image's information; use empty alt for decoration. +- Keep screen-reader names aligned with visible labels and outcomes. +- Do not rely on punctuation, color, or iconography to carry the message alone. -Every piece of copy should follow these rules: +Maintain a short terminology glossary when inconsistency spans the product. Do not vary words for literary effect in an interface. -1. **Be specific**: "Enter email" not "Enter value" -2. **Be concise**: Cut unnecessary words (but don't sacrifice clarity) -3. **Be active**: "Save changes" not "Changes will be saved" -4. **Be human**: "Oops, something went wrong" not "System error encountered" -5. **Tell users what to do**, not just what happened -6. **Be consistent**: Use same terms throughout (don't vary for variety) +## Verify -**NEVER**: -- Use jargon without explanation -- Blame users ("You made an error" → "This field is required") -- Be vague ("Something went wrong" without explanation) -- Use passive voice unnecessarily -- Write overly long explanations (be concise) -- Use humor for errors (be empathetic instead) -- Assume technical knowledge -- Vary terminology (pick one term and stick with it) -- Repeat information (headers restating intros, redundant explanations) -- Use placeholders as the only labels (they disappear when users type) +Read the flow in context and test: -## Verify Improvements +- comprehension without hidden product knowledge; +- actionability at errors, empty states, and decision points; +- factual accuracy and consistent terminology; +- scanability at target widths and 200% zoom; +- long names, localization expansion, pluralization, and dynamic values; +- accessible names and announced state changes; +- tone appropriate to consequence and emotional context. -Test that copy improvements work: +The final copy is as short as it can be without removing meaning or recovery. -- **Comprehension**: Can users understand without context? -- **Actionability**: Do users know what to do next? -- **Brevity**: Is it as short as possible while remaining clear? -- **Consistency**: Does it match terminology elsewhere? -- **Tone**: Is it appropriate for the situation? - -When the copy reads cleanly, hand off to `{{command_prefix}}impeccable polish` for the final pass. - ---- - -## Reference Material - -The sections below were previously `ux-writing.md` and live inline now so the clarify flow has its deep UX-writing reference in one place. - -### UX Writing - -#### The Button Label Problem - -**Never use "OK", "Submit", or "Yes/No".** These are lazy and ambiguous. Use specific verb + object patterns: - -| Bad | Good | Why | -|-----|------|-----| -| OK | Save changes | Says what will happen | -| Submit | Create account | Outcome-focused | -| Yes | Delete message | Confirms the action | -| Cancel | Keep editing | Clarifies what "cancel" means | -| Click here | Download PDF | Describes the destination | - -**For destructive actions**, name the destruction: -- "Delete" not "Remove" (delete is permanent, remove implies recoverable) -- "Delete 5 items" not "Delete selected" (show the count) - -#### Error Messages: The Formula - -Every error message should answer: (1) What happened? (2) Why? (3) How to fix it? Example: "Email address isn't valid. Please include an @ symbol." not "Invalid input". - -##### Error Message Templates - -| Situation | Template | -|-----------|----------| -| **Format error** | "[Field] needs to be [format]. Example: [example]" | -| **Missing required** | "Please enter [what's missing]" | -| **Permission denied** | "You don't have access to [thing]. [What to do instead]" | -| **Network error** | "We couldn't reach [thing]. Check your connection and [action]." | -| **Server error** | "Something went wrong on our end. We're looking into it. [Alternative action]" | - -##### Don't Blame the User - -Reframe errors: "Please enter a date in MM/DD/YYYY format" not "You entered an invalid date". - -#### Empty States Are Opportunities - -Empty states are onboarding moments: (1) Acknowledge briefly, (2) Explain the value of filling it, (3) Provide a clear action. "No projects yet. Create your first one to get started." not just "No items". - -#### Voice vs Tone - -**Voice** is your brand's personality, consistent everywhere. -**Tone** adapts to the moment. - -| Moment | Tone Shift | -|--------|------------| -| Success | Celebratory, brief: "Done! Your changes are live." | -| Error | Empathetic, helpful: "That didn't work. Here's what to try..." | -| Loading | Reassuring: "Saving your work..." | -| Destructive confirm | Serious, clear: "Delete this project? This can't be undone." | - -**Never use humor for errors.** Users are already frustrated. Be helpful, not cute. - -#### Writing for Accessibility - -**Link text** must have standalone meaning: "View pricing plans" not "Click here". **Alt text** describes information, not the image: "Revenue increased 40% in Q4" not "Chart". Use `alt=""` for decorative images. **Icon buttons** need `aria-label` for screen reader context. - -#### Writing for Translation - -##### Plan for Expansion - -German text is ~30% longer than English. Allocate space: - -| Language | Expansion | -|----------|-----------| -| German | +30% | -| French | +20% | -| Finnish | +30-40% | -| Chinese | -30% (fewer chars, but same width) | - -##### Translation-Friendly Patterns - -Keep numbers separate ("New messages: 3" not "You have 3 new messages"). Use full sentences as single strings (word order varies by language). Avoid abbreviations ("5 minutes ago" not "5 mins ago"). Give translators context about where strings appear. - -#### Consistency: The Terminology Problem - -Pick one term and stick with it: - -| Inconsistent | Consistent | -|--------------|------------| -| Delete / Remove / Trash | Delete | -| Settings / Preferences / Options | Settings | -| Sign in / Log in / Enter | Sign in | -| Create / Add / New | Create | - -Build a terminology glossary and enforce it. Variety creates confusion. - -#### Avoid Redundant Copy - -If the heading explains it, the intro is redundant. If the button is clear, don't explain it again. Say it once, say it well. - -#### Loading States - -Be specific: "Saving your draft..." not "Loading...". For long waits, set expectations ("This usually takes 30 seconds") or show progress. - -#### Confirmation Dialogs: Use Sparingly - -Most confirmation dialogs are design failures; consider undo instead. When you must confirm: name the action, explain consequences, use specific button labels ("Delete project" / "Keep project", not "Yes" / "No"). - -#### Form Instructions - -Show format with placeholders, not instructions. For non-obvious fields, explain why you're asking. - ---- - -**Avoid**: Jargon without explanation. Blaming users ("You made an error" → "This field is required"). Vague errors ("Something went wrong"). Varying terminology for variety. Humor for errors. +When the language reads cleanly, hand off to `{{command_prefix}}impeccable polish` for the final pass. diff --git a/skill/reference/codex.md b/skill/reference/codex.md index b86a14568..6c550f1dc 100644 --- a/skill/reference/codex.md +++ b/skill/reference/codex.md @@ -1,6 +1,6 @@ # Codex: Surface Probes & Asset Production -Load this from [new-work.md](new-work.md) only when the harness has native image generation and a substantial, high-fidelity surface would benefit from seeing the shortlisted concept before code. PRODUCT.md and DESIGN.md are preconditions. Init has already established the visual world; this file must not reopen it. +Load this from [new-work.md](new-work.md) only when the harness has native image generation and a substantial, high-fidelity surface would benefit from seeing the shortlisted concept before code. PRODUCT.md and DESIGN.md are preconditions. New-work has already resolved the visual world; this file must not reopen it. The purpose of a probe is to test composition, narrative, hierarchy, density, focal moment, signature use, and image requirements. It is not a second identity workshop. Keep DESIGN.md's palette, typography direction, material language, component character, imagery stance, and motion grammar fixed. diff --git a/skill/reference/colorize.md b/skill/reference/colorize.md index 7f9acc3b6..14f2b5317 100644 --- a/skill/reference/colorize.md +++ b/skill/reference/colorize.md @@ -1,257 +1,86 @@ > **Additional context needed**: existing brand colors. -Replace timid grayscale or single-accent designs with a strategic palette: pick a color strategy, choose a hue family that fits the brand, then apply color with intent. More color ≠ better. Strategic color beats rainbow vomit. +Introduce color as hierarchy, meaning, and atmosphere. Preserve confirmed brand and semantic conventions; do not replace a visual world under the guise of colorizing it. --- -## Register +## Visitor mode -Persuade + Experience: palette IS voice. Pick a color strategy first per SKILL.md (Restrained / Committed / Full palette / Drenched) and follow its dosage. Committed, Full palette, and Drenched deliberately exceed the ≤10% rule; that rule is Restrained only. Unexpected combinations are allowed; a dominant color can own the page when the chosen strategy calls for it. +- **Persuade + Experience:** color may carry the voice and own large regions when the selected world calls for it. +- **Operate + Read:** color primarily encodes action, selection, status, wayfinding, and reading hierarchy. Rarity gives an accent force. -Operate + Read: semantic-first and almost always Restrained. Accent color is reserved for primary action, current selection, and state indicators. Not decoration. Every color has a consistent meaning across every screen. +## Audit before choosing ---- +Read DESIGN.md, tokens, assets, current themes, and representative states. Identify: -## Assess Color Opportunity +- which colors are confirmed brand commitments; +- current surface, text, action, and semantic roles; +- places where grayscale obscures hierarchy or state; +- contrast failures and color-only communication; +- light/dark or data-visualization requirements; +- whether the task asks for more color or a new identity. -Analyze the current state and identify opportunities: +If a new identity is required, use [new-work.md](new-work.md). Ask only when a binding brand decision cannot be inferred. -1. **Understand current state**: - - **Color absence**: Pure grayscale? Limited neutrals? One timid accent? - - **Missed opportunities**: Where could color add meaning, hierarchy, or delight? - - **Context**: What's appropriate for this domain and audience? - - **Brand**: Are there existing brand colors we should use? +## Choose a strategy -2. **Identify where color adds value**: - - **Semantic meaning**: Success (green), error (red), warning (yellow/orange), info (blue) - - **Hierarchy**: Drawing attention to important elements - - **Categorization**: Different sections, types, or states - - **Emotional tone**: Warmth, energy, trust, creativity - - **Wayfinding**: Helping users navigate and understand structure - - **Delight**: Moments of visual interest and personality +Name the intended emotional temperature, dominant relationship, contrast range, and color dosage before editing. The strategy may be restrained or immersive; it must follow the brief and selected world rather than a fixed percentage rule. -If any of these are unclear from the codebase, {{ask_instruction}} +Build roles, not a bag of swatches: -**CRITICAL**: More color ≠ better. Strategic color beats rainbow vomit every time. Every color should have a purpose. +- canvas and elevated surfaces; +- primary and secondary text; +- action, focus, and selection; +- borders and separators; +- success, warning, error, and information; +- data categories or scales when needed. -## Plan Color Strategy +Use the project's existing color space. For a new web palette, prefer OKLCH because lightness and chroma can be adjusted predictably. Choose hue from product meaning and visual direction, never from a default category association. -Create a purposeful color introduction plan: +## Apply at system scale -- **Color palette**: What colors match the brand/context? (Choose 2-4 colors max beyond neutrals) -- **Dominant color**: Which color owns 60% of colored elements? -- **Accent colors**: Which colors provide contrast and highlights? (30% and 10%) -- **Application strategy**: Where does each color appear and why? +- Let the strongest color own a deliberate region or role instead of scattering tiny accents. +- Keep the primary action easy to find; do not spend its color on decoration. +- Tint neutrals only when the brand hue genuinely creates cohesion. Neutral gray is valid when it serves the world. +- On colored surfaces, derive secondary text from the foreground or surface hue rather than using washed-out generic gray. +- Keep semantic meanings consistent, but respect platform and domain conventions instead of assuming fixed hues. +- For data, use distinct lightness, chroma, shape, label, or pattern so color is not the only code. +- In dark mode, design surface elevation and contrast explicitly; do not invert the light theme mechanically. +- Define primitive values and semantic tokens when the project has a token system. Theme changes should normally remap semantic roles. -**IMPORTANT**: Color should enhance hierarchy and meaning, not create chaos. Less is more when it matters more. +Avoid decorative color that has no relation to hierarchy, state, content, or the visual world. Generic gradients, blobs, side stripes, and arbitrary colored headings are not a color strategy. -## Introduce Color Strategically +## Contrast and perception -Add color systematically across these dimensions: +Verify computed foreground/background pairs: -### Semantic Color -- **State indicators**: - - Success: Green tones (emerald, forest, mint) - - Error: Red/pink tones (rose, crimson, coral) - - Warning: Orange/amber tones - - Info: Blue tones (sky, ocean, indigo) - - Neutral: Gray/slate for inactive states +| Content | WCAG AA minimum | +|---|---| +| body text | 4.5:1 | +| large text | 3:1 | +| controls, icons, focus indicators | 3:1 | -- **Status badges**: Colored backgrounds or borders for states (active, pending, completed, etc.) -- **Progress indicators**: Colored bars, rings, or charts showing completion or health +Do not rely on eyesight alone. Check interactive states, overlays, text on images, disabled content, and both themes. Simulate common vision deficiencies. Information conveyed by color also needs text, shape, iconography, or position. -### Accent Color Application -- **Primary actions**: Color the most important buttons/CTAs -- **Links**: Add color to clickable text (maintain accessibility) -- **Icons**: Colorize key icons for recognition and personality -- **Headers/titles**: Add color to section headers or key labels -- **Hover states**: Introduce color on interaction +When deriving OKLCH ramps, vary lightness and reduce chroma near white and black. Do not keep high chroma at extreme lightness merely to make the math uniform. Prefer explicit colors over chains of translucent overlays when alpha would make contrast context-dependent. -### Background & Surfaces -- **Tinted backgrounds**: If you replace pure gray, tint toward the brand hue, not toward a generic-warm-or-cool pair. The default-warm-tint (`oklch(97% 0.01 60)` and its neighbors) is now the AI cream/sand giveaway. Be specific to the brand or stay neutral. -- **Colored sections**: Use subtle background colors to separate areas -- **Gradient backgrounds**: Add depth with subtle, intentional gradients (not generic purple-blue) -- **Cards & surfaces**: Tint cards or surfaces toward the brand, not "for warmth" by reflex +## Verify -**Use OKLCH for color**: It's perceptually uniform, meaning equal steps in lightness *look* equal. Great for generating harmonious scales. - -### Data Visualization -- **Charts & graphs**: Use color to encode categories or values -- **Heatmaps**: Color intensity shows density or importance -- **Comparison**: Color coding for different datasets or timeframes - -### Borders & Accents -- **Hairline borders**: 1px colored borders on full perimeter (not side-stripes; see the absolute ban on `border-left/right > 1px`) -- **Underlines**: Color underlines for emphasis or active states -- **Dividers**: Subtle colored dividers instead of gray lines -- **Focus rings**: Colored focus indicators matching brand -- **Surface tints**: A 4-8% background wash of the accent color instead of a stripe - -**NEVER**: `border-left` or `border-right` greater than 1px as a colored accent stripe. This is one of the three absolute bans in the parent skill. If you want to mark a card as "active" or "warning", use a full hairline border, a background tint, a leading glyph, or a numbered prefix. Not a side stripe. - -### Typography Color -- **Colored headings**: Use brand colors for section headings (maintain contrast) -- **Highlight text**: Color for emphasis or categories -- **Labels & tags**: Small colored labels for metadata or categories - -### Decorative Elements -- **Illustrations**: Add colored illustrations or icons -- **Shapes**: Geometric shapes in brand colors as background elements -- **Gradients**: Colorful gradient overlays or mesh backgrounds -- **Blobs/organic shapes**: Soft colored shapes for visual interest - -## Balance & Refinement - -Ensure color addition improves rather than overwhelms: - -### Maintain Hierarchy -- **Dominant color** (60%): Primary brand color or most used accent -- **Secondary color** (30%): Supporting color for variety -- **Accent color** (10%): High contrast for key moments -- **Neutrals** (remaining): Gray/black/white for structure - -### Accessibility -- **Contrast ratios**: Ensure WCAG compliance (4.5:1 for text, 3:1 for UI components) -- **Don't rely on color alone**: Use icons, labels, or patterns alongside color -- **Test for color blindness**: Verify red/green combinations work for all users - -### Cohesion -- **Consistent palette**: Use colors from defined palette, not arbitrary choices -- **Systematic application**: Same color meanings throughout (green always = success) -- **Temperature consistency**: Warm palette stays warm, cool stays cool - -**NEVER**: -- Use every color in the rainbow (choose 2-4 colors beyond neutrals) -- Apply color randomly without semantic meaning -- Put gray text on colored backgrounds. It looks washed out; use a darker shade of the background color or transparency instead -- Violate WCAG contrast requirements -- Use color as the only indicator (accessibility issue) -- Make everything colorful (defeats the purpose) -- Default to purple-blue gradients (AI slop aesthetic) - -## Verify Color Addition - -Test that colorization improves the experience: - -- **Better hierarchy**: Does color guide attention appropriately? -- **Clearer meaning**: Does color help users understand states/categories? -- **More engaging**: Does the interface feel warmer and more inviting? -- **Still accessible**: Do all color combinations meet WCAG standards? -- **Not overwhelming**: Is color balanced and purposeful? +- Every color has a stable role or a world-specific atmospheric purpose. +- Attention lands on the intended action, content, or state. +- The palette works across quiet, dense, interactive, error, and empty states. +- Light and dark themes are each composed, not mechanically inverted. +- Contrast and non-color cues pass in all relevant states. +- The result is recognizably this product, not a generic “colorful” treatment. When the palette earns its place, hand off to `{{command_prefix}}impeccable polish` for the final pass. ## Live-mode signature params -When invoked from live mode, each variant MUST declare a `color-amount` param so the user can dial between a restrained accent and a drenched surface without regeneration. Author the variant's CSS against `var(--p-color-amount, 0.5)`, typically as the alpha multiplier on backgrounds, or as a scaling factor on the chroma axis in an OKLCH expression. 0 = neutral/monochrome, 1 = full saturation / dominant coverage. +When invoked from live mode, every variant declares a `color-amount` parameter. Author CSS against `var(--p-color-amount, 0.5)` so the user can move from neutral to the variant's full color strategy without regeneration. ```json {"id":"color-amount","kind":"range","min":0,"max":1,"step":0.05,"default":0.5,"label":"Color amount"} ``` -Layer 1-2 variant-specific params on top: palette selection (`steps` with named options), temperature warmth, or tint vs. true color. See `reference/live.md` for the full params contract. - ---- - -## Reference Material - -The sections below were previously `color-and-contrast.md` and live inline now so the colorize flow has its deep color reference in one place. - -### Color & Contrast - -#### Color Spaces: Use OKLCH - -**Stop using HSL.** Use OKLCH (or LCH) instead. It's perceptually uniform, meaning equal steps in lightness *look* equal, unlike HSL where 50% lightness in yellow looks bright while 50% in blue looks dark. - -The OKLCH function takes three components: `oklch(lightness chroma hue)` where lightness is 0-100%, chroma is roughly 0-0.4, and hue is 0-360. To build a primary color and its lighter / darker variants, hold the chroma+hue roughly constant and vary the lightness, but **reduce chroma as you approach white or black**, because high chroma at extreme lightness looks garish. - -The hue you pick is a brand decision and should not come from a default. Do not reach for blue (hue 250) or warm orange (hue 60) by reflex; those are the dominant AI-design defaults, not the right answer for any specific brand. - -#### Building Functional Palettes - -##### Tinted Neutrals - -**Pure gray is dead.** A neutral with zero chroma feels lifeless next to a colored brand. Add a tiny chroma value (0.005-0.015) to all your neutrals, hued toward whatever your brand color is. The chroma is small enough not to read as "tinted" consciously, but it creates subconscious cohesion between brand color and UI surfaces. - -The hue you tint toward should come from THIS project's brand, not from a "warm = friendly, cool = tech" formula. If your brand color is teal, your neutrals lean toward teal. If your brand color is amber, they lean toward amber. The point is cohesion with the SPECIFIC brand, not a stock palette. - -**Avoid** the trap of always tinting toward warm orange or always tinting toward cool blue. Those are the two laziest defaults and they create their own monoculture across projects. - -##### Palette Structure - -A complete system needs: - -| Role | Purpose | Example | -|------|---------|---------| -| **Primary** | Brand, CTAs, key actions | 1 color, 3-5 shades | -| **Neutral** | Text, backgrounds, borders | 9-11 shade scale | -| **Semantic** | Success, error, warning, info | 4 colors, 2-3 shades each | -| **Surface** | Cards, modals, overlays | 2-3 elevation levels | - -**Skip secondary/tertiary unless you need them.** Most apps work fine with one accent color. Adding more creates decision fatigue and visual noise. - -##### The 60-30-10 Rule (Applied Correctly) - -This rule is about **visual weight**, not pixel count: - -- **60%**: Neutral backgrounds, white space, base surfaces -- **30%**: Secondary colors: text, borders, inactive states -- **10%**: Accent: CTAs, highlights, focus states - -The common mistake: using the accent color everywhere because it's "the brand color." Accent colors work *because* they're rare. Overuse kills their power. - -#### Contrast & Accessibility - -##### WCAG Requirements - -| Content Type | AA Minimum | AAA Target | -|--------------|------------|------------| -| Body text | 4.5:1 | 7:1 | -| Large text (18px+ or 14px bold) | 3:1 | 4.5:1 | -| UI components, icons | 3:1 | 4.5:1 | -| Non-essential decorations | None | None | - -##### Dangerous Color Combinations - -These commonly fail contrast or cause readability issues: - -- Light gray text on white (the #1 accessibility fail) -- Red text on green background (or vice versa): 8% of men can't distinguish these -- Blue text on red background (vibrates visually) -- Yellow text on white (almost always fails) -- Thin light text on images (unpredictable contrast) - -##### Testing - -Don't trust your eyes. Use tools: - -- [WebAIM Contrast Checker](https://webaim.org/resources/contrastchecker/) -- Browser DevTools → Rendering → Emulate vision deficiencies -- [Polypane](https://polypane.app/) for real-time testing - -#### Theming: Light & Dark Mode - -##### Dark Mode Is Not Inverted Light Mode - -You can't just swap colors. Dark mode requires different design decisions: - -| Light Mode | Dark Mode | -|------------|-----------| -| Shadows for depth | Lighter surfaces for depth (no shadows) | -| Dark text on light | Light text on dark (reduce font weight) | -| Vibrant accents | Desaturate accents slightly | -| White backgrounds | Either pure black or a deep surface that fits the brand (a brand-tinted near-black at oklch 12-18% works too) | - -In dark mode, depth comes from surface lightness, not shadow. Build a 3-step surface scale where higher elevations are lighter (e.g. 15% / 20% / 25% lightness). Use the SAME hue and chroma as your brand color (whatever it is for THIS project; do not reach for blue) and only vary the lightness. Reduce body text weight slightly (e.g. 350 instead of 400) because light text on dark reads as heavier than dark text on light. - -##### Token Hierarchy - -Use two layers: primitive tokens (`--blue-500`) and semantic tokens (`--color-primary: var(--blue-500)`). For dark mode, only redefine the semantic layer; primitives stay the same. - -#### Alpha Is A Design Smell - -Heavy use of transparency (rgba, hsla) usually means an incomplete palette. Alpha creates unpredictable contrast, performance overhead, and inconsistency. Define explicit overlay colors for each context instead. Exception: focus rings and interactive states where see-through is needed. - ---- - -**Avoid**: Relying on color alone to convey information. Creating palettes without clear roles for each color. Skipping color blindness testing (8% of men affected). +Add at most two variant-specific parameters, such as palette, temperature, or tint behavior. Follow [live.md](live.md)'s parameter contract. diff --git a/skill/reference/craft.md b/skill/reference/craft.md index 7a5b01932..dbbc9402c 100644 --- a/skill/reference/craft.md +++ b/skill/reference/craft.md @@ -1,5 +1,5 @@ -# Craft +# Craft (deprecated alias) -`craft` is the standard discovery-to-build flow. Resolve SKILL.md's init gate first: PRODUCT.md and DESIGN.md must establish the durable product and visual world. Then follow [new-work.md](new-work.md) to discover the task, develop genuinely different surface concepts inside that world, get the user's direction, write the auditable contract, build, and finish. +`craft` is a deprecated alias for an ordinary request to make new visual work. It adds no setup, interview, checkpoint, tool, or quality behavior. Apply SKILL.md's normal routing: create missing PRODUCT.md through [init.md](init.md), then follow [new-work.md](new-work.md) for visual authority, world and surface decisions, implementation, and finish. -Invoking `craft` explicitly makes the task checkpoints attended whenever a human or structured simulated-user tool exists. It does not rerun the identity workshop for every section or feature, and it does not skip creative collaboration merely because DESIGN.md already exists. +Do not tell users they need to invoke `craft`. Natural requests such as “build this feature,” “make a landing page,” or “redesign this screen” use the same flow. diff --git a/skill/reference/delight.md b/skill/reference/delight.md index 80280114f..58c17eb5f 100644 --- a/skill/reference/delight.md +++ b/skill/reference/delight.md @@ -1,302 +1,70 @@ -> **Additional context needed**: what's appropriate for the domain (playful vs professional vs quirky vs elegant). +> **Additional context needed**: the brand's emotional range. -Find the moments where personality and unexpected polish would turn a functional interface into one users remember and tell other people about. Add only where the moment earns it; delight everywhere reads as noise. +Make the experience memorable at moments that earn it. Delight is not a layer of generic whimsy; it is product character revealed through a useful interaction, a humane response, or an unexpectedly considered detail. --- -## Register +## Visitor mode -Persuade + Experience: delight can be distributed across copy voice, section transitions, discovery rewards, seasonal touches, personality across the whole surface. +- **Persuade + Experience:** personality may run through voice, composition, motion, and discovery, provided the artifact remains the focus. +- **Operate + Read:** concentrate delight at meaningful moments such as first use, completion, recovery, or mastery. Reliability carries everything else. -Operate + Read: delight at specific moments, not pages. Completion, first-time actions, error recovery, milestone crossings. Reliability and consistency carry the rest of the experience; delight pushed everywhere reads as noise. +## Find the opportunity ---- +Inspect the target, DESIGN.md, product voice, repeated-use frequency, and emotional context. Look for: -## Assess Delight Opportunities +- effort worth acknowledging; +- waiting that can become informative; +- an empty or first-use state that can orient; +- an error or recovery moment that needs empathy; +- an interaction whose physical or verbal response could express the brand; +- a useful capability people might enjoy discovering. -Identify where delight would enhance (not distract from) the experience: +Do not manufacture a celebration for an ordinary click. Ask only when the brand's emotional range or the stakes cannot be inferred. -1. **Find natural delight moments**: - - **Success states**: Completed actions (save, send, publish) - - **Empty states**: First-time experiences, onboarding - - **Loading states**: Waiting periods that could be entertaining - - **Achievements**: Milestones, streaks, completions - - **Interactions**: Hover states, clicks, drags - - **Errors**: Softening frustrating moments - - **Easter eggs**: Hidden discoveries for curious users +## Define one delight thesis -2. **Understand the context**: - - What's the brand personality? (Playful? Professional? Quirky? Elegant?) - - Who's the audience? (Tech-savvy? Creative? Corporate?) - - What's the emotional context? (Accomplishment? Exploration? Frustration?) - - What's appropriate? (Banking app ≠ gaming app) +State in one sentence what the user should feel and why that feeling belongs to this product. Then choose the smallest system that can deliver it: -3. **Define delight strategy**: - - **Subtle sophistication**: Refined micro-interactions (luxury brands) - - **Playful personality**: Whimsical illustrations and copy (consumer apps) - - **Helpful surprises**: Anticipating needs before users ask (productivity tools) - - **Sensory richness**: Satisfying sounds, smooth animations (creative tools) +- a distinctive response to a meaningful action; +- product-specific language that clarifies while carrying voice; +- an interaction or transition with a recognizable material behavior; +- an illustration, sound, haptic, or environmental detail grounded in the product world; +- a discovery reward that reveals real utility. -If any of these are unclear from the codebase, {{ask_instruction}} +Derive the treatment from product mechanism and visual world. Do not select from a stock catalog of confetti, particles, mascots, novelty cursors, jokes, or hover lifts. -**CRITICAL**: Delight should enhance usability, never obscure it. If users notice the delight more than accomplishing their goal, you've gone too far. +## Build for the emotional moment -## Delight Principles +- **Success:** match the response to the effort and consequence. Major milestones can expand; routine saves should simply feel certain. +- **Waiting:** show truthful progress, useful context, or product-specific activity. Never fake work or delay completion to stage a flourish. +- **Empty and first use:** make the next action clear before adding personality. +- **Error and recovery:** lead with the problem and recovery. Warmth may reduce stress; jokes must not trivialize loss, money, privacy, or blocked work. +- **Repeated interaction:** keep the response satisfying after the hundredth use. Variation is useful only when it remains coherent and predictable enough to trust. +- **Discovery:** reward curiosity without hiding required functionality. -Follow these guidelines: +Copy must use the product's language. Generic whimsy is worse than neutral clarity. -### Delight Amplifies, Never Blocks -- Delight moments should be quick (< 1 second) -- Never delay core functionality for delight -- Make delight skippable or subtle -- Respect user's time and task focus +## Protect the experience -### Surprise and Discovery -- Hide delightful details for users to discover -- Reward exploration and curiosity -- Don't announce every delight moment -- Let users share discoveries with others +Delight must not: -### Appropriate to Context -- Match delight to emotional moment (celebrate success, empathize with errors) -- Respect the user's state (don't be playful during critical errors) -- Match brand personality and audience expectations -- Cultural sensitivity (what's delightful varies by culture) +- delay, block, or obscure the primary task; +- override platform conventions or accessibility; +- add unrequested factual claims; +- play sound without consent or ignore mute settings; +- become mandatory, unskippable, or exhausting on repeat; +- add a dependency or asset cost disproportionate to the moment. -### Compound Over Time -- Delight should remain fresh with repeated use -- Vary responses (not same animation every time) -- Reveal deeper layers with continued use -- Build anticipation through patterns +For authored motion, load [animate.md](animate.md). Respect reduced motion, screen readers, keyboard use, touch, localization, and cultural context. Nonessential loops stop when hidden. Make celebration intensity proportional to frequency and consequence. -## Delight Techniques +## Verify -Add personality and joy through these methods: +- The moment is specific enough that a neighboring product could not use it unchanged. +- It improves comprehension, confidence, motivation, or emotional recovery. +- The interface remains fast and obvious without the flourish. +- Repetition does not turn charm into friction. +- Reduced-motion, muted, keyboard, touch, and localized paths work. +- The result feels like the selected world, not a generic “delight” treatment. -### Micro-interactions & Animation - -**Button delight**: -```css -/* Satisfying button press */ -.button { - transition: transform 0.1s, box-shadow 0.1s; -} -.button:active { - transform: translateY(2px); - box-shadow: 0 2px 4px rgba(0,0,0,0.2); -} - -/* Ripple effect on click */ -/* Smooth lift on hover */ -.button:hover { - transform: translateY(-2px); - transition: transform 0.2s cubic-bezier(0.25, 1, 0.5, 1); /* ease-out-quart */ -} -``` - -**Loading delight**: -- Playful loading animations (not just spinners) -- Personality in loading messages (write product-specific ones, not generic AI filler) -- Progress indication with encouraging messages -- Skeleton screens with subtle animations - -**Success animations**: -- Checkmark draw animation -- Confetti burst for major achievements -- Gentle scale + fade for confirmation -- Satisfying sound effects (subtle) - -**Hover surprises**: -- Icons that animate on hover -- Color shifts or glow effects -- Tooltip reveals with personality -- Cursor changes (custom cursors for branded experiences) - -### Personality in Copy - -**Playful error messages**: -``` -"Error 404" -"This page is playing hide and seek. (And winning)" - -"Connection failed" -"Looks like the internet took a coffee break. Want to retry?" -``` - -**Encouraging empty states**: -``` -"No projects" -"Your canvas awaits. Create something amazing." - -"No saved articles" -"Nothing on the nightstand yet. Save a story for later." -``` - -**Playful labels & tooltips**: -``` -"Delete" -"Send to void" (for playful brand) - -"Help" -"Rescue me" (tooltip) -``` - -**IMPORTANT**: Match copy personality to brand. Banks shouldn't be wacky, but they can be warm. - -### Illustrations & Visual Personality - -**Custom illustrations**: -- Empty state illustrations (not stock icons) -- Error state illustrations (friendly monsters, quirky characters) -- Loading state illustrations (animated characters) -- Success state illustrations (celebrations) - -**Icon personality**: -- Custom icon set matching brand personality -- Animated icons (subtle motion on hover/click) -- Illustrative icons (more detailed than generic) -- Consistent style across all icons - -**Background effects**: -- Subtle particle effects -- Gradient mesh backgrounds -- Geometric patterns -- Parallax depth -- Time-of-day themes (morning vs night) - -### Satisfying Interactions - -**Drag and drop delight**: -- Lift effect on drag (shadow, scale) -- Snap animation when dropped -- Satisfying placement sound -- Undo toast ("Dropped in wrong place? [Undo]") - -**Toggle switches**: -- Smooth slide with spring physics -- Color transition -- Haptic feedback on mobile -- Optional sound effect - -**Progress & achievements** (Operate surfaces with recurring tasks only; gamification on editorial, portfolio, or one-visit surfaces reads as noise): -- Streak counters with celebratory milestones -- Progress bars that "celebrate" at 100% -- Badge unlocks with animation -- Playful stats ("You're on fire! 5 days in a row") - -**Form interactions**: -- Input fields that animate on focus -- Checkboxes with a satisfying scale pulse when checked -- Success state that celebrates valid input -- Auto-grow textareas - -### Sound Design - -**Subtle audio cues** (when appropriate): -- Notification sounds (distinctive but not annoying) -- Success sounds (satisfying "ding") -- Error sounds (empathetic, not harsh) -- Typing sounds for chat/messaging -- Ambient background audio (very subtle) - -**IMPORTANT**: -- Respect system sound settings -- Provide mute option -- Keep volumes quiet (subtle cues, not alarms) -- Don't play on every interaction (sound fatigue is real) - -### Easter Eggs & Hidden Delights - -**Discovery rewards**: -- Konami code unlocks special theme -- Hidden keyboard shortcuts (Cmd+K for special features) -- Hover reveals on logos or illustrations -- Alt text jokes on images (for screen reader users too!) -- A view-source or console colophon (a note on the typefaces, a thank-you, a hiring line) - -**Seasonal touches**: -- Holiday themes (subtle, tasteful) -- Seasonal color shifts -- Weather-based variations -- Time-based changes (dark at night, light during day) - -**Contextual personality**: -- Different messages based on time of day -- Responses to specific user actions -- Randomized variations (not same every time) -- Progressive reveals with continued use - -### Loading & Waiting States - -**Make waiting engaging**: -- Interesting loading messages that rotate -- Progress bars with personality -- Mini-games during long loads -- Fun facts or tips while waiting -- Countdown with encouraging messages - -``` -Loading messages: write ones specific to your product, not generic AI filler: -- "Syncing with your team's changes..." -- "Fetching this week's issue..." -- "Developing your photos..." -- "Checking tomorrow's tide tables..." -``` - -**WARNING**: Avoid cliched loading messages like "Herding pixels", "Teaching robots to dance", "Consulting the magic 8-ball", "Counting backwards from infinity". These are AI-slop copy, instantly recognizable as machine-generated. Write messages that are specific to what your product actually does. - -### Celebration Moments - -**Success celebrations**: -- Confetti for major milestones -- Animated checkmarks for completions -- Progress bar celebrations at 100% -- "Achievement unlocked" style notifications -- Personalized messages ("You published your 10th article!") - -**Milestone recognition** (same scoping as Progress & achievements: Operate surfaces with recurring tasks): -- First-time actions get special treatment -- Streak tracking and celebration -- Progress toward goals -- Anniversary celebrations - -## Implementation Patterns - -**Animation libraries**: -- Framer Motion (React) -- GSAP (universal) -- Lottie (After Effects animations) -- Canvas confetti (party effects) - -**Sound libraries**: -- Howler.js (audio management) -- Use-sound (React hook) - -**Physics libraries**: -- React Spring (spring physics) -- Popmotion (animation primitives) - -**IMPORTANT**: File size matters. Compress images, optimize animations, lazy load delight features. - -**NEVER**: -- Delay core functionality for delight -- Force users through delightful moments (make skippable) -- Use delight to hide poor UX -- Overdo it (less is more) -- Ignore accessibility (animate responsibly, provide alternatives) -- Make every interaction delightful (special moments should be special) -- Sacrifice performance for delight -- Be inappropriate for context (read the room) - -## Verify Delight Quality - -Test that delight actually delights: - -- **User reactions**: Do users smile? Share screenshots? -- **Doesn't annoy**: Still pleasant after 100th time? -- **Doesn't block**: Can users opt out or skip? -- **Performant**: No jank, no slowdown -- **Appropriate**: Matches brand and context -- **Accessible**: Works with reduced motion, screen readers - -When the moments feel earned, hand off to `{{command_prefix}}impeccable polish` for the final pass. +When the personality feels earned, hand off to `{{command_prefix}}impeccable polish` for the final pass. diff --git a/skill/reference/document.md b/skill/reference/document.md index 37f41281f..eb39f53e5 100644 --- a/skill/reference/document.md +++ b/skill/reference/document.md @@ -1,6 +1,6 @@ Generate a `DESIGN.md` file at the project root that captures the current visual design system, so AI agents generating new screens stay on-brand. -DESIGN.md follows the [official DESIGN.md format spec](https://raw.githubusercontent.com/google-labs-code/design.md/main/docs/spec.md): YAML frontmatter carrying machine-readable design tokens, followed by a markdown body with exactly six sections in a fixed order. **Tokens are normative; prose provides context for how to apply them.** Sections may be omitted when not relevant, but **do not reorder them and do not rename them**. Section headers must match the spec character-for-character so the file stays parseable by other DESIGN.md-aware tools (Stitch itself, awesome-design-md, skill-rest, etc.). +DESIGN.md follows the [official DESIGN.md format spec](https://raw.githubusercontent.com/google-labs-code/design.md/main/docs/spec.md): optional YAML frontmatter carrying machine-readable design tokens, followed by up to eight markdown sections in a fixed order. **Tokens are normative; prose provides context for how to apply them.** Sections may be omitted when not relevant, but those present stay in the specified order. Use the canonical headings below so the file remains portable across DESIGN.md-aware tools. ## The frontmatter: token schema @@ -43,26 +43,28 @@ components: Rules that matter: - **Token refs** use `{path.to.token}` (e.g. `{colors.primary}`, `{rounded.md}`). Components may reference primitives; primitives may not reference each other. -- **Stitch validates colors as hex sRGB only** (`#RGB` / `#RGBA` / `#RRGGBB` / `#RRGGBBAA`); OKLCH/HSL/P3 trigger a linter warning, not a hard error. YAML accepts the string either way and our own parser is format-agnostic. Choose based on project posture: (a) if the project has an "OKLCH-only" doctrine or uses Display-P3 values that don't round-trip through sRGB, put OKLCH directly in the frontmatter and accept the Stitch linter warning; (b) if the project wants strict Stitch compliance or plans to use their Tailwind/DTCG export pipeline, put hex in the frontmatter and keep OKLCH in prose as the canonical reference. Never split the source of truth without explicit reason. +- **Colors accept any valid CSS color string.** Hex is the recommended default for portability, but preserve an incumbent `rgb()`, `hsl()`, `oklch()`, wide-gamut, or mixed-color value when it is the project's normative source. Never split the source of truth without explicit reason. - **Component sub-tokens** are limited to 8 props: `backgroundColor`, `textColor`, `typography`, `rounded`, `padding`, `size`, `height`, `width`. Shadows, motion, focus rings, backdrop-filter: none of those fit. Carry them in the sidecar (Step 4b). - **Scale keys are open-ended.** Use whatever names the project already uses (`oxblood-deep`, `surface-container-low`). Don't rename to Material defaults. - **Variants are naming convention, not schema.** `button-primary` / `button-primary-hover` / `button-primary-active` as sibling keys. -## The markdown body: six sections (exact order) +## The markdown body: eight sections (canonical order) 1. `## Overview` 2. `## Colors` 3. `## Typography` -4. `## Elevation` -5. `## Components` -6. `## Do's and Don'ts` +4. `## Layout` +5. `## Elevation & Depth` +6. `## Shapes` +7. `## Components` +8. `## Do's and Don'ts` -Optional evocative subtitles are allowed in the form `## 2. Colors: The [Name] Palette` (Stitch's own outputs do this), but the literal word in each header (Overview, Colors, Typography, Elevation, Components, Do's and Don'ts) must be present. Do NOT add extra top-level sections (Layout Principles, Responsive Behavior, Motion, Agent Prompt Guide). Fold that content into the six spec sections where it naturally belongs. +Omit irrelevant sections rather than filling them with invented rules. Put responsive layout in Layout, depth in Elevation & Depth, radius and form language in Shapes, and per-component behavior in Components. Unknown sections are preserved by the format, but new visual guidance should use the canonical structure whenever it fits. ## When to run -- The user just ran `/impeccable init` and needs the visual side documented. -- The skill noticed no `DESIGN.md` exists and nudged the user to create one. +- New-work found a coherent incumbent visual system but no `DESIGN.md`. +- The first implementation of a new world is complete and its provisional decisions need to be carbonized. - An existing `DESIGN.md` is stale (the design has drifted). - Before a large redesign, to capture the current state as a reference. @@ -71,9 +73,9 @@ If a `DESIGN.md` already exists, **do not silently overwrite it**. Show the user ## Two paths - **Scan mode** (default): the project has design tokens, components, or rendered output. Extract, then confirm descriptive language. Use when there's code to analyze. -- **Seed mode**: the project is pre-implementation (fresh init, nothing built yet). Reuse init's visual-world workshop and write the chosen world as a minimal DESIGN.md marked ``. Re-run in scan mode once there's code. +- **Seed mode**: the project is pre-implementation. Ensure PRODUCT.md exists, then reuse new-work's visual-world workshop and write its directional DESIGN.md seed. Re-run in scan mode once there's code. -Decide by scanning first (Scan mode Step 1). If the scan finds no tokens, no component files, and no rendered site, offer seed mode; don't silently switch. `/impeccable document --seed` requests init's seed workshop, but it does not authorize replacing coherent code: when an incumbent system exists, offer scan mode or route an explicit identity-replacement request through init. +Decide by scanning first (Scan mode Step 1). If the scan finds no tokens, no component files, and no rendered site, offer seed mode; don't silently switch. `/impeccable document --seed` requests new-work's world workshop, but it does not authorize replacing coherent code: when an incumbent system exists, offer scan mode or route an explicit identity-replacement request through new-work. ## Scan mode (approach C: auto-extract, then confirm descriptive language) @@ -97,7 +99,8 @@ Build a structured draft from the discovered tokens. For each token class: - **Typography**: Map observed sizes and weights to the Material hierarchy (display / headline / title / body / label). Note font-family stacks and the scale ratio. - **Elevation**: Catalogue the shadow vocabulary. If the project is flat and uses tonal layering instead, that's a valid answer; state it explicitly. - **Components**: For each common component (button, card, input, chip, list item, tooltip, nav), extract shape (radius), color assignment, hover/focus treatment, internal padding. -- **Spacing + layout**: Fold into Overview or relevant Components. The spec does NOT have a Layout section. +- **Layout + spacing**: Extract grid, container, breakpoint, rhythm, and density behavior into Layout. +- **Shapes**: Extract radius, corner, border, clipping, and recurring form behavior into Shapes. ### Step 2b: Stage the frontmatter @@ -115,16 +118,16 @@ Skip anything the project doesn't have. Empty scale keys or fabricated tokens po The following require creative input that cannot be auto-extracted. Ask them in two structured rounds of no more than three questions each (or the harness's lower limit), waiting between rounds: - **Creative North Star**: a single named metaphor for the whole system ("The Editorial Sanctuary", "The Golden State Curator", "The Lab Notebook"). Offer 2-3 options that honor PRODUCT.md's brand personality. -- **Overview voice**: mood adjectives, aesthetic philosophy in 2-3 sentences, anti-references (what the system should not feel like). +- **Overview voice**: mood adjectives, aesthetic philosophy in 2-3 sentences, and any confirmed visual anti-reference. - **Color character** (for auto-extracted colors): descriptive names ("Deep Muted Teal-Navy", not "blue-800"). Suggest 2-3 options per key color based on hue/saturation. - **Elevation philosophy**: flat/layered/lifted. If shadows exist, is their role ambient or structural? - **Component philosophy**: the feel of buttons, cards, inputs in one phrase ("tactile and confident" vs. "refined and restrained"). -Quote a line from PRODUCT.md when possible so the user sees their own strategic language carry forward. +Carry a line from PRODUCT.md only when it is a durable brand commitment that actually constrains the visual system. Page strategy and surface concepts do not belong here. ### Step 4: Write DESIGN.md -The file opens with the YAML frontmatter staged in Step 2b (schema documented at the top of this reference), then the markdown body using the structure below. Headers must match character-for-character. Optional evocative subtitles (e.g. `## 2. Colors: The Coastal Palette`) are allowed. +The file opens with the YAML frontmatter staged in Step 2b (schema documented at the top of this reference), then the markdown body using the canonical structure below. ```markdown --- @@ -136,13 +139,13 @@ colors: # Design System: [Project Title] -## 1. Overview +## Overview **Creative North Star: "[Named metaphor in quotes]"** -[2-3 paragraph holistic description: personality, density, aesthetic philosophy. Start from the North Star and work outward. State what this system explicitly rejects (pulled from PRODUCT.md's anti-references). End with a short **Key Characteristics:** bullet list.] +[2-3 paragraph holistic description: personality, density, and aesthetic philosophy. Start from the North Star and work outward. State only confirmed visual rejections. End with a short **Key Characteristics:** bullet list.] -## 2. Colors +## Colors [Describe the palette character in one sentence.] @@ -162,7 +165,7 @@ colors: ### Named Rules (optional, powerful) **The [Rule Name] Rule.** [Short, forceful prohibition or doctrine, e.g. "The One Voice Rule. The primary accent is used on ≤10% of any given screen. Its rarity is the point."] -## 3. Typography +## Typography **Display Font:** [Family] (with [fallback]) **Body Font:** [Family] (with [fallback]) @@ -180,7 +183,11 @@ colors: ### Named Rules (optional) **The [Rule Name] Rule.** [Short doctrine about type use.] -## 4. Elevation +## Layout + +[Describe the grid or spatial model, container behavior, density, responsive changes, and the spacing rhythm. Include exact values only when observed.] + +## Elevation & Depth [One paragraph: does this system use shadows, tonal layering, or a hybrid? If "no shadows", say so explicitly and describe how depth is conveyed instead.] @@ -191,7 +198,11 @@ colors: ### Named Rules (optional) **The [Rule Name] Rule.** [e.g. "The Flat-By-Default Rule. Surfaces are flat at rest. Shadows appear only as a response to state (hover, elevation, focus)."] -## 5. Components +## Shapes + +[Describe the form language: corner/radius strategy, borders, clipping, and any recurring silhouette or geometry.] + +## Components For each component, lead with a short character line, then specify shape, color assignment, states, and any distinctive behavior. @@ -223,9 +234,9 @@ For each component, lead with a short character line, then specify shape, color ### [Signature Component] (optional; if the project has a distinctive custom component worth documenting) [Description.] -## 6. Do's and Don'ts +## Do's and Don'ts -Concrete, forceful guardrails. Lead each with "Do" or "Don't". Be specific: include exact colors, pixel values, and named anti-patterns the user mentioned in PRODUCT.md. **Every anti-reference in PRODUCT.md should show up here as a "Don't" with the same language**, so the visual spec carries the strategic line through. Quote PRODUCT.md directly where possible: if PRODUCT.md says *"avoid dark mode with purple gradients, neon accents, glassmorphism"*, the Don'ts here should repeat that by name. +Concrete visual guardrails grounded in the incumbent implementation or the user's chosen world. Lead each with "Do" or "Don't" and include exact values only when established. Do not turn a task-specific concept or surface strategy into a system-wide prohibition. ### Do: - **Do** [specific prescription with exact values / named rule]. @@ -307,7 +318,7 @@ The `html` and `css` fields must be **self-contained, drop-in snippets** that re Aim for a tight set of **5-10 components** that best represent the visual system: - **Canonical primitives (always include if the project has them):** button (each variant as a separate component entry), input/text field, navigation, chip/tag, card. -- **Signature components (include if distinctive):** hero CTA, featured card, filter pill, a distinctive table or list-row treatment, any custom pattern the user mentioned as important in PRODUCT.md. +- **Signature components (include if distinctive):** the recurring custom patterns that actually define the implemented system. - **Skip the rest.** Utility components, form building blocks, wrapper layouts: not worth documenting unless visually distinctive. If the project has **no component library yet** (bare landing page, new project), synthesize canonical primitives from the tokens using best-practice defaults consistent with the DESIGN.md's rules. Every `.impeccable/design.json` has *something* to render, even on day zero. @@ -340,17 +351,17 @@ Your own write is the freshest source; subsequent commands in this session don't For projects with no visual system to extract yet. Produces a user-chosen visual-world scaffold, not a fabricated token spec. -### Step 1: Route through init's workshop +### Step 1: Route through new-work's workshop -PRODUCT.md is the prerequisite. If it is missing, load [init.md](init.md) and complete its strategic interview first. Do not create a visual identity without durable product context. +PRODUCT.md is the prerequisite. If it is missing, load [init.md](init.md) and complete its product interview first. Do not create a visual identity without durable product context. -If PRODUCT.md exists, load **Step 5: Establish the visual world** from [init.md](init.md) and run that same workshop. Do not start a parallel questionnaire about colors, fonts, or references: init's proposals must already be rooted in the audience world, cultural context, pinned direction, personality, and anti-references. A structured simulated user counts as the user and must get the same choice. +If PRODUCT.md exists, load [new-work.md](new-work.md), resolve visual authority, and run **Establish or replace the visual world** only when no authority exists or replacement was explicitly requested. Stop after its directional DESIGN.md seed; `document --seed` does not need a task concept. A structured simulated user counts as the user and must get the same choice. -If an init invocation already completed the workshop in this session, use its chosen direction directly. Do not ask again. +If new-work already completed the workshop in this session, use its chosen direction directly. Do not ask again. ### Step 2: Write seed DESIGN.md -Use the six-section spec from Scan mode. Populate the selected workshop direction and leave unresolved implementation facts as honest placeholders. The seed commits a world and its invariants; it does not pretend implementation tokens already exist. +Use the canonical section order from Scan mode. Populate the selected workshop direction and leave unresolved implementation facts as honest placeholders. The seed commits a world and its invariants; it does not pretend implementation tokens already exist. Lead the file with: @@ -360,12 +371,14 @@ Lead the file with: Per-section guidance in seed mode: -- **Overview**: the chosen design thesis, layout behavior, first-view or first-task idea, material character, imagery stance, motion, and signature. Reference the user's audience world, pinned direction, and anti-references where they actually constrain the design. -- **Colors**: the selected palette strategy and roles. Include values only when the user, an existing asset, or init's palette exploration established them; otherwise mark them `[to be resolved during implementation]`. +- **Overview**: the chosen design thesis, layout behavior, material character, imagery stance, motion grammar, and reusable signature. Do not promote the current page's first-view idea into the global world. +- **Colors**: the selected palette strategy and roles. Include values only when the user, an existing asset, or new-work's exploration established them; otherwise mark them `[to be resolved during implementation]`. - **Typography**: the selected type character and role relationship. Include font names only when established; otherwise mark the pairing `[to be resolved during implementation]`. -- **Elevation**: the selected material and depth behavior, stated as an invariant rather than inferred from a generic motion preset. +- **Layout**: the selected spatial grammar and responsive behavior, without pretending exact measurements are settled. +- **Elevation & Depth**: the selected material and depth behavior, stated as an invariant rather than inferred from a generic preset. +- **Shapes**: the selected form and corner language. - **Components**: omit entirely; no components exist yet. -- **Do's and Don'ts**: carry PRODUCT.md's anti-references directly plus the anti-reference named in Q5. +- **Do's and Don'ts**: record the durable guardrails confirmed during the world choice, not task-local refusals. Seed mode writes a minimal frontmatter with `name` and `description` only; no colors, typography, rounded, spacing, or components yet. Real tokens land on the next Scan-mode run. Skip the `.impeccable/design.json` sidecar in seed mode for the same reason: nothing to render. @@ -379,15 +392,15 @@ Your own write is the freshest source; no reload needed. ## Style guidelines - **Frontmatter first, prose second.** Tokens go in the YAML frontmatter; prose contextualizes them. Don't redefine a token value in two places; the frontmatter is normative. -- **Cite PRODUCT.md anti-references by name** in the Do's and Don'ts section. If PRODUCT.md lists "SaaS landing-page clichés" or "generic AI tool marketing" as anti-references, the DESIGN.md Don'ts should repeat those phrases verbatim so the visual spec enforces the strategic line. -- **Match the spec, don't invent new sections.** The six section names are fixed. If you have Layout/Motion/Responsive content to document, fold it into Overview (philosophy-level rules) or Components (per-component behavior). +- **Carry only durable product constraints.** A binding logo, identity asset, accessibility need, or brand commitment from PRODUCT.md may constrain DESIGN.md. Surface strategy stays in its surface brief. +- **Match the spec.** Use its eight canonical sections in order and omit any that are irrelevant. Put motion guidance with the world or component it affects rather than creating a token group the schema does not support. - **Descriptive > technical**: "Gently curved edges (8px radius)" > "rounded-lg". Include the technical value in parens, lead with the description. - **Functional > decorative**: for each token, explain WHERE and WHY it's used, not just WHAT it is. - **Exact values in parens**: hex codes, px/rem values, font weights; always the number in parens alongside the description. - **Use Named Rules**: `**The [Name] Rule.** [short doctrine]`. These are memorable, citable, and much stickier for AI consumers than bullet lists. Stitch's own outputs use them heavily ("The No-Line Rule", "The Ghost Border Fallback"). Aim for 1-3 per section. -- **Be forceful**. The voice of a design director. "Prohibited", "forbidden", "never", "always", not "consider", "might", "prefer". Match PRODUCT.md's tone. +- **Be decisive where evidence is decisive.** Use hard language for actual invariants and softer language for provisional guidance. - **Concrete anti-pattern tests**. Stitch writes things like *"If it looks like a 2014 app, the shadow is too dark and the blur is too small."* A one-sentence audit test beats a paragraph of principle. -- **Reference PRODUCT.md**. The anti-references section of PRODUCT.md should directly inform the Do's and Don'ts section here. Quote or paraphrase. +- **Reference PRODUCT.md selectively.** Product truth explains why the world fits; it does not supply page composition or a visual don't-list by default. - **Group colors by role**, not by hex-order or hue-order. Primary / Secondary / Tertiary / Neutral is the spec ordering. ## Pitfalls @@ -397,7 +410,7 @@ Your own write is the freshest source; no reload needed. - Don't invent components that don't exist. If the project only has buttons and cards, only document those. - Don't overwrite an existing DESIGN.md without asking. - Don't duplicate content from PRODUCT.md. DESIGN.md is strictly visual. -- Don't add a "Layout Principles" or "Motion" or "Responsive Behavior" top-level section. The spec has six, not nine. Fold that content where it belongs. +- Don't replace canonical sections with near-synonyms. Put layout and responsive behavior in `Layout`; put motion with the affected world or component. - Don't rename sections even slightly. "Colors" not "Color Palette & Roles". "Typography" not "Typography Rules". Tooling parsing depends on exact headers. - Don't duplicate token values between frontmatter and prose. If a color is in `colors.primary` as hex, the prose can name it and describe its role but should not reassert a different hex. The frontmatter is normative. - Don't invent frontmatter token groups outside Stitch's schema (no `motion:`, `breakpoints:`, `shadows:` at the top level). Stitch's Zod schema only accepts `colors`, `typography`, `rounded`, `spacing`, `components`. Anything else belongs in the sidecar's `extensions`. diff --git a/skill/reference/init.md b/skill/reference/init.md index ce989aa22..853648f97 100644 --- a/skill/reference/init.md +++ b/skill/reference/init.md @@ -1,127 +1,59 @@ -# Init Flow +# Init flow -The setup command for a project. One codebase crawl feeds everything it writes: - -- **PRODUCT.md** (strategic): root project file for target users, product purpose, positioning, audience world, cultural context, non-negotiable direction, personality, anti-references, and strategic design principles. Answers "who/what/why" and preserves the human knowledge future design work must not invent cold. Visitor mode is task-scoped and does not live here. -- **DESIGN.md** (visual): root project file for the user-approved visual world: theme, color roles, typography direction, material and component language, layout behavior, motion, and signature. Follows the [DESIGN.md format spec](https://raw.githubusercontent.com/google-labs-code/design.md/main/docs/spec.md). Answers "how it looks". -- **`.impeccable/live/config.json`** (live mode): pre-configured so `{{command_prefix}}impeccable live` boots straight into variant mode with no first-time detour. - -It closes by pointing the user at the best command to run next. Every other impeccable command reads PRODUCT.md and DESIGN.md before doing any work. Identity invention happens here, with the user, not later inside a page build. +`init` captures durable product truth in PRODUCT.md. It does not invent a visual world and does not write DESIGN.md; [new-work.md](new-work.md) creates or expands one, and [document.md](document.md) records an incumbent one. Existing runnable web projects may also receive `.impeccable/live/config.json`. ## Step 1: Load current state -Check what already exists. PRODUCT.md and DESIGN.md live at the project root, or under `.agents/context/` or `docs/` (case-insensitive). Read whichever are present with your native file tool and remember each resolved path. Refresh the resolved existing file; do not create a second root authority beside it. In a child app that inherits root context, confirm whether the user intends to update the shared root or create app-specific context before writing. Also note whether `.impeccable/live/config.json` already exists (Step 6 leaves it untouched if so). +Use the PRODUCT.md path resolved by context.mjs. Update it instead of creating a competing authority. In a child app inheriting root context, confirm shared versus app-specific scope before writing. -Decision tree: -- **Neither file exists (empty project or no context yet)**: do Steps 2-5. Write PRODUCT.md, then establish the visual world in DESIGN.md before any build resumes. -- **PRODUCT.md exists, DESIGN.md missing**: do Step 5. For refinement or extension, document a coherent incumbent implementation; otherwise run the visual-world workshop and write a seed DESIGN.md. -- **PRODUCT.md exists but has no `## Platform` section (legacy)**: add it the same way, but only when the project is native (`ios` / `android` / `adaptive`) or the user wants it explicit; a missing field already means `web`. -- **PRODUCT.md is missing Positioning, Audience World, Cultural Context where relevant, or Pinned Direction (legacy)**: interview only for the missing durable fields and merge them into the resolved file before substantial new work. -- **Both exist, ordinary init**: {{ask_instruction}} Ask which file to refresh. Skip the one the user doesn't want changed. -- **Redesign or rebrand**: keep confirmed product facts unless the user changes them, but replace DESIGN.md through a new visual-world choice. The old code and DESIGN.md are evidence and anti-reference, not constraints on the replacement. “Redesign this page/site” is enough authorization; do not require the user to say “discard the identity” twice. -- **Just DESIGN.md exists (unusual)**: do Steps 2-4 to produce PRODUCT.md. +- **No PRODUCT.md:** explore, interview, and write it. +- **PRODUCT.md exists:** ask what product knowledge is stale or missing; do not reopen confirmed fields without a reason. +- **Legacy PRODUCT.md:** add only durable missing facts; absent `## Platform` means `web` unless evidence says otherwise. +- **Only DESIGN.md exists:** leave it untouched and create PRODUCT.md. +- **Redesign/rebrand request:** preserve confirmed product truth unless the user changes it. Visual replacement happens later in new-work, not here. -Never silently overwrite an existing file. Always confirm first. +Never silently overwrite an existing file or offer DESIGN.md during init. If another request invoked init, finish PRODUCT.md and resume it. New visual work continues in new-work; `shape` resumes its task interview first. -If init was invoked as a setup blocker by another command, such as `{{command_prefix}}impeccable craft landing page`, pause that command here. Complete init, then resume the original command. Your own writes are the freshest source; do not rerun `context.mjs`. For craft, resume into the task-specific discovery and [new-work.md](new-work.md); init commits the world, while the surface flow decides the requested composition inside it. +## Step 2: Explore the project -## Step 2: Explore the codebase +Before asking, scan enough to avoid making the user repeat known facts: product docs and copy; package/config and app boundaries; features, workflows, routes, and roles; names, logos, legal/proof assets, and brand commitments; platform/accessibility signals; and the dev command/entry when live mode applies. -Before asking questions, thoroughly scan the project to discover what you can. This single crawl feeds PRODUCT.md, DESIGN.md, **and** the live-mode framework detection in Step 6, so be thorough once rather than re-scanning later: +Treat repository evidence as a hypothesis, not user approval. Note visual maturity without documenting, extending, or replacing the world. -- **README and docs**: Project purpose, target audience, any stated goals -- **Package.json / config files**: Tech stack, dependencies, existing design libraries, **and the framework** (Vite/SPA, Next.js, Nuxt, SvelteKit, Astro, multi-page static) plus the HTML entry the browser actually loads -- **Existing components**: Current design patterns, spacing, typography in use -- **Brand assets**: Logos, favicons, color values already defined -- **Design tokens / CSS variables**: Existing color palettes, font stacks, spacing scales -- **Any style guides or brand documentation** +Form a platform hypothesis: `web`, `ios`, `android`, or `adaptive` (one product that genuinely adapts its design language per OS). Mobile web remains `web`; a native wrapper around a website does not make its design language native. -Form a **platform hypothesis**: +## Step 3: Interview for product truth -- Native signals: React Native / Expo (`react-native`, `expo`), Flutter (`pubspec.yaml`, `flutter`), SwiftUI / UIKit (`.swift`, `.xcodeproj`, an `ios/` app target), Jetpack Compose / Android (`build.gradle`, an `android/` app module, `AndroidManifest.xml`). An `ios/` and/or `android/` directory that is a real app target, not just a Capacitor/Cordova wrapper around a website. -- Web signals (the default): a web framework (Vite, Next, Nuxt, SvelteKit, Astro), an HTML entry, a CSS/Tailwind setup, no native app target. +{{ask_instruction}} Ask only about material gaps the repository and original request do not answer with strong evidence. -Values: `web` / `ios` / `android` / `adaptive` (one codebase, ships both, adapts per OS). Mobile web is still `web`. This is a hypothesis; Step 3 confirms it when the repo does not make it certain. +Use the structured question tool when available; otherwise ask and wait. Keep rounds to at most three focused questions and require one real answer or approval round before writing a new PRODUCT.md. Confirm inferences. -Note what you've learned and what remains unclear. Also note any rough edges worth a follow-up command (thin hierarchy, flat or gray palette, missing error/empty states, dull copy); Step 7 turns these into concrete recommendations without re-analyzing. +Start with the unknowns that most change future product decisions: -## Step 3: Ask strategic questions (for PRODUCT.md) +1. Who is the primary user, in what situation, and what job are they doing? +2. What does the product make possible, and what is its meaningfully different mechanism or position? +3. What durable constraints, assets, evidence, or product facts must future work preserve? -{{ask_instruction}} Ask about anything the codebase doesn't answer with strong, explicit evidence. +Confirm ambiguous platform separately. Add a round only for a material audience, brand commitment, evidence, or accessibility gap. Record undecided facts instead of inventing them. -### Interview mode, not confirmation mode +Do not ask for an aesthetic direction, emotional feel, visual references, colors, typography, or style during init. If the user volunteers a binding visual constraint, record it without expanding it. -If the repo is empty or the user's brief is sparse, run a short interview before proposing PRODUCT.md. Do **not** turn a one-sentence request into a complete inferred PRODUCT.md and ask for blanket confirmation. +### What belongs here -- Use the harness's structured question tool when one exists. Otherwise, ask directly in chat and stop: one question at a time, with lettered options where the crawl suggests likely answers, waiting for each answer before the next. -- Keep skill vocabulary (belief ladder, anti-references, visual world) out of question text; ask for the thing in words the user would use. -- Ask in focused rounds and wait for answers between them. Keep **one topic per question**; add rounds rather than fold several topics into one either-or choice. Options obey the same rule: an option answers only the question asked; never write a compound option that bundles a feeling with a business outcome or names an additional audience. -- Use inferred answers as hypotheses or options, not as finished facts. -- Complete at least one real user-answer or approval round before drafting PRODUCT.md. Repo evidence may prefill the proposal, but it does not silently approve strategy or identity. -- Round 1 asks at most three high-leverage questions: who and what job, what makes the product meaningfully different, and what working or cultural world should feel native to it. Confirm platform separately only when repository evidence is ambiguous. -- Add a second round only for a pinned direction, decisive anti-reference, missing proof/content, or accessibility requirement that would materially change the proposals. Do not collect personality adjectives and reference lists by default. +- users, jobs, workflows, purpose, success, positioning, and operating context; +- capabilities, constraints, terminology, evidence, platform, and accessibility; +- confirmed voice, assets, and brand commitments. -### Minimum viable interview +### What does not belong here -Ask enough to capture users, purpose, positioning, the audience's working world, and any pinned direction or anti-reference the user actually has. Confirm **platform** (`web` / `ios` / `android` / `adaptive`) when repository evidence is ambiguous. Relevant cultural context, conversion proof, named references, personality, and additional accessibility needs are optional fields, not mandatory interview ceremony. Complete at least one real answer round, then propose only the remaining inferred facts for confirmation before writing. Never synthesize PRODUCT.md from the original task prompt alone. - -### Platform - -Every project targets **web** (includes responsive mobile web), **ios**, **android**, or **adaptive** (one codebase, ships both, adapts per OS: Flutter, React Native, KMP). Platform picks the native rulebook: HIG for `ios`, Material 3 for `android`, both for `adaptive`, none for `web`. - -If Step 2 produced a clear hypothesis, lead with it: *"From the codebase, this looks like a [web / ios / android / adaptive] project. Does that match?"* For cross-platform apps, decide by the **design language the app renders**, not the toolchain: one look on both platforms (Flutter's Material-everywhere default) takes that platform's value; genuine per-OS adaptation (Cupertino on iOS, Material on Android) is `adaptive`. When in doubt, `web`. - -A monorepo shipping both a website and a native app gets a PRODUCT.md per app, each with its own `## Platform`; the root PRODUCT.md carries the primary surface's platform. - -### Users & Purpose -- Who uses this? What's their context when using it? -- What job are they trying to get done? -- What is this for? A purpose stated in README or docs is a hypothesis, not strong evidence; confirm it, don't transcribe it. -- What does success look like? -- If more than one kind of user is plausible, confirm a primary and secondary audience; don't manufacture a split that isn't there. An audience implied by another answer (a success metric, a CTA) is still unconfirmed; ask before writing it as secondary. -- If the surface speaks to a different audience than the people who use the product, ask the user to name both. -- What workflow or decision are they in when they use it? - -### Positioning -- In one line, what does this do that nothing else does? The single strategic claim every screen reinforces. - -### Brand & Personality -- How would you describe the brand personality in 3 words? -- Reference sites or apps that capture the right feel? What specifically about them? - - Push for specific named references with the *specific* thing about them that fits this brand, not generic "modern" adjectives or category-bucket lanes. -- What should this explicitly NOT look like? Any anti-references? - -### Audience world & direction - -The visual world needs roots deeper than a style adjective. Learn the reality the audience already inhabits before proposing a direction: - -- What tools, places, objects, documents, materials, or rituals are familiar to them in this context? -- For Persuade and Experience work, what cultural home feels truthful: a place, era, craft, medium, or scene? Ask only for associations that illuminate the product; never force a decorative metaphor. -- Is any visual direction non-negotiable? Preserve the user's exact constraint, whether it is a named aesthetic, an existing identity, a reference, or a deliberate refusal of one. - -These are strategic inputs, not a request for the user to design the page. Do not ask them to choose colors, fonts, radii, or a component recipe here. Step 5 turns the confirmed inputs into genuinely different visual-world proposals and asks the user to choose. - -### Conversion & proof (Persuade surfaces only) - -Ask these only when the current request is a Persuade surface (marketing, landing, campaigns) and the answers are not already in the brief. Experience and Read surfaces get no CTA, belief-ladder, or proof questions; visitor mode is decided per task and is not stored in PRODUCT.md. - -- What's the primary CTA? -- What's the secondary fallback, for visitors not ready for the primary? -- The one line a visitor should remember after 10 seconds. -- What must the visitor believe, in order, before taking the primary CTA? (The template's belief ladder.) -- What proof is on hand? Ask the user to hand over any testimonials, case studies, press, or client/partner logos they already have. If you can receive files directly, collect them; otherwise create `.impeccable/assets/proof/` and ask the user to add files there. Reference supplied files by path; record text proof inline. - -### Accessibility & Inclusion -- Specific accessibility requirements? (WCAG level, known user needs) -- Considerations for reduced motion, color blindness, or other accommodations? - -Skip questions where the answer is already clear. **Do NOT ask about colors, fonts, radii, or visual styling here.** Those belong in DESIGN.md, not PRODUCT.md. +- visual worlds, palettes, typography, components, or page concepts; +- visitor mode, narrative, CTA/proof sequence, or other surface strategy; +- invented testimonials, customers, benchmarks, pricing, licensing, or deployment claims; +- a requirement to decide every optional field. ## Step 4: Write PRODUCT.md -Write PRODUCT.md only after the user has confirmed the strategic answers from Step 3. If an inferred answer is uncertain or unconfirmed, ask before writing. Confirmed means what the user actually said yes to; do not pad a confirmed answer with extras they never picked (additional anti-references, audiences, roadmap claims, a WCAG level), whether drawn from the crawl, another answer, or your own option text. If an extra belongs in the doc, ask about it first. - -Synthesize into a strategic document: +Write only confirmed facts and explicitly marked open decisions. Omit irrelevant sections rather than filling them with generic prose. ```markdown # Product @@ -131,103 +63,48 @@ Synthesize into a strategic document: web ## Users -[Who they are, their context, the job to be done. Primary audience; a secondary audience or a surface-vs-user split only when they apply.] +[Primary users, their situation, and job. Add other audiences only when confirmed.] ## Product Purpose -[What this product does, why it exists, what success looks like] +[What the product does, why it exists, and what success means.] ## Positioning -[The single strategic claim every screen reinforces. Not a visual rule, not an anti-reference.] +[The product mechanism or claim a neighboring product could not truthfully copy.] -## Audience World -[The tools, places, objects, documents, materials, and rituals familiar to the audience in this context. Include only what the user confirmed.] +## Operating Context +[Workflows, environments, tools, documents, materials, and rituals that are factual parts of using or evaluating the product.] -## Cultural Context -[The truthful place, era, craft, medium, or scene that can ground Persuade or Experience work. Omit the section when it is genuinely irrelevant to an Operate or Read product.] +## Capabilities and Constraints +[Confirmed functionality, technical constraints, terminology, and explicitly undecided product facts.] -## Pinned Direction -[Any visual direction, existing identity, named aesthetic, or reference the user made non-negotiable. Write `None.` when the user explicitly wants the workshop to remain open.] +## Brand Commitments +[Existing name, voice, assets, personality, identity constraints, and references the user explicitly made binding. Omit when none exist.] -## Conversion & proof -[Persuade surfaces only (marketing, landing, campaigns). Omit this section entirely, heading included, for Experience, Operate, or Read surfaces.] -- Primary and secondary CTA: [...] -- The line a visitor remembers after 10 seconds: [...] -- Belief ladder: [...] -- Proof on hand: [testimonials, case studies, press, or logos, referenced by path] +## Evidence on Hand +[Real content, data, demonstrations, testimonials, case studies, press, or assets, with paths where applicable. State absences that future work must not fabricate.] -## Brand Personality -[Voice, tone, 3-word personality, emotional goals] - -## Anti-references -[What this should NOT look like. Specific bad-example sites or patterns to avoid.] - -## Design Principles -[3-5 strategic principles derived from the conversation. Principles like "practice what you preach", "show, don't tell", "expert confidence". NOT visual rules like "use OKLCH" or "magenta accent".] +## Product Principles +[Three to five durable strategic principles derived from confirmed answers; no visual recipes.] ## Accessibility & Inclusion -[WCAG level, known user needs, considerations] +[Known user needs or required standard. Omit when no product-specific requirement was established.] ``` -Platform is `web`, `ios`, `android`, or `adaptive` as a bare value; omit the section only on legacy files you're leaving untouched, otherwise write `web` explicitly. +Platform is the bare value `web`, `ios`, `android`, or `adaptive`. Preserve useful legacy headings. New files go at `PROJECT_ROOT/PRODUCT.md`; otherwise update the resolved file. Write it before any visual-world or surface-concept work. -Write fields as prose, and use bold sparingly: only where a word carries a decision, never as a label lead-in on every line. +## Step 5: Configure live mode when useful -For a new context file, write to `PROJECT_ROOT/PRODUCT.md`. When PRODUCT.md was resolved from another supported location, update that exact file instead. If `.impeccable.md` existed, the loader already renamed it; merge into that content rather than starting from scratch. +Skip native or non-runnable projects and leave existing config untouched. Otherwise follow [live.md](live.md)'s first-time setup. Any CSP source edit still requires its stated consent. -## Step 5: Establish the visual world (for DESIGN.md) +## Step 6: Wrap up or resume -Identity is not an unattended prelude to the page build. Establish it here, while the user can choose it, and write DESIGN.md before any new-work flow resumes. +Summarize captured and deliberately undecided facts. Do not offer DESIGN.md merely because it is missing. -### Refinement or extension: document the incumbent world +Recommend the next action from the actual project state: -If the request preserves or extends the current identity and the crawl found an intentional visual system in real code, do not invent a replacement merely because DESIGN.md is missing. Load [document.md](document.md), use scan mode, and show the user the design language you found before writing it down. Ask before replacing an existing DESIGN.md. +- Empty or early project: ask naturally for the surface to be built, or use `/impeccable shape ` when the user wants a confirmed brief without implementation. New-work will establish a visual world only when the requested work needs one. +- Existing coherent interface without DESIGN.md: `/impeccable document` if the user wants the incumbent system recorded independently of a new build. +- Existing surface needing work: name the most relevant scoped command. +- Web project ready for visual iteration: `/impeccable live` when configured. -### Greenfield or redesign: run the workshop - -Run the workshop when the project is visually uncommitted or the user asked for a redesign/rebrand. On redesign, keep the old system visible only long enough to identify what must not survive and which product facts, content, functions, or assets remain useful. Do not offer “the old look, polished” as a candidate world. - -1. **Synthesize two or three credible worlds.** Derive them from the confirmed product mechanism, audience world, cultural context, pinned direction, personality, and anti-references in PRODUCT.md. Each proposal must have a distinct identity thesis, layout grammar, type and material character, palette strategy, component character, imagery stance, motion grammar, and one reusable signature. They must be different ways to make *this product* true, not generic category styles with new names. Do not design a particular page here; later craft work composes new surfaces inside the chosen grammar. -2. **Use color entropy as a challenger, never an answer.** If color is genuinely unpinned, run `node {{scripts_path}}/palette.mjs` to challenge the reflex palette. Translate useful tension into a proposal; never let the script override the confirmed brief, pinned direction, existing assets, accessibility, or the user's choice. Structural concept entropy belongs to the task-scoped [new-work.md](new-work.md) flow, not to identity selection. -3. **Ask the user to choose.** Present the directions concisely in the structured question tool when available, one option per world plus a way to revise the premises. Otherwise ask in chat and stop. The user may choose, combine compatible ideas, reject all of them, or tighten the direction. A harness-provided simulated user is a real answer mechanism and must exercise this same turn. Do not silently select a world while a question mechanism exists. -4. **Resolve the chosen world.** Follow up only on choices that materially affect the system. Do not turn this into a token questionnaire. The goal is agreement on a coherent world and its invariants, not approval of every CSS value. -5. **Write a seed DESIGN.md.** Follow the [DESIGN.md format spec](https://raw.githubusercontent.com/google-labs-code/design.md/main/docs/spec.md). Record the chosen thesis, layout behavior, typography direction, color roles, surfaces and materials, components, imagery, motion, and signature. Include concrete values only when the code, assets, palette exploration, or user established them; mark unresolved implementation details as such instead of fabricating a finished token system. Add `` near the top. Write a new file at the project root; refresh an existing DESIGN.md at its resolved path. - -If there is truly no human or structured question mechanism, derive the proposals anyway, choose the one best supported by the explicit brief and pinned constraints, and add `` to both context files. Surface the assumptions in the final response and force confirmation on the next attended init. This is a degraded fallback, not permission for a capable harness to skip the interview or call the world user-approved. - -## Step 6: Configure live mode (when code exists) - -**Skip this step when the platform is native** (`ios` / `android` / `adaptive`): live mode drives a browser overlay. A hybrid wrapper or Expo web target serving HTML doesn't change that. - -If the project has code with HTML entries and a dev server (the same "code exists" condition that puts `/impeccable document` in scan mode), pre-configure live mode now. You already identified the framework and the served HTML entry in Step 2, so this is nearly free, and it spares the user the first-time setup detour when they later run `/impeccable live`. - -**Skip this step for empty / pre-implementation projects** (nothing to inject into yet). Tell the user live mode will configure itself the first time they run it once there's code. - -**If `.impeccable/live/config.json` already exists, leave it untouched** and note that live mode is already configured. - -Otherwise: - -1. Write `.impeccable/live/config.json`. Choose `files` (the HTML entries the browser actually loads), `insertBefore`, and `commentSyntax` from the framework table in [live.md](live.md)'s **First-time setup** section, using the framework you found in Step 2. That table is canonical; do not restate it here. For multi-page static sites, prefer a glob (`["public/**/*.html"]`) over a literal list. -2. Run `node {{scripts_path}}/detect-csp.mjs`. If it reports a patchable shape (`append-arrays` / `append-string`), use the **consent prompt template** from live.md before editing any source file. On decline, skip the patch. For `middleware` / `meta-tag` shapes, surface the detected files and ask the user to add `http://localhost:8400` to `script-src` and `connect-src` manually. For `null`, there's nothing to do. -3. Set `cspChecked: true` in the config once CSP is handled (patched, declined, manual, or not needed). The schema and per-shape patch details live in live.md's First-time setup; follow it rather than duplicating. - -Writing the config file is harmless and needs no consent; only the CSP **source-file patch** requires a yes. - -## Step 7: Recommend starting points, then wrap up - -Summarize tersely: -- Platform captured (web / ios / android / adaptive) when relevant -- What was written (PRODUCT.md, the chosen visual world in DESIGN.md, live config, or a subset) -- The 3-5 strategic principles from PRODUCT.md that will guide future work -- If DESIGN.md or live config is pending, one line on how to set it up later - -Then recommend the **best commands to run next**, drawn from what your Step 2 crawl already surfaced. Do not run a fresh analysis here; surface observations you already have. Tailor to the current surface and platform, offer the 2-4 most relevant (not a menu dump), and give the exact command to type. Group by intent: - -- **Build something new**: `/impeccable craft ` (shape, then build end-to-end) or `/impeccable shape ` (plan first). Lead with this for empty or early-stage projects. -- **Improve what's there**: name the specific surface. `/impeccable critique ` for a scored UX review; `/impeccable audit ` for a11y / perf / responsive checks; `/impeccable polish ` for a pre-ship pass. When the crawl flagged a specific weakness, point the matching command at it: thin hierarchy or spacing → `layout`, flat or gray palette → `colorize`, missing error / empty states → `harden` or `onboard`, dull or unclear copy → `clarify`. -- **Iterate visually** (web only): `/impeccable live` (configured in Step 6) to pick elements in the browser and generate variants in place. **Skip this group for native platforms.** - -The full command menu is one bare `/impeccable` away; keep this list short and pointed. - -If init was invoked as a blocker by another impeccable command (e.g. the user ran `/impeccable polish` with no PRODUCT.md), resume that original task now. Your own writes are the freshest source; no reload needed. - -Optionally {{ask_instruction}} Ask whether they'd like a brief summary of PRODUCT.md appended to {{config_file}} for easier agent reference. If yes, append a short **Design Context** pointer section there. +If init was invoked by another request, resume without rerunning context.mjs; new-work owns later visual decisions. diff --git a/skill/reference/ios.md b/skill/reference/ios.md index 53a3531fc..9d2ba20a0 100644 --- a/skill/reference/ios.md +++ b/skill/reference/ios.md @@ -2,7 +2,7 @@ For native iOS / iPadOS apps: SwiftUI, UIKit, React Native, Expo, Flutter shipping to Apple hardware. -On native, register narrows. HIG conformance governs structure, navigation, and interaction whatever the register; brand expresses through the expressive layer the platform provides (tint, type, motion, content). Calm, Duolingo, and Spotify carry strong identity entirely inside HIG conventions. +On native, the visitor mode narrows what expression may override. HIG conformance governs structure, navigation, and interaction in every mode; brand expresses through the layer the platform leaves open (tint, type, motion, content). ## The iOS slop test diff --git a/skill/reference/layout.md b/skill/reference/layout.md index d171dbe3b..da8725a14 100644 --- a/skill/reference/layout.md +++ b/skill/reference/layout.md @@ -2,7 +2,7 @@ Space is the most underused design tool. Find the layout's actual problem (monot --- -## Register +## Visitor mode Persuade + Experience: asymmetric compositions, fluid spacing with `clamp()`, intentional grid-breaking for emphasis. Rhythm through contrast: tight groupings paired with generous separations. @@ -14,7 +14,7 @@ Native (`ios` / `android` / `adaptive`): structure follows the Layout section of ## Two isolated assessments (required) -Spawn two parallel sub-agents whenever a sub-agent/Task tool is exposed: one for the layout assessment, one for the mechanical pre-scan. If the harness needs explicit user permission for sub-agents, stop and ask before proceeding. Isolation is the point: detector output anchors visual judgment toward what the scan can see, so neither sub-agent gets the other's output. Each assessment runs in its own sub-agent; running either one in this context when a sub-agent tool exists is not permitted, even when it is faster; the fallback below is only for sessions with no sub-agent tool. Give each a self-contained prompt (target files, register, documented spacing scale when present, and its instructions below); do not assume it can read this file. +Spawn two parallel sub-agents whenever a sub-agent/Task tool is exposed: one for the layout assessment, one for the mechanical pre-scan. If the harness needs explicit user permission for sub-agents, stop and ask before proceeding. Isolation is the point: detector output anchors visual judgment toward what the scan can see, so neither sub-agent gets the other's output. Each assessment runs in its own sub-agent; running either one in this context when a sub-agent tool exists is not permitted, even when it is faster; the fallback below is only for sessions with no sub-agent tool. Give each a self-contained prompt (target files, visitor mode, documented spacing scale when present, and its instructions below); do not assume it can read this file. **Sub-agent A (layout assessment)**: give it the full [Assess Current Layout](#assess-current-layout) checklist below, verbatim, in its prompt. It works through every item and returns per-item findings citing file, selector, or value. diff --git a/skill/reference/live.md b/skill/reference/live.md index a0789791e..fb3ecf6a4 100644 --- a/skill/reference/live.md +++ b/skill/reference/live.md @@ -37,7 +37,7 @@ Chat is overhead. No recap, no tutorial output, no pasting PRODUCT / DESIGN bodi node {{scripts_path}}/live.mjs ``` -Output JSON: `{ ok, serverPort, serverToken, pageFiles, hasProduct, product, productPath, hasDesign, design, designPath }`. `pageFiles` is the list of HTML entries the live script was injected into. Keep PRODUCT.md and DESIGN.md in mind for variant generation; **DESIGN.md wins on visual decisions; PRODUCT.md wins on strategic/voice decisions.** When DESIGN.md is missing, identity is **not** absent; extract it from CSS variables, computed styles, and sibling components on the page (see Step 4 Phase A). Identity preservation is the default; departure from existing identity requires an explicit trigger from PRODUCT.md anti-references or the user's freeform prompt. +Output JSON: `{ ok, serverPort, serverToken, pageFiles, hasProduct, product, productPath, hasDesign, design, designPath }`. `pageFiles` is the list of HTML entries the live script was injected into. Keep PRODUCT.md, DESIGN.md, and any surface brief already loaded by Setup in mind for variant generation: **DESIGN.md wins on visual decisions; PRODUCT.md wins on durable product and voice decisions; the surface brief wins on this surface's strategy.** When DESIGN.md is missing, identity is **not** absent; extract it from CSS variables, computed styles, and sibling components on the page (see Step 4 Phase A). Identity preservation is the default; departure requires the user's explicit redesign/replacement intent. `serverPort` and `serverToken` belong to the small **Impeccable live helper** HTTP server (serves `/live.js`, SSE, and `/poll`). That port is **not** your dev server and is usually not the URL you open to view the app. The browser page is whatever origin serves one of the `pageFiles` entries (Vite / Next / Bun / tunnel / LAN hostname). @@ -228,10 +228,7 @@ This sentence is the **identity lock**. Every variant must be readable as the sa **Default mode**: the existing identity is preserved. Variants vary expression axes within it. *This is the right mode for ~90% of live sessions.* The user picked an element on a real product they're shipping; they expect variants of *their* hero, not three different brands' heroes. -**Departure mode**: the existing identity is rejected. Variants propose alternatives consistent with PRODUCT.md voice. Trigger only when at least one is true: - -- PRODUCT.md anti-references explicitly call out the current surface ("the current `index.html` is itself an example"; "diffuse away from this"; "the page on screen is the failure"). Generic anti-references that describe what to avoid in general do **not** trigger departure mode; only ones that point at *this* surface specifically. -- The user's freeform prompt explicitly asks for departure ("rebuild this from scratch", "what if it weren't editorial at all", "show me something completely different"). +**Departure mode**: the existing identity is rejected. Variants propose alternatives consistent with durable product and brand truth. Trigger only when the user explicitly asks for departure in the current request or freeform prompt ("redesign this", "rebuild this from scratch", "what if it weren't editorial at all", "show me something completely different"). A stale page critique or an old task note is not replacement authorization. If you're unsure, you're in default mode. The cost of being wrong about default is "three on-brand variants with similar feel": recoverable, the user picks none. The cost of being wrong about departure is "three off-brand variants": unrecoverable, the user is annoyed. @@ -293,7 +290,7 @@ In **default mode**, the prompt narrows the axes you choose, not the identity. * In **departure mode**, the prompt narrows the lanes you draw from, not the families. *"Make it feel like a newspaper front page"* would itself be a departure-mode prompt; honor it but pick three meaningfully different newspaper-adjacent lanes (broadsheet vs. tabloid vs. trade journal), and run the family pass to confirm they don't collapse into one. -When the prompt and PRODUCT.md anti-references conflict (the prompt asks for X, the anti-references ban X), the anti-references win; they describe the brand's standing position, the prompt is one moment. +When the prompt conflicts with a confirmed binding brand commitment or DESIGN.md invariant, preserve the invariant unless the user explicitly revokes or replaces it. Task-local strategy from the matching surface brief may change when the user changes that surface's goal. ### 6. Write all variants in a single edit diff --git a/skill/reference/new-work.md b/skill/reference/new-work.md index 2a06bc5fb..6adf4083a 100644 --- a/skill/reference/new-work.md +++ b/skill/reference/new-work.md @@ -1,81 +1,164 @@ -# Surface concept and craft +# New visual work -This is the shared task-scoped concept playbook for `craft`, `shape`, and substantial from-scratch surface work. `craft` continues through the contract, build, and finish below. `shape` follows this file through the user's concept choice, then reads [shape.md](shape.md), writes the design brief, and stops before code. PRODUCT.md owns durable product truth; DESIGN.md owns the current user-approved visual world. +This flow owns two decisions: the durable visual world when authority is absent, expanding, or explicitly replaced; and the task-scoped concept for the surface being made. PRODUCT.md owns product truth, DESIGN.md confirmed visual truth, and `.impeccable/surfaces/` the task strategy. Complete [init.md](init.md) first when PRODUCT.md is missing. Missing DESIGN.md does not route back to init. -If PRODUCT.md or DESIGN.md is missing, stop and complete [init.md](init.md) first. For refinement, init documents coherent incumbent visual code instead of inventing a replacement. For redesign, init replaces the old visual world before returning here; the old system is evidence and anti-reference, not authority. +## 1. Name the intent -**A committed world does not decide the new surface.** Every case study, dashboard view, feature page, or section still needs an ownable task concept. The job here is to invent that concept with the user without re-rolling the brand. +- **Greenfield:** no coherent visual implementation. +- **Extension:** a new surface inside an established world. +- **Expression expansion:** an established brand entering an unresolved surface family. +- **Redesign/rebrand:** replace the world while preserving unchanged product truth, content, function, native affordances, constraints, and brand commitments. +- **Refinement:** leave this flow for the scoped command; preserve the world and scope. -## Name the work +A plain “redesign this page/site” authorizes replacement. “Redesign this within the current brand/system” means extension or refinement. Ask once only when the wording is genuinely ambiguous. -Use the user's intent, not the age of the codebase: +## 2. Resolve visual authority -- **Greenfield** creates the first surface inside the world init just established. -- **Redesign** composes inside the replacement world init just established. Preserve product truth, real content, functionality, and native affordances; do not preserve the discarded look by habit. -- **Extension** adds a new surface inside the committed world. Preserve its lineage and interaction conventions while giving this task its own composition. -- **Refinement** belongs to the invoked refinement command, not this full concept flow. Preserve the incumbent world and named scope. +Read DESIGN.md and representative code, tokens, components, and assets. Choose one path: -If “redesign” could mean either a replacement identity or an on-brand structural adjustment, use the structured question tool to resolve that single ambiguity before proceeding. A plain “redesign this page/site” means replacement; “within the current brand/system” means extension or refinement. +### A. Explicit redesign -## Discover the surface +The old DESIGN.md and implementation are not authority. Keep only unrevoked product facts, content, function, native expectations, constraints, and brand commitments. Establish a replacement world. -Name the subject, audience, surface job, visitor mode, real content, and primary action. Read PRODUCT.md and DESIGN.md as anchors, but ask about what is unique to this task. A case-study section, for example, needs the proof available, the transformation it must make legible, the audience's reading order, and the moment worth remembering; the global brand interview cannot answer those. +### B. DESIGN.md covers this kind of surface -In an attended run, ask a focused round of no more than three task questions, then wait. Use the structured question tool when available. Do not re-ask durable questions already settled in PRODUCT.md or DESIGN.md. A harness-provided simulated user is attended and must receive the same questions. +Use its invariants and normative tokens. Skip world-building and discover the surface. -For a narrow request whose content, outcome, and constraints are already explicit, assert what you understand and ask the user to confirm or correct it. Do not manufacture an interview when there is no material uncertainty. +### C. A coherent implementation exists but DESIGN.md does not -## Develop the surface concept +Code, assets, tokens, type, and component behavior are incumbent authority. Run [document.md](document.md) in scan mode and confirm extracted invariants before writing DESIGN.md. Do not offer replacement worlds unless the user asked for a redesign. + +### D. The brand exists, but this surface family is unresolved + +Preserve logo, color/type assets, voice, recognizable component/motion traits, and constraints. Ask what must carry and where expression may expand. Offer two or three compatible ranges, not replacement identities, and merge the choice into DESIGN.md. Use a child-app DESIGN.md when the range is local. + +### E. No confirmed visual authority exists + +Establish a world. Scaffolds, framework defaults, and stray utilities are not identity. + +## 3. Establish or replace the visual world + +Run this only for A or E. The world must govern more than one artifact and still constrain the build. + +1. **Ground.** Use PRODUCT.md's mechanism, users, context, evidence, commitments, and the brief. Ask at most three questions about unknown visual premises, never CSS values. +2. **Derive.** Generate five to seven grounded candidates. State each identity thesis, information/layout grammar, material and type behavior, color strategy, imagery, motion, and reusable signature. Do not rank yet. +3. **Add external selection pressure.** Run `node {{scripts_path}}/concept-seed.mjs --scope world`. Promote the assigned grounded candidate into the serious shortlist and weigh the printed challengers only when they can become a coherent system rather than a one-page costume. +4. **Test breadth and defaults.** Reject one-hero costumes. Test navigation, quiet/dense content, interaction/state, and an unlike surface. Compare survivors with the category's habitual and predictable contrarian looks; revise defaults without turning anti-reference into recipe. +5. **Offer neutral choices.** Present two or three materially different worlds without recommendation cues. Explain consequences; ask what is closest, should combine, or feels wrong. Rejection is allowed. +6. **Resolve.** Set durable type, color roles, materials, layout, imagery, motion, and signature. Defer exact files and tokens when implementation is the honest decision point. + +Without an answer mechanism, use the assigned grounded candidate only if it survives product fit and breadth; mark assumptions. This is fallback, not user choice. + +### Write the directional DESIGN.md seed + +Before code, write or replace DESIGN.md at the resolved project/app boundary using the [format spec](https://raw.githubusercontent.com/google-labs-code/design.md/main/docs/spec.md). Record the chosen overview and relevant visual sections. Add: + +`` + +Do not fabricate YAML tokens; add exact values only after the user, assets, or implementation establishes them. A redesign plus the user's world choice authorizes replacement without another confirmation. + +## 4. Discover the requested surface + +Name this surface's audience, job, visitor mode, real content, primary action/task, evidence, constraints, and memorable moment. PRODUCT.md supplies truth and DESIGN.md the world; neither decides narrative or composition. + +Ask one attended round of at most three material questions without repeating durable facts. CTA hierarchy, proof sequence, content gaps, and interaction outcomes belong here, not PRODUCT.md. For a fully specified narrow request, state the interpretation and invite correction. + +When `shape` has already completed its discovery interview, reuse those confirmed answers and do not ask this round again. + +## 5. Develop the surface concept The visual world supplies the vocabulary; the task concept supplies the sentence. -1. **State the mechanism.** In one sentence, name what this surface does, proves, or enables that a neighboring product could not truthfully claim. -2. **Expose the defaults.** Describe the category's habitual arrangement and the predictable contrarian response. Treat both as warnings, not automatic answers. -3. **Derive structural material.** From the task's real content, PRODUCT.md's audience world, and DESIGN.md's existing motifs, list five to seven forms, documents, rituals, spatial arrangements, or behaviors that could carry the mechanism. Translate their reading order and relationships, not their costume, into interface structure. -4. **Break the model's ranking rut.** For substantial greenfield, redesign, or extension work, run `node {{scripts_path}}/concept-seed.mjs`. Use its assigned index to promote one overlooked grounded candidate, and weigh its challengers only on audience identification and product clarity. A challenger may change topology or interaction, but it may not override the current DESIGN.md. Skip the seed for a small extension or when the user has already pinned the surface concept. -5. **Offer real choices.** Present two or three materially different surface concepts. For each, give the layout or interaction thesis, narrative sequence, first-view or focal moment, signature use, and why it belongs in the committed world. These are not moodboards with different adjectives; the content must be organized or experienced differently. -6. **Let the user direct.** Ask which concept is closest, what to combine, and what feels wrong. The user may reject all of them. Resolve the chosen concept before code. If one direction is overwhelmingly supported, assert it and ask for confirmation instead of staging a fake menu. -7. **Probe when pictures would clarify structure.** When the harness has native image generation and the substantial, high-fidelity surface would benefit from a visual test, load [codex.md](codex.md) before writing the direction contract. Its probes stay inside DESIGN.md and pressure-test the shortlisted surface concepts; they never reopen palette, typography, or identity. Skip it for narrow extensions, low-fidelity work, or when the user already supplied an approved comp. +1. **State the mechanism.** Name what this surface does, proves, or enables that a neighbor could not truthfully claim. +2. **Derive structural material.** From content, mechanism, audience, and DESIGN.md, list five to seven forms, behaviors, spatial arrangements, or narratives. Translate relationships and reading order, not costume. Do not rank yet. +3. **Break the ranking rut.** For substantial greenfield, redesign, expression expansion, or extension work, run `node {{scripts_path}}/concept-seed.mjs --scope surface`. Promote its assigned grounded candidate and weigh challengers on audience identification and product clarity. Skip the roll for a small extension or a user-pinned concept. +4. **Audit defaults.** Name the habitual arrangement and predictable contrarian response. Judge the shortlist skin-blind: without color, type, texture, or concept nouns, distinct candidates still differ in topology, sequence, or interaction. +5. **Offer neutral choices.** Present two or three concepts without recommendation cues. Give each thesis, sequence, focal moment, signature, implementation consequence, and world lineage. +6. **Let the user direct.** Ask what is closest, should combine, or feels wrong. Resolve before code; rejection is allowed. +7. **Probe when useful.** For a substantial high-fidelity surface with native image generation, load [codex.md](codex.md). Probes stay inside the world. -When no human or structured question mechanism exists, follow the same derivation, build the seed's assigned grounded candidate when it survives the two tests, record the decision, and continue. Unattended does not mean unconsidered; external selection is what prevents the model from quietly returning to its own first choice. +Without an answer mechanism, use the assigned grounded concept only if it survives both tests. -For `shape`, stop here after the user selects the concept and continue in [shape.md](shape.md). Do not write a direction contract or implementation. +For `shape`, stop after the user selects the concept and continue in [shape.md](shape.md). Keep a newly written DESIGN.md seed directional; exact tokens wait for implementation. -## Write the direction contract +## 6. Persist the surface brief -Before code, write the chosen task direction as a contract of at most 150 words. Place it in an opening HTML comment or framework comment block so the Impeccable Stop hook can audit the render against it. The first 200 characters of the comment must name `DIRECTION CONTRACT`. +Once the primary target or route is known, persist task-local product/UX strategy separately from PRODUCT.md and DESIGN.md. Prefer a clone-stable source file; map routes and alternate entries as related targets. Read any record first: -Use these six short blocks: +`node {{scripts_path}}/surface-brief.mjs read ` -- `UNIQUE`: the surface thesis tied to the product mechanism; +Exit 0: preserve still-valid decisions and change only what the user changed. For redesign, retain valid product strategy, content, function, and open decisions; replace visual direction and contract. Exit 2: no brief. After the contract, write with `node {{scripts_path}}/surface-brief.mjs write [related-target ...]`. + +The body is concise and contains: + +```markdown +# Surface brief: [name] + +## Scope +[Primary/related route or artifact, visitor mode, and what this surface owns.] + +## Product strategy +[Surface-specific audience and job, desired outcome, primary/secondary action or task, content and proof sequence, factual constraints, and explicitly open decisions.] + +## Selected direction +[Reference to the applicable DESIGN.md world or expression range, selected surface concept, focal moment, narrative/interaction sequence, and implementation consequence.] + +## Direction contract +[The six contract blocks below.] + +## Open decisions +[Only unresolved items that later work must not silently invent. Omit when empty.] +``` + +Commit `.impeccable/surfaces/.md` as stable later-work context. Exclude global truth, exact tokens, transient notes, and work logs. + +## 7. Write the direction contract + +If a competent default could satisfy the concept, sharpen its focal moment until one product-specific move changes implementation. Difficulty must clarify the product, not add spectacle. + +Before code, write a direction contract of at most 150 words in an opening HTML or framework comment. The first 200 characters must name `DIRECTION CONTRACT`. + +- `UNIQUE`: the task thesis tied to the product mechanism; - `NOT-TEMPLATE`: the category-default arrangement this structure refuses; -- `OWN-WORLD`: the specific current DESIGN.md invariants, tokens, and materials it uses; -- `STORY`: what the visitor understands, believes, and does from entry to action; -- `FIRST VIEWPORT`: the exact composition, hierarchy, and primary action (or the equivalent first task for a product surface); -- `FORM`: the chosen structural or behavioral form, its signature, and the concept-seed key when one was used. +- `OWN-WORLD`: the confirmed DESIGN.md invariants, tokens, and materials it uses; +- `STORY`: what the visitor understands, believes, and does; +- `FIRST VIEWPORT`: exact composition, hierarchy, action, and where the concept exceeds competent convention; +- `FORM`: chosen structure or behavior, signature, implementation consequence, and seed key. -The contract is not visitor-facing content and not a second design system. It makes the task's promise inspectable. The user's selected concept is the authority; the seed is only provenance. +The contract is task-scoped, inspectable, and subordinate to the user's choice. Put the same six blocks in the surface brief and artifact comment. -## Plan, self-check, build +## 8. Plan, build, and commit -Plan how the chosen concept uses the current DESIGN.md's tokens or directions, reusable technical components, imagery language, and motion grammar. In a redesign, replace visual tokens from the discarded system rather than preserving them through implementation convenience. The layout has two legitimate sources: the concept and the content's real structure. The category's habitual skeleton is neither. Compare the plan with what you would produce for a neighboring product; wherever they converge for no product-specific reason, revise the generic part. +Plan from the concept and real content, never a category skeleton. In redesign, remove inherited visual tokens. -Build the strongest coherent direction once. Commitment means the concept governs the entire requested surface; it does not mean disguising familiar controls as metaphors or violating the design system. +Load only needed specialist references. Focal interaction or authored animation reads [animate.md](animate.md), even without the `animate` command. -**Make the opening a thesis.** The first viewport or first task should demonstrate the product's mechanism, not wrap a generic promise in generic chrome. If someone leaves after that moment, they should remember an idea or interaction, not merely a mood. +Build the strongest coherent direction once. Its grammar governs navigation, actions, controls, content, and transitions without disguising affordances. Give the focal form the scale that gives it force; do not trap it inside a standard hero panel. -**Pace the whole surface.** Long surfaces are a rhythm, not a stack. Vary density, scale, image, and quiet inside DESIGN.md's grammar. A case study should reveal evidence in the order it becomes persuasive; an Operate flow should reveal control in the order the task demands. Cut sections that only repeat claims. +**Make the opening a thesis.** Demonstrate the mechanism immediately; leave an idea, interaction, or evidence, not merely mood. -**Commit before correcting.** Land the chosen concept at full strength before the finishing pass makes it clear, usable, and effective. Do not weaken the hard creative move in anticipation of a generic “too gimmicky” critique; the measured failure is partial commitment, not excess conviction. +**Commit before correcting.** Land the hard move at full strength before refining it. In unattended work, safety is the known risk. -**Make the signature structural.** Use the world's signature where the task concept peaks, at enough scale or consequence that the composition organizes around it. Scattering a motif as decoration is not commitment. +**Commit at page scale.** Let color, material, image, or type own a region when the world calls for it. Scattered signature decoration is not commitment. -**Prove, don't claim.** Show the mechanism working, the actual artifact, the before-and-after, the data, or the specific content. A surface earns belief through evidence a competitor could not copy-paste. +**Pace the whole surface.** Vary density, scale, image, motion, and quiet inside one grammar. Cut repeated claims; prove the mechanism with real artifact, interaction, data, transformation, or content. -For Operate and Read, familiar controls and comprehension remain primary; expression comes from topology, hierarchy, density, rhythm, state, and the system around them. For Persuade and Experience, dramatic pacing and art direction are available when the selected concept earns them, while the primary action and reading order stay clear. +**Author motion as material.** Motion expresses the world and task. Premium moments go beyond repeated transform/opacity through earned focus, depth, masks, continuity, light, or material change. Bound expensive effects, test in-browser, keep content visible by default, and design reduced motion. -Briefs that depend on imagery ship real, verified imagery. Preserve semantic HTML, familiar interaction behavior, accessibility, performance, responsive behavior, and the project's technical conventions. +Preserve semantics, affordances, accessibility, performance, responsiveness, and project conventions. Operate/Read express through topology, hierarchy, density, rhythm, and state; Persuade/Experience may earn drama. -## Finish like a studio +## 9. Solidify the visual record -Inspect desktop and mobile, write one honest critique against the task brief, DESIGN.md, the user's selected concept, and the direction contract, then patch material defects. Judge the skeleton skin-blind: mentally remove color, type, texture, and concept nouns; if the remaining block arrangement is the category template, rebuild the structure. Run the detector once. On harnesses with a Stop hook, let its contract audit run and fix every real gap it identifies; classify false positives rather than distorting intentional work. Repeat only while a real defect remains. A separate reviewer is optional when the harness provides one and the risk earns the cost. +After first implementation of a new/replacement world or approved expansion, refresh DESIGN.md from the build: + +- replace provisional direction with the exact type, color roles, tokens, spacing/radii, components, states, and motion that survived; +- add normative YAML tokens only for values the implementation actually uses; +- remove the directional-seed status once the record and implementation agree; +- preserve broader world invariants and expression ranges; +- do not promote the task's story, hero composition, or one-off motif into a global rule unless it is intentionally reusable. + +Ordinary extension does not rewrite DESIGN.md; only approved durable changes do. + +## 10. Finish like a studio + +Inspect desktop and mobile; critique against the brief, DESIGN.md, concept, and contract; patch material defects; recheck skin-blind; run the detector once. With a Stop hook, fix real gaps and classify false positives until none remain. Add a reviewer only when risk earns it. diff --git a/skill/reference/polish.md b/skill/reference/polish.md index bc26cfc02..b81f7c94b 100644 --- a/skill/reference/polish.md +++ b/skill/reference/polish.md @@ -1,241 +1,98 @@ -> **Additional context needed**: quality bar (MVP vs flagship). +> **Additional context needed**: quality bar and shipping constraints. -Perform a meticulous final pass to catch all the small details that separate good work from great work. The difference between shipped and polished. +Polish is refinement, never concealed redesign. Preserve the incumbent visual world, content, behavior, and everything outside scope. If the concept itself is wrong, say so and recommend redesign or `bolder` instead of smuggling in a replacement. -Detector and automated QA output are defect evidence only. A clean script result is never proof that the design is strong; gather browser evidence and inspect the real interaction path. +A detector result is defect evidence, not proof of quality. Inspect the rendered experience and real interaction path. -## Design System Discovery +## 1. Establish the system -Aligning the feature to the design system is **not optional**. Polish without alignment is decoration on top of drift, and it makes the next person's job harder. Discovery comes before any other polish work. +Read DESIGN.md and representative tokens, shared components, patterns, and neighboring flows. If no formal system exists, use coherent project conventions. -1. **Find the design system**: Search for design system documentation, component libraries, style guides, or token definitions. Study the core patterns: design principles, target audience, color tokens, spacing scale, typography styles, component API, motion conventions. -2. **Note the conventions**: How are shared components imported? What spacing scale is used? Which colors come from tokens vs hard-coded values? What motion and interaction patterns are established? What flow shapes are used for comparable actions (modal vs full-page, inline vs route, save-on-blur vs explicit submit)? -3. **Identify drift, then name the root cause**: For every deviation, classify it as a **missing token** (the value should exist in the system but doesn't), a **one-off implementation** (a shared component already exists but wasn't used), or a **conceptual misalignment** (the feature's flow, IA, or hierarchy doesn't match neighboring features). The fix differs by category: patch the value, swap to the shared component, or rework the flow. Fixing the symptom without naming the cause is how drift compounds. +Classify each drift before fixing it: -If a design system exists, polish **must** align the feature with it. If none exists, polish against the conventions visible in the codebase. **If anything about the system is ambiguous, ask. Never guess at design system principles.** +- **missing token:** the system needs a reusable value; +- **one-off implementation:** an existing shared component or pattern should replace it; +- **conceptual mismatch:** the flow, information architecture, or hierarchy differs from comparable product areas; +- **local defect:** the implementation is simply incomplete or inconsistent. -## Pre-Polish Assessment +Fix the cause at the narrowest correct level. Ask when a binding system principle cannot be inferred. -Understand the current state and goals before touching anything: +## 2. Gather the evidence -1. **Review completeness**: - - Is it functionally complete? - - Are there known issues to preserve (mark with TODOs)? - - What's the quality bar? (MVP vs flagship feature?) - - When does it ship? (How much time for polish?) +Use the feature yourself at representative desktop and mobile sizes. Determine: -2. **Think experience-first**: Who actually uses this, and what's the best possible experience for them? Effective design beats decorative polish; a feature that looks beautiful but fights the user's flow is not polished. Walk the path from their perspective before opening DevTools. +- whether the path is functionally complete; +- the intended quality bar and time available; +- known constraints or deliberately unfinished work; +- the states, content lengths, roles, and input methods users will actually encounter. -3. **Identify polish areas**: - - Visual inconsistencies - - Spacing and alignment issues - - Interaction state gaps - - Copy inconsistencies - - Edge cases and error states - - Loading and transition smoothness - - Information architecture and flow drift (does this feature reveal complexity the way neighboring features do?) +If a prior critique exists, use it as one input: -4. **Pull in any prior critique** (optional signal): If `{{command_prefix}}impeccable critique` has been run on the same target, its priority issues are a useful prior for what to address first. Resolve the target to a file path or URL, then: - ```bash - slug=$(node {{scripts_path}}/critique-storage.mjs slug "") - node {{scripts_path}}/critique-storage.mjs latest "$slug" - ``` - Exit 0 with body = found; fold the P0/P1 items into your polish list and mention the snapshot path so the user sees what you read. Exit 2 = no snapshot, continue without it. The critique is one input among many. Do your own pass either way. +```bash +slug=$(node {{scripts_path}}/critique-storage.mjs slug "") +node {{scripts_path}}/critique-storage.mjs latest "$slug" +``` -5. **Triage cosmetic vs functional**: Classify each issue as **cosmetic** (looks off, doesn't impede the user) or **functional** (breaks, blocks, or confuses the experience). When polish time is tight, functional issues ship first; cosmetic ones can land in a follow-up. Quality should be consistent; never perfect one corner while leaving another rough. +Exit 0 returns the latest snapshot; incorporate relevant P0/P1 findings and name the snapshot read. Exit 2 means none exists. Perform an independent pass either way. -**CRITICAL**: Polish is the last step, not the first. Don't polish work that's not functionally complete. +## 3. Triage -## Polish Systematically +Separate functional defects from cosmetic ones and fix in this order: -Work through these dimensions methodically: +1. broken or blocked tasks, data loss, misleading state, and inaccessible paths; +2. missing loading, empty, error, success, disabled, and permission states; +3. flow, hierarchy, responsive, and design-system drift; +4. visual and motion inconsistencies; +5. code and asset cleanup. -### Visual Alignment & Spacing +Do not perfect one corner while leaving the rest below the same quality bar. -- **Pixel-perfect alignment**: Everything lines up to grid -- **Consistent spacing**: All gaps use spacing scale (no random 13px gaps) -- **Optical alignment**: Adjust for visual weight (icons may need offset for optical centering) -- **Responsive consistency**: Spacing and alignment work at all breakpoints -- **Grid adherence**: Elements snap to baseline grid +## 4. Polish the whole path -**Check**: -- Enable grid overlay and verify alignment -- Check spacing with browser inspector -- Test at multiple viewport sizes -- Look for elements that "feel" off +### Flow and hierarchy -### Information Architecture & Flow +- Match neighboring mental models, terminology, disclosure, routing, save behavior, and optimistic or pessimistic patterns. +- Make the primary task and current state obvious without flattening every element to equal weight. +- Ensure arrival, transition, empty, and recovery paths connect instead of behaving as isolated screens. -Visual polish on a misshapen flow is wasted work. Match the *shape* of the experience to the system, not just the surface. +### Layout and type -- **Progressive disclosure**: Match how much is revealed when, compared to neighboring features. A settings page exposing 40 fields when the rest of the app reveals 5 at a time is drift, even if every field is perfectly styled. -- **Established user flows**: Multi-step actions follow the same shape as comparable flows elsewhere: modal vs full-page, inline edit vs separate route, save-on-blur vs explicit submit, optimistic vs pessimistic updates. -- **Hierarchy & complexity**: The same conceptual weight gets the same visual weight throughout. Primary actions don't become tertiary in one corner of the product, and tertiary actions don't shout. -- **Empty, loading, and arrival transitions**: How content arrives, updates, and leaves matches how it does in adjacent features. -- **Naming and mental model**: The feature uses the same nouns and verbs as the rest of the system. A "Workspace" here shouldn't be a "Project" three screens away. +- Align to the project's grid and spacing scale; fix optical as well as mathematical alignment. +- Group related content tightly and separate distinct groups generously. +- Keep same-role typography consistent; test measure, wrapping, localization expansion, zoom, and font loading. +- Verify every supported viewport rather than correcting only the current screenshot. -### Typography Refinement +### Color, imagery, and icons -- **Hierarchy consistency**: Same elements use same sizes/weights throughout -- **Line length**: 45-75 characters for body text -- **Line height**: Appropriate for font size and context -- **Widows & orphans**: No single words on last line -- **Hyphenation**: Appropriate for language and column width -- **Kerning**: Adjust letter spacing where needed (especially headlines) -- **Font loading**: No FOUT/FOIT flashes +- Use semantic tokens and stable color meanings across themes. +- Verify text, control, and focus contrast in every state. +- Keep icon families, stroke/weight, sizing, and optical alignment coherent. +- Prevent image layout shift; use correct aspect ratios, responsive sources, and useful alt text. -### Color & Contrast +### Interaction and state -- **Contrast ratios**: All text meets WCAG standards -- **Consistent token usage**: No hard-coded colors, all use design tokens -- **Theme consistency**: Works in all theme variants -- **Color meaning**: Same colors mean same things throughout -- **Accessible focus**: Focus indicators visible with sufficient contrast -- **Gray on color**: Never put gray text on colored backgrounds; use a shade of that color or transparency +- Every control needs appropriate default, hover, focus, active, disabled, loading, error, and success behavior. +- Preserve visible keyboard focus, logical tab order, labels, and platform-appropriate touch targets. +- Keep motion coherent, interruptible, performant, and reduced-motion aware. Do not add animation merely to make polish visible. +- Validate long, missing, localized, offline, slow, and permission-limited content where the product can encounter it. -### Interaction States +### Content and code -Every interactive element needs all states: +- Keep terminology, capitalization, punctuation, and factual copy consistent. Ask before changing claims. +- Remove debug output, dead code, unused imports, obsolete styles, and polish-created duplication. +- Replace custom implementations with shared components where the system owns the pattern. +- Promote genuinely reusable values to tokens; do not create a system abstraction for one local exception. -- **Default**: Resting state -- **Hover**: Subtle feedback (color, scale, shadow) -- **Focus**: Keyboard focus indicator (never remove without replacement) -- **Active**: Click/tap feedback -- **Disabled**: Clearly non-interactive -- **Loading**: Async action feedback -- **Error**: Validation or error state -- **Success**: Successful completion +## 5. Verify and finish -**Missing states create confusion and broken experiences**. +Walk the complete path again with mouse, keyboard, and touch where applicable. Check: -### Micro-interactions & Transitions +- mobile, intermediate, and wide layouts; +- loading, empty, error, success, disabled, long-content, and missing-content states; +- zoom, contrast, focus, semantics, screen-reader names, and reduced motion; +- console errors, layout shift, interaction latency, image loading, and supported browsers; +- agreement with DESIGN.md, neighboring features, and the user's scope. -- **Smooth transitions**: All state changes animated appropriately (150-300ms) -- **Consistent easing**: Use ease-out-quart/quint/expo for natural deceleration. Never bounce or elastic; they feel dated. -- **No jank**: Smooth animations; use atmospheric blur/filter/mask/shadow effects when they add polish, but bound expensive paint areas and avoid casual layout-property animation -- **Appropriate motion**: Motion serves purpose, not decoration -- **Reduced motion**: Respects `prefers-reduced-motion` +Run the relevant detector or QA commands, fix real defects, and document only narrow intentional exceptions. A clean scan does not replace visual judgment. -### Content & Copy - -- **Consistent terminology**: Same things called same names throughout -- **Consistent capitalization**: Title Case vs Sentence case applied consistently -- **Grammar & spelling**: No typos -- **Appropriate length**: Not too wordy, not too terse -- **Punctuation consistency**: Periods on sentences, not on labels (unless all labels have them) - -### Icons & Images - -- **Consistent style**: All icons from same family or matching style -- **Appropriate sizing**: Icons sized consistently for context -- **Proper alignment**: Icons align with adjacent text optically -- **Alt text**: All images have descriptive alt text -- **Loading states**: Images don't cause layout shift, proper aspect ratios -- **Retina support**: 2x assets for high-DPI screens - -### Forms & Inputs - -- **Label consistency**: All inputs properly labeled -- **Required indicators**: Clear and consistent -- **Error messages**: Helpful and consistent -- **Tab order**: Logical keyboard navigation -- **Auto-focus**: Appropriate (don't overuse) -- **Validation timing**: Consistent (on blur vs on submit) - -### Edge Cases & Error States - -- **Loading states**: All async actions have loading feedback -- **Empty states**: Helpful empty states, not just blank space -- **Error states**: Clear error messages with recovery paths -- **Success states**: Confirmation of successful actions -- **Long content**: Handles very long names, descriptions, etc. -- **No content**: Handles missing data gracefully -- **Offline**: Appropriate offline handling (if applicable) - -### Responsiveness - -- **All breakpoints**: Test mobile, tablet, desktop -- **Touch targets**: 44x44px minimum on touch devices -- **Readable text**: No text smaller than 14px on mobile -- **No horizontal scroll**: Content fits viewport -- **Appropriate reflow**: Content adapts logically - -### Performance - -- **Fast initial load**: Optimize critical path -- **No layout shift**: Elements don't jump after load (CLS) -- **Smooth interactions**: No lag or jank -- **Optimized images**: Appropriate formats and sizes -- **Lazy loading**: Off-screen content loads lazily - -### Code Quality - -- **Remove console logs**: No debug logging in production -- **Remove commented code**: Clean up dead code -- **Remove unused imports**: Clean up unused dependencies -- **Consistent naming**: Variables and functions follow conventions -- **Type safety**: No TypeScript `any` or ignored errors -- **Accessibility**: Proper ARIA labels and semantic HTML - -## Polish Checklist - -Go through systematically: - -- [ ] Aligned to the design system (drift named and resolved by root cause) -- [ ] Information architecture and flow shape match neighboring features -- [ ] Visual alignment perfect at all breakpoints -- [ ] Spacing uses design tokens consistently -- [ ] Typography hierarchy consistent -- [ ] All interactive states implemented -- [ ] All transitions smooth (60fps) -- [ ] Copy is consistent and polished -- [ ] Icons are consistent and properly sized -- [ ] All forms properly labeled and validated -- [ ] Error states are helpful -- [ ] Loading states are clear -- [ ] Empty states are welcoming -- [ ] Touch targets are 44x44px minimum -- [ ] Contrast ratios meet WCAG AA -- [ ] Keyboard navigation works -- [ ] Focus indicators visible -- [ ] No console errors or warnings -- [ ] No layout shift on load -- [ ] Works in all supported browsers -- [ ] Respects reduced motion preference -- [ ] Code is clean (no TODOs, console.logs, commented code) - -**IMPORTANT**: Polish is about details. Zoom in. Squint at it. Use it yourself. The little things add up. - -Sweat the details. Zoom in until the alignment is right and the spacing reads as deliberate. Then ship. - -**NEVER**: -- Polish before it's functionally complete -- Polish without aligning to the design system; that's decoration on drift -- Guess at design system principles instead of asking when something is ambiguous -- Spend hours on polish if it ships in 30 minutes (triage) -- Introduce bugs while polishing (test thoroughly) -- Ignore systematic issues (if spacing is off everywhere, fix the system, not just one screen) -- Perfect one thing while leaving others rough (consistent quality level) -- Create new one-off components when design system equivalents exist -- Hard-code values that should use design tokens -- Introduce new patterns or flows that diverge from established ones - -## Final Verification - -Before marking as done: - -- **Use it yourself**: Actually interact with the feature. -- **Test on real devices**: Not just browser DevTools. -- **Ask someone else to review**: Fresh eyes catch things. -- **Compare to design**: Match intended design. -- **Check all states**: Don't just test happy path. -- **Treat automation carefully**: Run detector or QA commands when they are available and relevant, fix their defects, but never cite a clean result as proof that the work is polished. - -## Clean Up - -After polishing, ensure code quality: - -- **Replace custom implementations**: If the design system provides a component you reimplemented, switch to the shared version. -- **Remove orphaned code**: Delete unused styles, components, or files made obsolete by polish. -- **Consolidate tokens**: If you introduced new values, check whether they should be tokens. -- **Verify DRYness**: Look for duplication introduced during polishing and consolidate. +Finish with a source diff: remove accidental churn, orphaned code, redundant values, and temporary artifacts. Ship only when the feature is functionally complete and consistently finished across the path. diff --git a/skill/reference/quieter.md b/skill/reference/quieter.md index dc3e960f9..7bba66e76 100644 --- a/skill/reference/quieter.md +++ b/skill/reference/quieter.md @@ -2,7 +2,7 @@ Quiet design is harder than bold design. Subtlety needs precision. Reduce visual --- -## Register +## Visitor mode Persuade + Experience: "quieter" means more restrained palette, more whitespace, more typographic air. Drama is reduced, not eliminated; the POV stays intact. diff --git a/skill/reference/routing.md b/skill/reference/routing.md index eb1327f99..e59a28483 100644 --- a/skill/reference/routing.md +++ b/skill/reference/routing.md @@ -11,7 +11,7 @@ Reason over the signals; there is no score to obey: - `critique.latest` with a low `score` or non-zero `p0` / `p1` → `polish` (it reads that snapshot as its backlog), or re-run `critique` if the snapshot looks stale. - `git.changedFiles` pointing at one surface → scope `audit` or `polish` to those files specifically, naming them. - `devServer.running` true → `live` is available for in-browser iteration; if false, don't lead with `live`. **`live` and the bundled `detect.mjs` are web-only.** If `setup.platform` is `ios`, `android`, or `adaptive`, don't lead with either; the browser overlay and the HTML rule engine don't apply to native app code. -- Otherwise group by intent exactly as init's "Recommend starting points" step does (build new / improve what's there / iterate visually), tailored to the current surface and `setup.platform`. +- Otherwise group by intent (build new / improve what's there / iterate visually), tailored to the current surface and `setup.platform`. **If `scan.targets` is non-empty and `setup.platform` is not `ios`/`android`/`adaptive`, run `node {{scripts_path}}/detect.mjs --json ` once** (the bundled detector over local files: no network, no npx; it reads HTML/CSS, so skip it for native projects). `scan.via` tells you what they are: `git-changes` (the markup/style files in your dirty tree, the most relevant set), `source-dir` (e.g. `src`, `app`), `html`, or `root`. Fold the hits into your picks: many quality / contrast hits → `audit` or `polish`; a specific slop family → the matching command (gradient text or eyebrows → `quieter` / `typeset`, flat or gray palette → `colorize`, and so on). It's a real, current signal that beats guessing. If detect errors or the tree is large and slow, skip it and recommend the user run `audit` themselves; never block the suggestion on it. diff --git a/skill/reference/shape.md b/skill/reference/shape.md index 2fbf161e3..acbb26e5b 100644 --- a/skill/reference/shape.md +++ b/skill/reference/shape.md @@ -1,23 +1,61 @@ # Shape -Plan the requested surface without writing implementation code. Resolve SKILL.md's init gate, then follow [new-work.md](new-work.md) through task discovery, grounded candidate derivation, external concept seeding when applicable, and the user's concept choice. Return here before the direction contract or build. +Discover what should be made and how it should work, then return a confirmed design brief without code. -## Write the brief +**Product gate:** when context reports that PRODUCT.md is missing, load and complete [init.md](init.md) before Phase 1. Do not substitute shape questions for the product interview. Once PRODUCT.md exists, return here; product context does not replace task discovery. -Turn the confirmed answers and selected concept into the smallest brief that can guide excellent implementation: +## Phase 1: Discovery interview -1. **Surface job:** who arrives, what they need to understand or do, and the visitor mode. -2. **Selected concept:** the product mechanism, structural thesis, narrative or task sequence, focal moment, and signature use inside DESIGN.md. -3. **Scope:** fidelity, breadth, interactivity, named target, and what must remain untouched. -4. **Content and evidence:** real copy, data, assets, states, ranges, and the proof the design must carry. Name missing inputs instead of inventing placeholders. -5. **Interaction and layout:** hierarchy, topology, responsive behavior, primary action, familiar affordances, and important state transitions. Describe intent, not CSS. -6. **Implementation constraints:** platform, framework, performance, accessibility, localization, and reusable project components. -7. **Open decisions:** only unresolved choices that would materially change the build. +Do not write code or choose visual direction yet. -Use a compact 3–5 bullet brief when the prompt, PRODUCT.md, DESIGN.md, and answers already settle the task. Use the full structure only for a genuinely ambiguous, multi-screen, or standalone planning request. Do not restate the conversation to look thorough. +### Cadence + +- Use the structured question tool when available; otherwise ask and stop. +- Ask two or three related questions per round, then wait. One round is the default; add a second only when the answers expose a material gap. +- Do not dump a questionnaire, repeat settled facts, or turn obvious facts into menus. Assert the likely reading and invite correction. +- A sparse prompt requires at least one answer round. A precise prompt may need only a compact confirmation. + +### Round 1: purpose, people, and outcome + +Choose the two or three questions that most change the result: + +- What is this surface or feature for, and what problem must it solve? +- Who specifically reaches it, in what situation and state of mind? +- What is the primary thing they must understand or do? What would success look like? +- What is uniquely true here that a neighboring product or generic template could not claim? + +### Round 2: material, behavior, and boundaries + +Run only for material unresolved decisions: + +- What real content, evidence, data, and assets must the experience carry? What are realistic minimum, typical, and maximum ranges? +- Which states and transitions matter: first-run, empty, loading, error, success, permissions, overflow, or expert use? +- What is the intended fidelity, breadth, and interactivity: exploration, production-ready screen, full flow, or broader surface? +- What must remain untouched? What would make the result feel wrong even if it looked polished? +- Which platform, framework, performance, accessibility, localization, or delivery constraints are binding? + +Never ask for CSS values or canned aesthetic lanes. New-work owns visual-world and concept choices. + +## Phase 2: Resolve the design direction + +For new surfaces, brand expansion, or replacement, follow [new-work.md](new-work.md) through visual authority, any world workshop, and concept choice. Reuse discovery, then return before its contract, persistence, or implementation. Inside an established world, use its concept process only when composition or interaction remains materially open. + +## Phase 3: Write the brief + +Write the smallest useful brief: + +1. **Job and audience:** who arrives, their context, need, and visitor mode. +2. **Outcome and proof:** primary task/action, success, real evidence, and product-specific truth. +3. **Selected direction:** visual authority, structural/interaction thesis, sequence, focal moment, and implementation consequence. +4. **Scope and boundaries:** fidelity, breadth, interactivity, named target, what remains untouched, and explicit anti-goals. +5. **States and ranges:** realistic content/data ranges and material states. +6. **Interaction and layout:** hierarchy, topology, responsiveness, affordances, feedback, and transitions; intent, not CSS. +7. **Constraints and open decisions:** platform, delivery, accessibility, localization, reusable components, and choices a builder must not invent. + +Use three to five bullets when the task is settled; use the full structure only for ambiguous, multi-screen, or standalone planning. Do not restate the conversation. ## Confirm and stop -In an attended run, present the brief through the structured question tool for explicit confirmation or one focused correction round. A simulated user counts. Then stop: shape never writes code or a direction contract. +Present the brief for explicit confirmation or one correction round, then stop: shape never writes code or a direction contract. -When no human or structured answer mechanism exists, record the selected concept and material assumptions as the confirmation surrogate, return the brief, and stop. +When no human or structured answer mechanism exists, mark assumptions plainly, return the brief, and stop. diff --git a/skill/reference/typeset.md b/skill/reference/typeset.md index 4035b157f..269ea5548 100644 --- a/skill/reference/typeset.md +++ b/skill/reference/typeset.md @@ -2,9 +2,9 @@ Typography carries most of the information on the page. Replace generic defaults --- -## Register +## Visitor mode -New identity work belongs to [init.md](init.md), which establishes typography direction with the rest of DESIGN.md. This command works from that committed world. If the user explicitly wants to replace its typographic identity, route the identity change through init and update DESIGN.md; otherwise improve hierarchy, scale, measure, weights, and pairing inside the existing direction. Fluid `clamp()` scale and a ≥1.25 ratio between display steps are useful starting points for Persuade and Experience, not universal mandates. +New or replacement identity work belongs to [new-work.md](new-work.md), which establishes typography direction with the rest of DESIGN.md. This command works from that committed world. If the user explicitly wants to replace its typographic identity, route the identity change through new-work and update DESIGN.md; otherwise improve hierarchy, scale, measure, weights, and pairing inside the existing direction. Fluid `clamp()` scale and a ≥1.25 ratio between display steps are useful starting points for Persuade and Experience, not universal mandates. Operate + Read: system fonts and familiar sans stacks are legitimate here. One well-tuned family typically carries the whole UI. Fixed `rem` scale, 1.125–1.2 ratio between more closely-spaced steps. Long-form Read content wants a steady reading measure and a quiet, stable scale, not display-scale drama. @@ -12,7 +12,7 @@ Operate + Read: system fonts and familiar sans stacks are legitimate here. One w ## Two isolated assessments (required) -Spawn two parallel sub-agents whenever a sub-agent/Task tool is exposed: one for the typography assessment, one for the mechanical pre-scan. If the harness needs explicit user permission for sub-agents, stop and ask before proceeding. Isolation is the point: detector output anchors visual judgment toward what the scan can see, so neither sub-agent gets the other's output. Each assessment runs in its own sub-agent; running either one in this context when a sub-agent tool exists is not permitted, even when it is faster; the fallback below is only for sessions with no sub-agent tool. Give each a self-contained prompt (target files, register, **DESIGN.md** content when present, and its instructions below); do not assume it can read this file. +Spawn two parallel sub-agents whenever a sub-agent/Task tool is exposed: one for the typography assessment, one for the mechanical pre-scan. If the harness needs explicit user permission for sub-agents, stop and ask before proceeding. Isolation is the point: detector output anchors visual judgment toward what the scan can see, so neither sub-agent gets the other's output. Each assessment runs in its own sub-agent; running either one in this context when a sub-agent tool exists is not permitted, even when it is faster; the fallback below is only for sessions with no sub-agent tool. Give each a self-contained prompt (target files, visitor mode, **DESIGN.md** content when present, and its instructions below); do not assume it can read this file. **Sub-agent A (typography assessment)**: give it the full [Assess Current Typography](#assess-current-typography) checklist below, verbatim, in its prompt. It works through every item and returns per-item findings citing file, selector, or value. @@ -185,7 +185,7 @@ Use `ch` units for character-based measure (`max-width: 65ch`). Line-height scal #### Font Selection & Pairing -When a typeface change is actually authorized by the brief and DESIGN.md, use the anti-reflex corrections and pairing principles below. For a broader identity replacement, return to [init.md](init.md) instead of letting a typography command silently create a parallel world. +When a typeface change is actually authorized by the brief and DESIGN.md, use the anti-reflex corrections and pairing principles below. For a broader identity replacement, return to [new-work.md](new-work.md) instead of letting a typography command silently create a parallel world. ##### Anti-reflexes worth defending against diff --git a/skill/scripts/command-metadata.json b/skill/scripts/command-metadata.json index 83796d54b..dad8ef2e0 100644 --- a/skill/scripts/command-metadata.json +++ b/skill/scripts/command-metadata.json @@ -1,6 +1,6 @@ { "craft": { - "description": "Full confirmed-brief-then-build flow. Runs multi-round shape discovery first, resolves visual probe and north-star mock gates when available, then builds and visually iterates. Use when building a new feature end-to-end.", + "description": "Deprecated compatibility alias for an ordinary Impeccable new-work request. It adds no behavior; natural build and redesign requests use the same flow.", "argumentHint": "[feature description]" }, "init": { diff --git a/skill/scripts/concept-seed.mjs b/skill/scripts/concept-seed.mjs index a06028d26..6a5e580aa 100644 --- a/skill/scripts/concept-seed.mjs +++ b/skill/scripts/concept-seed.mjs @@ -1,6 +1,7 @@ #!/usr/bin/env node /** - * Surface-concept seed: the dice half of new-work's task-composition procedure. + * External concept seed: the dice half of new-work's world and surface + * selection procedures. * * The model derives a grounded shortlist of candidate FORMS from the * audience's world and the subject's cultural home (see @@ -22,8 +23,8 @@ * material and win over thin categories, which is the intended shape. * * Usage: - * node scripts/concept-seed.mjs # roll at random - * node scripts/concept-seed.mjs --from # deterministic (hash key) + * node scripts/concept-seed.mjs --scope surface + * node scripts/concept-seed.mjs --scope world --from * * Env vars: * IMPECCABLE_CONCEPT_SEED — same as --from; for reproducible eval runs. @@ -39,6 +40,12 @@ const pool = JSON.parse(readFileSync(join(here, 'concept-ingredients.json'), 'ut const args = process.argv.slice(2); const fromIdx = args.indexOf('--from'); +const scopeIdx = args.indexOf('--scope'); +const scope = scopeIdx !== -1 ? args[scopeIdx + 1] : 'surface'; +if (scope !== 'surface' && scope !== 'world') { + process.stderr.write('concept-seed: --scope must be world or surface\n'); + process.exit(1); +} // When no key is supplied, generate one and print it: a user reporting a // bad outcome can hand us the key and we replay the exact roll. const key = fromIdx !== -1 @@ -46,7 +53,7 @@ const key = fromIdx !== -1 : (process.env.IMPECCABLE_CONCEPT_SEED || crypto.randomBytes(4).toString('hex')); function hashUnit(k, salt) { - const h = crypto.createHash('sha256').update(`${salt}:${k}`).digest(); + const h = crypto.createHash('sha256').update(`${scope}:${salt}:${k}`).digest(); return h.readUInt32BE(0) / 0xffffffff; } const unit = (salt) => hashUnit(key, salt); @@ -66,24 +73,44 @@ for (let i = 0; picks.length < 3 && i < 60; i++) { } } -process.stdout.write(`CONCEPT SEED (key: ${key}; rerun with --from ${key} to reproduce this roll) -PROMOTED INDEX: ${buildIndex} - After ordering the task's grounded structural candidates by resonance, +const promotedInstruction = scope === 'world' + ? `After ordering the grounded visual-world candidates by product fit, promote + candidate ${buildIndex} into the serious shortlist. Present it beside the + strongest materially different candidates and let the user select or revise + the durable world. It must survive navigation, quiet and dense content, + interaction and state, and a surface unlike the current request.` + : `After ordering the task's grounded structural candidates by resonance, promote candidate ${buildIndex} into the serious shortlist. In an attended run, present it beside the strongest materially different candidates and let the user select or revise the surface concept. In a truly unattended - run, use it when it survives audience identification and product clarity. + run, use it when it survives audience identification and product clarity.`; + +const challengerInstruction = scope === 'world' + ? `A challenger enters the world shortlist only when its structure can become + reusable identity grammar across the product, not a one-page costume. Weigh + product identification, product clarity, and cross-surface system breadth.` + : `A challenger wins only when it beats the grounded list on both audience + identification and product clarity. It may change task topology or + interaction, but never the committed visual identity.`; + +const authorityInstruction = scope === 'world' + ? `PRODUCT.md and explicit incumbent brand commitments constrain every world. +The seed never chooses exact colors, fonts, tokens, or a user preference.` + : `PRODUCT.md and DESIGN.md constrain every surface candidate's identity +vocabulary; they do not cancel task-level composition. The seed never +authorizes a new palette, type system, material world, or unfamiliar control +behavior.`; + +process.stdout.write(`${scope.toUpperCase()} CONCEPT SEED (key: ${key}; rerun with --scope ${scope} --from ${key} to reproduce this roll) +PROMOTED INDEX: ${buildIndex} + ${promotedInstruction} The promotion exists to refuse the model's ranking rut, not to outrank the user or the brief. -CHALLENGERS (weigh against your derived candidates on the same two axes, -audience identification and product clarity; a challenger wins only when -it beats the grounded list on both): +CHALLENGERS: 1. ${picks[0]} 2. ${picks[1]} 3. ${picks[2]} -If a challenger survives, it may enter the shortlist as a structural option. -PRODUCT.md and DESIGN.md constrain every candidate's identity vocabulary; -they do not cancel task-level composition. A user- or brief-pinned surface -concept beats the roll, always. The seed never authorizes a new palette, -type system, material world, or unfamiliar control behavior. +${challengerInstruction} +${authorityInstruction} +A user- or brief-pinned decision beats the roll, always. `); diff --git a/skill/scripts/context-signals.mjs b/skill/scripts/context-signals.mjs index 78c1b1286..d296320d7 100644 --- a/skill/scripts/context-signals.mjs +++ b/skill/scripts/context-signals.mjs @@ -11,7 +11,7 @@ * output is always valid JSON. * * Signals: - * - setup: PRODUCT.md / DESIGN.md presence, register, whether code exists + * - setup: PRODUCT.md / DESIGN.md presence and whether code exists * - critique: the latest cached critique score (.impeccable/critique) * - git: branch + files changed vs the default branch (a scope hint) * - devServer: whether a local dev server answers on a common port (gates live) diff --git a/skill/scripts/context.mjs b/skill/scripts/context.mjs index cc33d4851..aa657b863 100644 --- a/skill/scripts/context.mjs +++ b/skill/scripts/context.mjs @@ -1,8 +1,10 @@ /** - * Context loader: prints PRODUCT.md (and DESIGN.md if present) as one - * markdown block on stdout, or prints a `NO_PRODUCT_MD:` message when no + * Context loader: prints PRODUCT.md, DESIGN.md when present, the matching + * persisted surface brief when one can be resolved, and native-platform + * guidance selected from PRODUCT.md. It prints a + * `NO_PRODUCT_MD:` message when no * PRODUCT.md is found anywhere. The skill keys off that message to branch: - * from-scratch build commands (init / teach / craft / shape) and clear + * from-scratch build requests (plus init / teach / shape) and clear * build/shape intent divert into the init flow, while scoped commands proceed * using the existing code as context. * @@ -24,9 +26,11 @@ import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { parseTargetOptions } from './lib/target-args.mjs'; import { IMPECCABLE_COMMAND } from './lib/provider.mjs'; +import { resolveSurfaceBrief } from './lib/surface-briefs.mjs'; const PRODUCT_NAMES = ['PRODUCT.md', 'Product.md', 'product.md']; const DESIGN_NAMES = ['DESIGN.md', 'Design.md', 'design.md']; +const SKILL_REFERENCE_DIR = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..', 'reference'); const FALLBACK_DIRS = ['.agents/context', 'docs']; const MONOREPO_MARKER_FILES = ['pnpm-workspace.yaml', 'turbo.json', 'nx.json', 'lerna.json']; const MONOREPO_FALLBACK_PROJECT_DIRS = ['apps', 'packages']; @@ -75,6 +79,12 @@ export function loadContext(cwd = process.cwd(), options = {}) { const designPath = resolved.designPath; const product = productPath ? safeRead(productPath) : null; const design = designPath ? safeRead(designPath) : null; + const platform = extractPlatform(product); + const surfaceResolution = resolveSurfaceBrief( + resolved.projectRoot, + hasTargetOption(options) ? options.targetPath : null, + ); + const surfaceBrief = surfaceResolution.brief; return { hasProduct: !!product, product, @@ -85,7 +95,18 @@ export function loadContext(cwd = process.cwd(), options = {}) { contextDir: resolved.contextDir, productContextDir: productPath ? path.dirname(productPath) : null, designContextDir: designPath ? path.dirname(designPath) : null, + hasSurfaceBrief: !!surfaceBrief, + surfaceBrief: surfaceBrief?.text ?? null, + surfaceBriefPath: surfaceBrief?.path ? path.relative(absCwd, surfaceBrief.path) : null, + surfaceBriefReason: surfaceResolution.reason, + surfaceBriefCandidates: surfaceResolution.candidates.map((brief) => ({ + slug: brief.slug, + path: path.relative(absCwd, brief.path), + primaryTarget: brief.primaryTarget, + relatedTargets: brief.relatedTargets, + })), hasVisualImplementation: hasVisualImplementation(resolved.projectRoot), + platform, projectRoot: resolved.projectRoot, repoRoot: resolved.repoRoot, isMonorepo: resolved.isMonorepo, @@ -698,6 +719,19 @@ function safeRead(p) { } } +function loadNativePlatformReferences(platform) { + const names = platform === 'adaptive' + ? ['ios', 'android'] + : platform === 'ios' || platform === 'android' + ? [platform] + : []; + return names.flatMap((name) => { + const filePath = path.join(SKILL_REFERENCE_DIR, `${name}.md`); + const content = safeRead(filePath); + return content ? [{ name, filePath, content }] : []; + }); +} + /** * Best-effort evidence that the project already has an incumbent visual * implementation. DESIGN.md is documentation, not the only source of design @@ -989,13 +1023,13 @@ async function cli() { const parts = ctx.hasVisualImplementation ? [ 'NO_PRODUCT_MD: This project has no PRODUCT.md yet, but it does have an incumbent visual implementation. ' + - 'For `init`, `teach`, `craft`, or `shape`, load reference/init.md and create PRODUCT.md with the user first. ' + - 'For extension, init documents the incumbent system; for redesign/rebrand, init replaces it through a new ' + - 'visual-world choice. Other ' + + 'For `init`, `teach`, `shape`, or any request to create a new surface or replacement visual world, load reference/init.md and create PRODUCT.md with the user first. ' + + 'After init writes PRODUCT.md, reference/new-work.md preserves and documents the incumbent system for an ' + + 'extension or replaces it with the user for a redesign/rebrand. Other ' + 'narrow refinement commands may read the CSS, tokens, components, and assets and proceed without blocking, then ' + `offer \`${IMPECCABLE_COMMAND} init\` as a follow-up.`, - 'BUILD_INIT_REQUIRED: Before `craft` or `shape`, init must capture PRODUCT.md with the human or structured ' + - 'simulated user. A redesign then replaces the visual world; an extension documents it.', + 'BUILD_INIT_REQUIRED: Before shape or any new-surface/redesign flow, init must capture PRODUCT.md with the human or structured ' + + 'simulated user. Init writes product truth only; reference/new-work.md owns every visual decision.', 'SCOPED_EXISTING_ALLOWED: Narrow refinement commands may use the incumbent implementation as authority without ' + 'blocking on context setup; they must preserve it and offer init afterward.', 'EXISTING_VISUAL_SYSTEM: For refinement or extension, code and assets are incumbent design authority and missing ' + @@ -1004,17 +1038,18 @@ async function cli() { ] : [ 'NO_PRODUCT_MD: This project has no PRODUCT.md yet. ' + - 'For `init`, `teach`, `craft`, `shape`, ' + + 'For `init`, `teach`, `shape`, ' + 'or wording that clearly maps to a from-scratch build/shape flow, load ' + - 'reference/init.md, complete its human or structured simulated-user interview, and write PRODUCT.md plus the ' + - 'user-chosen seed DESIGN.md before building. If no answer mechanism truly exists, init may infer only from the ' + - 'explicit brief, label its assumptions, and still write both files. For any other ' + + 'reference/init.md, complete its human or structured simulated-user interview, and write PRODUCT.md before ' + + 'designing. If no answer mechanism truly exists, init may infer only from the explicit brief and must label its ' + + 'assumptions. It never writes DESIGN.md. For any other ' + '(scoped) command against existing code, proceed using the code as ' + `context and offer \`${IMPECCABLE_COMMAND} init\` as a suggestion (do not block).`, - 'IDENTITY_INIT_REQUIRED: No committed product or visual world was found. New builds and redesigns ' + - 'must finish reference/init.md before reference/new-work.md develops the task-specific surface concept. Scoped ' + + 'PRODUCT_INIT_REQUIRED: No product context or visual authority was found. New builds and redesigns ' + + 'must finish reference/init.md for PRODUCT.md, then reference/new-work.md establishes the world and surface. Scoped ' + 'fixes to existing code do not need the new-surface flow.', ]; + appendSurfaceBriefContext(parts, ctx); parts.push(buildResolvedContextDirective(ctx, cliOptions, { targetExists })); if (shouldWarnMissingTarget(ctx, targetProvided, targetExists)) { parts.push(buildMissingTargetDirective()); @@ -1027,30 +1062,28 @@ async function cli() { if (ctx.hasDesign) { parts.push(`# DESIGN.md\n\n${ctx.design.trim()}`); } + appendSurfaceBriefContext(parts, ctx); parts.push(buildResolvedContextDirective(ctx, cliOptions, { targetExists })); if (shouldWarnMissingTarget(ctx, targetProvided, targetExists)) { parts.push(buildMissingTargetDirective()); } if (!ctx.hasDesign) { parts.push(ctx.hasVisualImplementation - ? 'BUILD_DESIGN_DOCUMENT_REQUIRED: PRODUCT.md exists and DESIGN.md is missing, but code contains incumbent visual decisions. ' + - 'Before `craft` or `shape`, load reference/init.md Step 5. For extension, document CSS, tokens, components, and ' + - 'assets as the incumbent world. For redesign/rebrand, replace the visual world with the user and treat the old ' + - 'look only as evidence and anti-reference. Narrow refinement commands may proceed using the implementation directly.' - : 'IDENTITY_INIT_REQUIRED: PRODUCT.md exists but no DESIGN.md or incumbent visual implementation was found. ' + - 'A new build or redesign must complete reference/init.md Step 5 with the human or structured simulated ' + - 'user before reference/new-work.md develops the task-specific surface concept. Scoped fixes to existing code do not need it.'); + ? 'INCUMBENT_WORLD_UNDOCUMENTED: PRODUCT.md exists and DESIGN.md is missing, but code contains incumbent visual decisions. ' + + 'For shape or a new-surface/redesign request, load reference/new-work.md: an extension documents and preserves the code-defined world; ' + + 'a redesign replaces it with the user and uses the old look only as evidence and anti-reference. Narrow refinement ' + + 'commands may proceed using the implementation directly.' + : 'WORLD_DISCOVERY_REQUIRED: PRODUCT.md exists but no DESIGN.md or incumbent visual implementation was found. ' + + 'For a new build or redesign, load reference/new-work.md and establish the visual world with the human or structured ' + + 'simulated user before developing the task concept. Scoped fixes to existing code do not need this flow.'); } - const platform = extractPlatform(ctx.product); - const nativeRefs = - platform === 'adaptive' ? ['ios', 'android'] : platform === 'ios' || platform === 'android' ? [platform] : []; - if (nativeRefs.length) { - const refList = nativeRefs.map(p => `\`reference/${p}.md\``).join(' and '); - const label = platform === 'adaptive' ? '`adaptive` (both iOS and Android)' : `\`${platform}\``; + const platformReferences = loadNativePlatformReferences(ctx.platform); + for (const reference of platformReferences) { parts.push( - `NEXT STEP: This project targets ${label}. Also read ${refList} for native conventions, in addition to SKILL.md's mode guidance.`, + `# NATIVE PLATFORM REFERENCE: ${reference.name.toUpperCase()} (reference/${reference.name}.md)\n\n${reference.content.trim()}`, ); - } else if (!platform) { + } + if (!ctx.platform) { // A `## Platform` section that names something we don't recognize (a // toolchain like `flutter`, a typo) would otherwise silently fall back to // web — the wrong default exactly when the user tried to say "native". @@ -1087,10 +1120,29 @@ function buildResolvedContextDirective(ctx, options, { targetExists = null } = { repoRoot: ctx.repoRoot, productPath: ctx.productPath, designPath: ctx.designPath, + surfaceBriefPath: ctx.surfaceBriefPath, + surfaceBriefReason: ctx.surfaceBriefReason, + surfaceBriefCandidates: ctx.surfaceBriefCandidates, hasVisualImplementation: ctx.hasVisualImplementation, + platform: ctx.platform, }, null, 2)}`; } +function appendSurfaceBriefContext(parts, ctx) { + if (ctx.hasSurfaceBrief && ctx.surfaceBrief) { + parts.push(`# SURFACE BRIEF (${ctx.surfaceBriefPath})\n\n${ctx.surfaceBrief.trim()}`); + return; + } + if (!ctx.surfaceBriefCandidates?.length) return; + const helper = path.join(path.dirname(fileURLToPath(import.meta.url)), 'surface-brief.mjs'); + parts.push( + 'SURFACE_CONTEXT_AVAILABLE: Persisted surface briefs exist, but none was selected unambiguously for this invocation. ' + + 'Resolve the requested surface to its concrete primary or related source path, then run ' + + `\`node ${helper} read \` once before changing that surface. Candidates:\n` + + JSON.stringify(ctx.surfaceBriefCandidates, null, 2), + ); +} + function shouldWarnMissingTarget(ctx, targetProvided, targetExists = null) { if (ctx.isMonorepo && targetProvided && targetExists === false) return true; return !!( diff --git a/skill/scripts/critique-storage.mjs b/skill/scripts/critique-storage.mjs index 6b4d225cf..38674285a 100644 --- a/skill/scripts/critique-storage.mjs +++ b/skill/scripts/critique-storage.mjs @@ -29,8 +29,9 @@ import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath, pathToFileURL } from 'node:url'; import { getCritiqueDir } from './lib/impeccable-paths.mjs'; +import { slugFromTarget } from './lib/target-slug.mjs'; -const SLUG_MAX = 50; +export { slugFromTarget } from './lib/target-slug.mjs'; /** * Mechanically derive a slug from a resolved target. Returns null if the @@ -40,46 +41,6 @@ const SLUG_MAX = 50; * concrete artifact before calling this — we never slug a natural-language * phrase. */ -export function slugFromTarget(resolved, { cwd = process.cwd() } = {}) { - if (!resolved || typeof resolved !== 'string') return null; - const trimmed = resolved.trim(); - if (!trimmed) return null; - - // URL - if (/^https?:\/\//i.test(trimmed)) { - let url; - try { url = new URL(trimmed); } catch { return null; } - const hostPath = `${url.hostname}${url.pathname}`; - return kebab(hostPath); - } - - // File path. Make it project-relative so two devs critiquing the same - // checkout get the same slug regardless of where their repo is cloned. - const abs = path.isAbsolute(trimmed) ? trimmed : path.resolve(cwd, trimmed); - let rel = path.relative(cwd, abs); - // If the target is outside cwd, fall back to the basename so we still - // produce a stable slug (vs the absolute path, which would include - // home dirs / usernames). - if (rel.startsWith('..') || path.isAbsolute(rel)) { - rel = path.basename(abs); - } - if (!rel || rel === '.' || rel === '') return null; - return kebab(rel); -} - -function kebab(s) { - const slug = s - .toLowerCase() - .replace(/[/\\.]+/g, '-') - .replace(/[^a-z0-9-]+/g, '-') - .replace(/-+/g, '-') - .replace(/^-|-$/g, ''); - if (!slug) return null; - // Cap from the tail — the tail (filename) is more identifying than the - // top-level directory. - return slug.length <= SLUG_MAX ? slug : slug.slice(slug.length - SLUG_MAX).replace(/^-/, ''); -} - /** * Filename-safe UTC ISO timestamp: hyphens for separators, trailing Z. * Plain colons aren't allowed on Windows filesystems. diff --git a/skill/scripts/lib/surface-briefs.mjs b/skill/scripts/lib/surface-briefs.mjs new file mode 100644 index 000000000..f83416f69 --- /dev/null +++ b/skill/scripts/lib/surface-briefs.mjs @@ -0,0 +1,151 @@ +import fs from 'node:fs'; +import path from 'node:path'; +import { slugFromTarget } from './target-slug.mjs'; + +export const SURFACE_BRIEF_VERSION = 1; + +export function getSurfaceBriefDir(projectRoot) { + return path.join(projectRoot, '.impeccable', 'surfaces'); +} + +export function normalizeSurfaceTarget(target, { projectRoot = process.cwd() } = {}) { + if (!target || typeof target !== 'string' || !target.trim()) return null; + const trimmed = target.trim(); + if (/^https?:\/\//i.test(trimmed)) { + try { + const url = new URL(trimmed); + url.hash = ''; + url.search = ''; + return url.toString().replace(/\/$/, '') || url.origin; + } catch { + return null; + } + } + if (/^route:/i.test(trimmed)) { + const route = trimmed.slice(trimmed.indexOf(':') + 1).trim(); + if (!route.startsWith('/') || route.includes('..')) return null; + const normalizedRoute = route.split(/[?#]/, 1)[0].replace(/\/{2,}/g, '/').replace(/\/$/, '') || '/'; + return `route:${normalizedRoute}`; + } + if (trimmed === '/') return 'route:/'; + if (trimmed.startsWith('/')) { + const absolute = path.resolve(trimmed); + const relativeToProject = path.relative(projectRoot, absolute); + const isProjectFile = relativeToProject && !relativeToProject.startsWith('..') && !path.isAbsolute(relativeToProject); + if (!isProjectFile && !fs.existsSync(absolute) && !trimmed.includes('..')) { + const normalizedRoute = trimmed.split(/[?#]/, 1)[0].replace(/\/{2,}/g, '/').replace(/\/$/, '') || '/'; + return `route:${normalizedRoute}`; + } + } + const abs = path.isAbsolute(trimmed) ? trimmed : path.resolve(projectRoot, trimmed); + const rel = path.relative(projectRoot, abs); + if (!rel || rel === '.' || rel.startsWith('..') || path.isAbsolute(rel)) return null; + return rel.split(path.sep).join('/'); +} + +export function surfaceBriefPathForTarget(target, { projectRoot = process.cwd() } = {}) { + const normalized = normalizeSurfaceTarget(target, { projectRoot }); + if (!normalized) return null; + const slugInput = normalized.startsWith('route:') ? `route${normalized.slice('route:'.length)}` : normalized; + const slug = slugFromTarget(slugInput, { cwd: projectRoot }); + return slug ? path.join(getSurfaceBriefDir(projectRoot), `${slug}.md`) : null; +} + +export function parseSurfaceBrief(text, filePath = null) { + const match = String(text || '').match(/^---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/); + const meta = {}; + if (match) { + for (const line of match[1].split(/\r?\n/)) { + const colon = line.indexOf(':'); + if (colon < 0) continue; + const key = line.slice(0, colon).trim(); + const raw = line.slice(colon + 1).trim(); + if (!key) continue; + if (/^(?:\[|\{|\")/.test(raw) || /^(?:true|false|null|-?\d+(?:\.\d+)?)$/.test(raw)) { + try { meta[key] = JSON.parse(raw); continue; } catch { /* keep string */ } + } + meta[key] = raw.replace(/^['"]|['"]$/g, ''); + } + } + const primaryTarget = typeof meta.primary_target === 'string' ? meta.primary_target : null; + const relatedTargets = Array.isArray(meta.related_targets) + ? meta.related_targets.filter((value) => typeof value === 'string') + : []; + return { + path: filePath, + text: String(text || ''), + body: match ? String(text || '').slice(match[0].length).trim() : String(text || '').trim(), + meta, + slug: typeof meta.slug === 'string' ? meta.slug : filePath ? path.basename(filePath, '.md') : null, + primaryTarget, + relatedTargets, + targets: [primaryTarget, ...relatedTargets].filter(Boolean), + }; +} + +export function listSurfaceBriefs(projectRoot = process.cwd()) { + const dir = getSurfaceBriefDir(projectRoot); + let names; + try { + names = fs.readdirSync(dir).filter((name) => name.endsWith('.md')).sort(); + } catch { + return []; + } + return names.flatMap((name) => { + const filePath = path.join(dir, name); + try { + return [parseSurfaceBrief(fs.readFileSync(filePath, 'utf-8'), filePath)]; + } catch { + return []; + } + }); +} + +export function resolveSurfaceBrief(projectRoot = process.cwd(), target = null) { + const briefs = listSurfaceBriefs(projectRoot); + if (!target) { + return { + brief: briefs.length === 1 ? briefs[0] : null, + candidates: briefs, + reason: briefs.length === 1 ? 'only-brief' : briefs.length > 1 ? 'ambiguous' : 'none', + }; + } + + const normalized = normalizeSurfaceTarget(target, { projectRoot }); + if (!normalized) return { brief: null, candidates: briefs, reason: 'invalid-target' }; + const exactPath = surfaceBriefPathForTarget(normalized, { projectRoot }); + const exact = briefs.find((brief) => brief.path === exactPath && (!brief.targets.length || brief.targets.includes(normalized))); + if (exact) return { brief: exact, candidates: briefs, reason: 'slug' }; + const mapped = briefs.filter((brief) => brief.targets.includes(normalized)); + return { + brief: mapped.length === 1 ? mapped[0] : null, + candidates: mapped.length > 1 ? mapped : briefs, + reason: mapped.length === 1 ? 'mapping' : mapped.length > 1 ? 'ambiguous-target' : 'not-found', + }; +} + +export function writeSurfaceBrief({ + projectRoot = process.cwd(), + primaryTarget, + relatedTargets = [], + body, +}) { + const normalizedPrimary = normalizeSurfaceTarget(primaryTarget, { projectRoot }); + if (!normalizedPrimary) throw new Error('surface brief requires a concrete project-relative primary target or URL'); + const normalizedRelated = [...new Set(relatedTargets + .map((target) => normalizeSurfaceTarget(target, { projectRoot })) + .filter((target) => target && target !== normalizedPrimary))]; + const slug = slugFromTarget(normalizedPrimary, { cwd: projectRoot }); + const filePath = surfaceBriefPathForTarget(normalizedPrimary, { projectRoot }); + fs.mkdirSync(path.dirname(filePath), { recursive: true }); + const frontmatter = [ + '---', + `version: ${SURFACE_BRIEF_VERSION}`, + `slug: ${JSON.stringify(slug)}`, + `primary_target: ${JSON.stringify(normalizedPrimary)}`, + `related_targets: ${JSON.stringify(normalizedRelated)}`, + '---', + ].join('\n'); + fs.writeFileSync(filePath, `${frontmatter}\n\n${String(body || '').trim()}\n`, 'utf-8'); + return filePath; +} diff --git a/skill/scripts/lib/target-slug.mjs b/skill/scripts/lib/target-slug.mjs new file mode 100644 index 000000000..025915ad5 --- /dev/null +++ b/skill/scripts/lib/target-slug.mjs @@ -0,0 +1,33 @@ +import path from 'node:path'; + +const SLUG_MAX = 50; + +/** Derive one clone-stable slug from a concrete file path or URL. */ +export function slugFromTarget(resolved, { cwd = process.cwd() } = {}) { + if (!resolved || typeof resolved !== 'string') return null; + const trimmed = resolved.trim(); + if (!trimmed) return null; + + if (/^https?:\/\//i.test(trimmed)) { + let url; + try { url = new URL(trimmed); } catch { return null; } + return kebab(`${url.hostname}${url.pathname}`); + } + + const abs = path.isAbsolute(trimmed) ? trimmed : path.resolve(cwd, trimmed); + let rel = path.relative(cwd, abs); + if (rel.startsWith('..') || path.isAbsolute(rel)) rel = path.basename(abs); + if (!rel || rel === '.') return null; + return kebab(rel); +} + +export function kebab(value) { + const slug = String(value || '') + .toLowerCase() + .replace(/[/\\.]+/g, '-') + .replace(/[^a-z0-9-]+/g, '-') + .replace(/-+/g, '-') + .replace(/^-|-$/g, ''); + if (!slug) return null; + return slug.length <= SLUG_MAX ? slug : slug.slice(slug.length - SLUG_MAX).replace(/^-/, ''); +} diff --git a/skill/scripts/surface-brief.mjs b/skill/scripts/surface-brief.mjs new file mode 100644 index 000000000..723f7c1b4 --- /dev/null +++ b/skill/scripts/surface-brief.mjs @@ -0,0 +1,74 @@ +#!/usr/bin/env node +import fs from 'node:fs'; +import path from 'node:path'; +import { fileURLToPath, pathToFileURL } from 'node:url'; +import { resolveProjectRoot } from './context.mjs'; +import { + listSurfaceBriefs, + resolveSurfaceBrief, + surfaceBriefPathForTarget, + writeSurfaceBrief, +} from './lib/surface-briefs.mjs'; + +function summary(brief, projectRoot) { + return { + slug: brief.slug, + path: path.relative(projectRoot, brief.path).split(path.sep).join('/'), + primaryTarget: brief.primaryTarget, + relatedTargets: brief.relatedTargets, + }; +} + +function main(argv) { + const [command, target, bodyFile, ...relatedTargets] = argv; + const projectRoot = resolveProjectRoot(process.cwd(), target ? { targetPath: target } : {}); + if (command === 'path') { + const filePath = surfaceBriefPathForTarget(target, { projectRoot }); + if (!filePath) throw new Error('surface brief path requires a concrete target'); + process.stdout.write(`${path.relative(process.cwd(), filePath) || filePath}\n`); + return; + } + if (command === 'list') { + process.stdout.write(`${JSON.stringify(listSurfaceBriefs(projectRoot).map((brief) => summary(brief, projectRoot)), null, 2)}\n`); + return; + } + if (command === 'read') { + const result = resolveSurfaceBrief(projectRoot, target || null); + if (result.brief) { + process.stdout.write(result.brief.text); + return; + } + if (result.candidates.length) process.stderr.write(`${JSON.stringify(result.candidates.map((brief) => summary(brief, projectRoot)), null, 2)}\n`); + process.exit(2); + } + if (command === 'write') { + if (!target || !bodyFile) throw new Error('usage: surface-brief.mjs write '); + const filePath = writeSurfaceBrief({ + projectRoot, + primaryTarget: target, + relatedTargets, + body: fs.readFileSync(bodyFile, 'utf-8'), + }); + process.stdout.write(`${path.relative(process.cwd(), filePath) || filePath}\n`); + return; + } + throw new Error('usage: surface-brief.mjs [target] [body-file] [related-target ...]'); +} + +function isMainModule() { + if (!process.argv[1]) return false; + try { + return fs.realpathSync(fileURLToPath(import.meta.url)) === fs.realpathSync(process.argv[1]); + } catch { + return import.meta.url === pathToFileURL(process.argv[1]).href; + } +} + +if (isMainModule()) { + try { + main(process.argv.slice(2)); + } catch (error) { + process.stderr.write(`${error?.message || error}\n`); + process.exit(1); + } +} diff --git a/tests/concept-seed.test.mjs b/tests/concept-seed.test.mjs new file mode 100644 index 000000000..6d1779984 --- /dev/null +++ b/tests/concept-seed.test.mjs @@ -0,0 +1,36 @@ +import { describe, it } from 'node:test'; +import assert from 'node:assert/strict'; +import { spawnSync } from 'node:child_process'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..'); +const SCRIPT = path.join(ROOT, 'skill', 'scripts', 'concept-seed.mjs'); + +function run(scope) { + return spawnSync(process.execPath, [SCRIPT, '--scope', scope, '--from', 'stable-test'], { + cwd: ROOT, + encoding: 'utf-8', + }); +} + +describe('concept seed scopes', () => { + it('keeps world and surface rolls reproducible but independent', () => { + const worldA = run('world'); + const worldB = run('world'); + const surface = run('surface'); + assert.equal(worldA.status, 0); + assert.equal(worldA.stdout, worldB.stdout); + assert.notEqual(worldA.stdout, surface.stdout); + assert.match(worldA.stdout, /WORLD CONCEPT SEED/); + assert.match(worldA.stdout, /cross-surface system breadth/); + assert.match(surface.stdout, /SURFACE CONCEPT SEED/); + assert.match(surface.stdout, /committed visual identity/); + }); + + it('rejects unknown scopes', () => { + const result = run('unknown'); + assert.notEqual(result.status, 0); + assert.match(result.stderr, /world or surface/); + }); +}); diff --git a/tests/context.test.mjs b/tests/context.test.mjs index 81b576528..ac4949b32 100644 --- a/tests/context.test.mjs +++ b/tests/context.test.mjs @@ -787,7 +787,7 @@ describe('context.mjs CLI', () => { assert.match(res.stdout, /^NO_PRODUCT_MD:/); assert.match(res.stdout, /reference\/init\.md/); assert.match(res.stdout, /structured simulated-user interview/); - assert.match(res.stdout, /IDENTITY_INIT_REQUIRED:/); + assert.match(res.stdout, /PRODUCT_INIT_REQUIRED:/); }); it('prints a PRODUCT.md markdown block when only PRODUCT.md exists', async () => { @@ -798,10 +798,10 @@ describe('context.mjs CLI', () => { assert.match(res.stdout, /^# PRODUCT\.md/); assert.match(res.stdout, /# Acme/); assert.equal(res.stdout.includes('# DESIGN.md'), false); - // Directives are appended after `---`; with no DESIGN.md the - // init identity gate directive fires before the task concept flow. + // Directives are appended after `---`; missing visual authority now + // routes to new-work rather than back through product init. assert.match(res.stdout, /\n---\n\n/); - assert.match(res.stdout, /IDENTITY_INIT_REQUIRED: PRODUCT\.md exists but no DESIGN\.md/); + assert.match(res.stdout, /WORLD_DISCOVERY_REQUIRED: PRODUCT\.md exists but no DESIGN\.md/); }); it('treats tokenized code as incumbent design authority when DESIGN.md is missing', () => { @@ -810,9 +810,9 @@ describe('context.mjs CLI', () => { assert.equal(hasVisualImplementation(scratch), true); const res = spawnSync(process.execPath, [SCRIPT_PATH], { cwd: scratch, encoding: 'utf8', env: { ...process.env, IMPECCABLE_NO_UPDATE_CHECK: '1' } }); assert.equal(res.status, 0); - assert.match(res.stdout, /BUILD_DESIGN_DOCUMENT_REQUIRED:/); - assert.match(res.stdout, /Before `craft` or `shape`, load reference\/init\.md Step 5/); - assert.doesNotMatch(res.stdout, /IDENTITY_INIT_REQUIRED:/); + assert.match(res.stdout, /INCUMBENT_WORLD_UNDOCUMENTED:/); + assert.match(res.stdout, /For shape or a new-surface\/redesign request, load reference\/new-work\.md/); + assert.doesNotMatch(res.stdout, /WORLD_DISCOVERY_REQUIRED:/); assert.match(res.stdout, /"hasVisualImplementation": true/); }); @@ -834,7 +834,7 @@ describe('context.mjs CLI', () => { assert.equal(hasVisualImplementation(scratch), true); }); - it('routes craft through init but keeps narrow refinements non-blocking when visual code exists without PRODUCT.md', () => { + it('routes new surfaces through init but keeps narrow refinements non-blocking when visual code exists without PRODUCT.md', () => { write('styles/theme.css', ':root { --brand: #124; --surface: #fff; --text: #111; }\nmain { color: var(--text); background-color: var(--surface); border-color: var(--brand); }\n'); const res = spawnSync(process.execPath, [SCRIPT_PATH], { cwd: scratch, encoding: 'utf8', env: { ...process.env, IMPECCABLE_NO_UPDATE_CHECK: '1' } }); assert.equal(res.status, 0); @@ -843,9 +843,9 @@ describe('context.mjs CLI', () => { assert.match(res.stdout, /BUILD_INIT_REQUIRED:/); assert.match(res.stdout, /SCOPED_EXISTING_ALLOWED:/); assert.match(res.stdout, /proceed without blocking/); - assert.match(res.stdout, /For `init`, `teach`, `craft`, or `shape`/); + assert.match(res.stdout, /For `init`, `teach`, `shape`, or any request to create a new surface/); assert.match(res.stdout, /For a redesign\/rebrand.*old look only as evidence and anti-reference/s); - assert.doesNotMatch(res.stdout, /IDENTITY_INIT_REQUIRED:/); + assert.doesNotMatch(res.stdout, /WORLD_DISCOVERY_REQUIRED:/); }); it('concatenates PRODUCT.md and DESIGN.md with a --- separator', async () => { @@ -857,7 +857,98 @@ describe('context.mjs CLI', () => { assert.match(res.stdout, /^# PRODUCT\.md/); assert.match(res.stdout, /\n---\n/); assert.match(res.stdout, /# DESIGN\.md\n\n# Acme design/); - assert.equal(res.stdout.includes('IDENTITY_INIT_REQUIRED:'), false); + assert.equal(res.stdout.includes('WORLD_DISCOVERY_REQUIRED:'), false); + }); + + it('loads the only persisted surface brief as current task context', () => { + write('PRODUCT.md', '# Acme product\n'); + write('DESIGN.md', '# Acme design\n'); + write('.impeccable/surfaces/src-pages-pricing-astro.md', `--- +version: 1 +slug: "src-pages-pricing-astro" +primary_target: "src/pages/pricing.astro" +related_targets: [] +--- + +# Surface brief: Pricing + +## Product strategy +Make plan tradeoffs legible before asking for a trial. +`); + const res = spawnSync(process.execPath, [SCRIPT_PATH], { + cwd: scratch, + encoding: 'utf8', + env: { ...process.env, IMPECCABLE_NO_UPDATE_CHECK: '1' }, + }); + assert.equal(res.status, 0); + assert.match(res.stdout, /# SURFACE BRIEF \(\.impeccable\/surfaces\/src-pages-pricing-astro\.md\)/); + assert.match(res.stdout, /Make plan tradeoffs legible/); + assert.match(res.stdout, /"surfaceBriefReason": "only-brief"/); + }); + + it('selects a surface brief by an exact primary or related target', () => { + write('PRODUCT.md', '# Acme product\n'); + write('DESIGN.md', '# Acme design\n'); + write('src/pages/pricing.astro', '
    Pricing
    \n'); + write('.impeccable/surfaces/src-pages-pricing-astro.md', `--- +version: 1 +slug: "src-pages-pricing-astro" +primary_target: "src/pages/pricing.astro" +related_targets: ["src/components/PricingTable.astro"] +--- + +# Surface brief: Pricing + +PRICING_STRATEGY_SENTINEL +`); + write('.impeccable/surfaces/src-pages-home-astro.md', `--- +version: 1 +slug: "src-pages-home-astro" +primary_target: "src/pages/home.astro" +related_targets: [] +--- + +# Surface brief: Home + +HOME_STRATEGY_SENTINEL +`); + const res = spawnSync(process.execPath, [SCRIPT_PATH, '--target', 'src/pages/pricing.astro'], { + cwd: scratch, + encoding: 'utf8', + env: { ...process.env, IMPECCABLE_NO_UPDATE_CHECK: '1' }, + }); + assert.equal(res.status, 0); + assert.match(res.stdout, /PRICING_STRATEGY_SENTINEL/); + assert.doesNotMatch(res.stdout, /HOME_STRATEGY_SENTINEL/); + }); + + it('lists candidates instead of guessing when several surface briefs exist', () => { + write('PRODUCT.md', '# Acme product\n'); + write('DESIGN.md', '# Acme design\n'); + for (const [slug, target] of [ + ['src-pages-pricing-astro', 'src/pages/pricing.astro'], + ['src-pages-home-astro', 'src/pages/home.astro'], + ]) { + write(`.impeccable/surfaces/${slug}.md`, `--- +version: 1 +slug: "${slug}" +primary_target: "${target}" +related_targets: [] +--- + +# Surface brief: ${slug} +`); + } + const res = spawnSync(process.execPath, [SCRIPT_PATH], { + cwd: scratch, + encoding: 'utf8', + env: { ...process.env, IMPECCABLE_NO_UPDATE_CHECK: '1' }, + }); + assert.equal(res.status, 0); + assert.match(res.stdout, /SURFACE_CONTEXT_AVAILABLE:/); + assert.match(res.stdout, /src\/pages\/pricing\.astro/); + assert.match(res.stdout, /src\/pages\/home\.astro/); + assert.doesNotMatch(res.stdout, /# SURFACE BRIEF \(/); }); it('reads from a fallback dir when cwd is clean', async () => { @@ -875,25 +966,26 @@ describe('context.mjs CLI', () => { const res = spawnSync(process.execPath, [SCRIPT_PATH], { cwd: scratch, encoding: 'utf8', env: { ...process.env, IMPECCABLE_NO_UPDATE_CHECK: '1' } }); assert.equal(res.status, 0); assert.doesNotMatch(res.stdout, /REGISTER:/); - assert.match(res.stdout, /IDENTITY_INIT_REQUIRED: PRODUCT\.md exists but no DESIGN\.md/); + assert.match(res.stdout, /WORLD_DISCOVERY_REQUIRED: PRODUCT\.md exists but no DESIGN\.md/); }); - it('appends a native platform directive for an ios project', async () => { + it('loads the native platform reference for an ios project', async () => { write('PRODUCT.md', '# Acme\n\n## Platform\n\nios\n'); const { spawnSync } = await import('node:child_process'); const res = spawnSync(process.execPath, [SCRIPT_PATH], { cwd: scratch, encoding: 'utf8', env: { ...process.env, IMPECCABLE_NO_UPDATE_CHECK: '1' } }); assert.equal(res.status, 0); - assert.match(res.stdout, /This project targets `ios`\./); - assert.match(res.stdout, /read `reference\/ios\.md`/); + assert.match(res.stdout, /# NATIVE PLATFORM REFERENCE: IOS \(reference\/ios\.md\)/); + assert.match(res.stdout, /Apple Human Interface Guidelines|iOS/i); + assert.doesNotMatch(res.stdout, /NEXT STEP:.*reference\/ios\.md/); }); - it('appends both native directives for an adaptive project', async () => { + it('loads both native platform references for an adaptive project', async () => { write('PRODUCT.md', '# Acme\n\n## Platform\n\nadaptive\n'); const { spawnSync } = await import('node:child_process'); const res = spawnSync(process.execPath, [SCRIPT_PATH], { cwd: scratch, encoding: 'utf8', env: { ...process.env, IMPECCABLE_NO_UPDATE_CHECK: '1' } }); assert.equal(res.status, 0); - assert.match(res.stdout, /targets `adaptive` \(both iOS and Android\)/); - assert.match(res.stdout, /reference\/ios\.md` and `reference\/android\.md`/); + assert.match(res.stdout, /# NATIVE PLATFORM REFERENCE: IOS \(reference\/ios\.md\)/); + assert.match(res.stdout, /# NATIVE PLATFORM REFERENCE: ANDROID \(reference\/android\.md\)/); }); it('appends no native platform directive for a web project', async () => { @@ -905,13 +997,13 @@ describe('context.mjs CLI', () => { assert.equal(res.stdout.includes('reference/ios.md'), false); }); - it('appends a native platform directive for an android project', async () => { + it('loads the native platform reference for an android project', async () => { write('PRODUCT.md', '# Acme\n\n## Platform\n\nandroid\n'); const { spawnSync } = await import('node:child_process'); const res = spawnSync(process.execPath, [SCRIPT_PATH], { cwd: scratch, encoding: 'utf8', env: { ...process.env, IMPECCABLE_NO_UPDATE_CHECK: '1' } }); assert.equal(res.status, 0); - assert.match(res.stdout, /This project targets `android`\./); - assert.match(res.stdout, /read `reference\/android\.md`/); + assert.match(res.stdout, /# NATIVE PLATFORM REFERENCE: ANDROID \(reference\/android\.md\)/); + assert.match(res.stdout, /Material Design|Android/i); }); it('warns on an unrecognized platform value instead of silently defaulting to web', async () => { @@ -957,6 +1049,12 @@ describe('context.mjs update check', () => { const providerSrc = path.join(path.dirname(SCRIPT_PATH), 'lib', 'provider.mjs'); const providerDest = path.join(path.dirname(skillScript), 'lib', 'provider.mjs'); fs.copyFileSync(providerSrc, providerDest); + for (const helper of ['surface-briefs.mjs', 'target-slug.mjs']) { + fs.copyFileSync( + path.join(path.dirname(SCRIPT_PATH), 'lib', helper), + path.join(path.dirname(skillScript), 'lib', helper), + ); + } fs.writeFileSync( path.join(scratch, 'skill', 'SKILL.md'), `---\nname: impeccable\nversion: ${LOCAL_VERSION}\n---\n\nbody\n`, diff --git a/tests/detect-antipatterns-fixtures.test.mjs b/tests/detect-antipatterns-fixtures.test.mjs index a6725ee3b..62c37f26f 100644 --- a/tests/detect-antipatterns-fixtures.test.mjs +++ b/tests/detect-antipatterns-fixtures.test.mjs @@ -406,15 +406,10 @@ describe('detectHtml — static HTML/CSS fixtures', () => { } }); - it('numbered-section-markers: visible sequence flags while script/style/svg internals pass', async () => { + it('numeric content is not classified without DOM context', async () => { const f = await detectHtml(path.join(FIXTURES, 'numbered-section-markers.html')); const numbered = f.filter(r => r.antipattern === 'numbered-section-markers'); - assert.equal( - numbered.length, - 1, - `expected one visible numbered-marker finding, got: ${numbered.map(r => r.snippet).join('; ')}` - ); - assert.match(numbered[0].snippet, /01, 02, 03/); + assert.equal(numbered.length, 0, 'raw numeric sequences must not masquerade as semantic section evidence'); }); it('numbered-section-labels: tiny repeated index labels flag, deliberate/list/card numbering passes', async () => { @@ -567,10 +562,6 @@ describe('detectHtml — hero-eyebrow-chip', () => { 'Span Eyebrow Above Hero', 'Pill Chip Above Hero', 'Already Uppercase Text', - // The rule no longer gates on heading font size (modern hero h1s - // use clamp() / vw / var() that static HTML/CSS cannot resolve), and the - // eyebrow text ceiling moved 30 → 60 chars. Both shapes now flag. - 'Body-Sized Heading Below Eyebrow', 'Long Uppercase Sentence Above Hero', ]; const SHOULD_PASS = [ @@ -578,6 +569,8 @@ describe('detectHtml — hero-eyebrow-chip', () => { 'Uppercase Caption Far From Hero', 'Hero With No Eyebrow', 'Heading Above Heading', + 'Body-Sized Heading Below Eyebrow', + 'Application Panel Heading', ]; it('hero-eyebrow-chip: flags only the should-flag column', async () => { diff --git a/tests/detect-antipatterns.test.js b/tests/detect-antipatterns.test.js index 23ea9d523..c0d032a46 100644 --- a/tests/detect-antipatterns.test.js +++ b/tests/detect-antipatterns.test.js @@ -326,18 +326,18 @@ describe('partials skip page-level checks', () => { }); }); -describe('detectText — numbered section markers', () => { - test('flags visible full-page numbered section labels', () => { +describe('detectText — numeric content', () => { + test('does not infer section scaffolding from raw numeric sequences', () => { const page = '' + - '
    01

    Strategy

    ' + - '
    02

    Prototype

    ' + - '
    03

    Launch

    ' + + '
    1. 01 — Glassline — 03:11
    2. ' + + '
    3. 02 — Tidal Memory — 04:08
    4. ' + + '
    5. 03 — Pale Signal — 05:02
    ' + ''; const f = detectText(page, 'test.html'); - expect(f.some(r => r.antipattern === 'numbered-section-markers')).toBe(true); + expect(f.some(r => r.antipattern === 'numbered-section-markers')).toBe(false); }); - test('does not run page-level numbered marker analysis on JS source with embedded HTML strings', () => { + test('does not infer section scaffolding from JS source with embedded numbers', () => { const source = ` const shell = 'Preview'; const palette = 'oklch(86% 0.07 84 / 0.08)'; @@ -1514,6 +1514,11 @@ describe('hero-eyebrow dash-prefix branch', () => { expect(checkHeroEyebrow({ ...base, siblingHasAccentDashPseudo: false })).toHaveLength(0); }); + test('compact application headings stay legal', () => { + expect(checkHeroEyebrow({ ...base, headingFontSize: 36, siblingHasAccentDashPseudo: true })).toHaveLength(0); + expect(checkHeroEyebrow({ ...base, headingInApplicationContext: true, siblingHasAccentDashPseudo: true })).toHaveLength(0); + }); + test('static engine resolves the dash through the cascade', async () => { await withStaticFixture({ 'index.html': `