diff --git a/AGENTS.md b/AGENTS.md index c77b196f..21c3bed5 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -1,6 +1,6 @@ # AGENTS.md — instructions for AI coding agents working in this repo -This repo is the source for the `visualize` agent skill. Most edits land under `visualize/` (the canonical mount); the mirror mounts under `skills/`, `hermes/design/`, and `pi/agent/skills/` are regenerated by `bin/sync-mounts.sh`, including the trimmed `skills/visualize-lite/` build (see the lite section in `bin/transform.mjs`). **Never edit the mirrors directly** — CI fails on drift. +This repo is the source for the `visualize` agent skill. Most edits land under `visualize/` (the canonical mount); the three mirror mounts under `skills/`, `hermes/design/`, and `pi/agent/skills/` are regenerated by `bin/sync-mounts.sh`. **Never edit the mirrors directly** — CI fails on drift. ## Editing workflow @@ -47,11 +47,10 @@ visualize/ ← canonical mount, the only directory humans ed └── scripts/ ← bash + Node helpers skills/visualize/ ← mirror — npm-skills (`npx skills add`) -skills/visualize-lite/ ← trimmed mirror — npm-skills `--skill visualize-lite` (design systems fetched on demand) hermes/design/visualize/ ← mirror — Hermes well-known pi/agent/skills/visualize/ ← mirror — Pi / OpenClaw fixtures/ ← 10 brand-profile worked examples (DESIGN.md + PRODUCT.md per fixture) -bin/sync-mounts.sh ← regenerate the mirrors (including the lite build) +bin/sync-mounts.sh ← regenerate the three mirrors .github/workflows/ci.yml ← mirror-check + JSON lint + shellcheck ``` diff --git a/README.md b/README.md index ff74760c..0dd2dc1f 100644 --- a/README.md +++ b/README.md @@ -14,14 +14,6 @@ npx skills add display-dev/visualize --skill visualize Works across Claude Code, Cursor, Codex, OpenCode, Hermes, and Pi. -### Visualize Lite - -```sh -npx skills add display-dev/visualize --skill visualize-lite -``` - -The same skill in about 130 files instead of about 800, for hosts that limit skill size or file count. It bundles the design-system index and the `clean` system. When you pick another design system, it downloads that system's `README.md`, `DESIGN.md`, and `tokens.css` from this repository at the matching release, so it needs network access to `raw.githubusercontent.com` for those. Commands are `/visualize-lite …`. `bin/sync-mounts.sh` generates it from `visualize/`; never edit `skills/visualize-lite/` directly. - ## Why visualize? Four problems with how agents publish HTML today: diff --git a/bin/transform.mjs b/bin/transform.mjs index ef8b8c42..7243da3d 100755 --- a/bin/transform.mjs +++ b/bin/transform.mjs @@ -77,83 +77,8 @@ const MIRRORS = [ { provider: 'skills', path: 'skills/visualize', displayName: 'npm skills' }, { provider: 'hermes', path: 'hermes/design/visualize', displayName: 'Hermes' }, { provider: 'pi', path: 'pi/agent/skills/visualize', displayName: 'Pi' }, - { provider: 'skills', path: 'skills/visualize-lite', displayName: 'npm skills (lite)', lite: true }, ]; -// ============================================================================= -// Lite build -// ============================================================================= -// -// `skills/visualize-lite/` is the same skill with a small file count, for hosts -// whose validators or size limits reject the full design-system catalog. It -// leaves out the design-system packages (except _template and clean) and the -// test files, writes design-systems/REMOTE.md with the full catalog's URL at -// the release tag, and adds one rule to SKILL.md telling the agent to fetch a -// missing package from there. Install: npx skills add display-dev/visualize -// --skill visualize-lite. - -const LITE_KEPT_DESIGN_SYSTEMS = new Set(['_template', 'clean']); -const LITE_EXCLUDED_FILES = new Set([ - 'scripts/image-test-fixtures.mjs', - 'scripts/pattern-fixture-smoke.mjs', -]); -const LITE_RULE_ANCHOR = 'The design systems under `design-systems/` are reference packages'; -const LITE_RULE = - 'This lite build bundles only `catalog-index.json`, `_template/`, and `clean/` from `design-systems/`. ' + - '`design-systems/REMOTE.md` holds the base URL of the full catalog at the same version. ' + - 'When a selected package folder is missing, fetch its `README.md`, `DESIGN.md`, and `tokens.css` from that base URL plus `/` ' + - "with `curl` or the host's web fetch, then use them as if they were local. " + - 'If a fetch fails, name the package that could not be loaded and continue with the next shortlisted reference or the fallback; never rebuild a package from memory.'; - -function liteExcludes(rel) { - const parts = rel.split(sep); - if (parts[0] === 'fixtures') return true; - if (parts[0] === 'scripts' && parts[1] === 'fixtures') return true; - if (parts[0] === 'scripts' && parts.length === 2 && parts[1].endsWith('.test.mjs')) return true; - if (LITE_EXCLUDED_FILES.has(parts.join('/'))) return true; - if (parts[0] === 'design-systems' && parts.length > 2 && !LITE_KEPT_DESIGN_SYSTEMS.has(parts[1])) return true; - return false; -} - -function canonicalVersion(canonicalSkillContent) { - const match = canonicalSkillContent.match(/^version:\s*([0-9]+\.[0-9]+\.[0-9]+)\s*$/m); - if (!match) throw new Error('Canonical SKILL.md is missing a semver version field'); - return match[1]; -} - -// Applied to canonical text before placeholder substitution. -function liteRewrite(rel, content) { - let out = content - .replaceAll('{{command_prefix}}visualize', '{{command_prefix}}visualize-lite') - .replaceAll('*/visualize/scripts/', '*/visualize-lite/scripts/'); - if (rel === 'SKILL.md') { - out = out.replace(/^name: visualize$/m, 'name: visualize-lite'); - if (!/^name: visualize-lite$/m.test(out)) throw new Error('lite: SKILL.md name field not rewritten'); - const lines = out.split('\n'); - const hits = lines.filter((line) => line.startsWith(LITE_RULE_ANCHOR)).length; - if (hits !== 1) throw new Error(`lite: SKILL.md must contain exactly one line starting "${LITE_RULE_ANCHOR}" (found ${hits})`); - const at = lines.findIndex((line) => line.startsWith(LITE_RULE_ANCHOR)); - lines.splice(at + 1, 0, '', LITE_RULE); - out = lines.join('\n'); - } - return out; -} - -function liteGeneratedFiles(version) { - const base = `https://raw.githubusercontent.com/display-dev/visualize/v${version}/skills/visualize/design-systems/`; - return new Map([[ - join('design-systems', 'REMOTE.md'), - Buffer.from( - '# Remote design-system catalog\n\n' + - 'This lite build of Visualize bundles `catalog-index.json`, `_template/`, and `clean/`.\n' + - 'Every other design system listed in `catalog-index.json` is at:\n\n' + - `${base}\n\n` + - 'Append `/README.md`, `/DESIGN.md`, or `/tokens.css`.\n', - 'utf8', - ), - ]]); -} - // Substitution is scoped to authoring formats — .md and .html. Everything // else (scripts, fonts, images) is byte-copied untouched. const TEXT_EXTENSIONS_FOR_SUBSTITUTION = new Set(['.md', '.html']); @@ -325,38 +250,16 @@ function assertArgumentHintFresh(canonicalContent) { * @returns {() => { displayName, mirrorPath, written, drift }} transform fn */ function createTransformer(config) { - const { provider, path: mirrorPath, displayName, outputRoot, checkMode, lite = false, version } = config; + const { provider, path: mirrorPath, displayName, outputRoot, checkMode } = config; const mirrorDir = join(outputRoot, mirrorPath); return function transform() { - const allCanonicalFiles = listFiles(CANONICAL); - const canonicalFiles = lite - ? new Set([...allCanonicalFiles].filter((rel) => !liteExcludes(rel))) - : allCanonicalFiles; - const generated = lite ? liteGeneratedFiles(version) : new Map(); + const canonicalFiles = listFiles(CANONICAL); const mirrorFiles = listFiles(mirrorDir); let drift = 0; let written = 0; - for (const [rel, buf] of generated) { - const dstPath = join(mirrorDir, rel); - if (checkMode) { - if (!existsSync(dstPath)) { - console.error(`missing in ${mirrorPath}: ${rel}`); - drift++; - } else if (!readFileSync(dstPath).equals(buf)) { - console.error(`drift in ${mirrorPath}: ${rel}`); - drift++; - } - } else { - mkdirSync(dirname(dstPath), { recursive: true }); - writeFileSync(dstPath, buf); - chmodSync(dstPath, 0o644); - written++; - } - } - for (const rel of canonicalFiles) { const srcPath = join(CANONICAL, rel); const dstPath = join(mirrorDir, rel); @@ -365,8 +268,7 @@ function createTransformer(config) { let resolved; if (shouldSubstitute(rel)) { - const text = lite ? liteRewrite(rel, srcBuf.toString('utf8')) : srcBuf.toString('utf8'); - const replaced = replacePlaceholders(text, provider); + const replaced = replacePlaceholders(srcBuf.toString('utf8'), provider); assertNoUnresolvedPlaceholders(replaced, `${mirrorPath}/${rel}`); resolved = Buffer.from(replaced, 'utf8'); } else { @@ -399,7 +301,7 @@ function createTransformer(config) { // Stale-file cleanup: files in mirror with no canonical counterpart. // Write mode deletes them; --check reports as drift. for (const rel of mirrorFiles) { - if (canonicalFiles.has(rel) || generated.has(rel)) continue; + if (canonicalFiles.has(rel)) continue; const dstPath = join(mirrorDir, rel); if (checkMode) { console.error(`stale in ${mirrorPath}: ${rel}`); @@ -463,7 +365,7 @@ function main() { let totalDrift = 0; for (const mirror of MIRRORS) { - const transform = createTransformer({ ...mirror, outputRoot, checkMode, version: canonicalVersion(canonicalSkillContent) }); + const transform = createTransformer({ ...mirror, outputRoot, checkMode }); const result = transform(); totalWritten += result.written; totalDrift += result.drift; diff --git a/skills/visualize-lite/AUTHORING.md b/skills/visualize-lite/AUTHORING.md deleted file mode 100644 index eb82710c..00000000 --- a/skills/visualize-lite/AUTHORING.md +++ /dev/null @@ -1,71 +0,0 @@ -# Visualize Authoring - -This document is for maintainers. It is not part of the normal creation, review, or polish loading graph. - -## Design principles - -These are decisions that shape every edit to `SKILL.md`, `reference/`, and `detect.mjs`. Reverse one only after considering the failure mode it prevents. - -### 1. Brand cues are hard invariants, not aspirational prose - -Named fonts, named palettes, named anti-references from the resolved brand profile (or from fallback project instructions) are enforceable invariants — checked in preflight and re-audited at pre-render time. Revise before render; do not surface a violation to the user. Soft prose like "follow the brand profile" loses every time to the training prior. - -### 2. Reflex-rejection lives at the picking step, not the refusal step - -The composition reflex check runs in preflight, before shape sketching. Its output stays internal — the shape gate carries forward only the chosen alternative, not the rejection process. Anti-reflex procedures upstream of generation catch reflexes not yet cataloged; post-build refusal rules force the model into the next-most-common reflex instead. Reference implementation: impeccable's 4-step font-selection procedure. - -### 3. Catalog access follows the task's scope - -`teach` and [System Explore](reference/explore-system.md) use the catalog and reference packages as calibration when deriving project identities. Catalog choices do not themselves establish project authority; adoption follows `teach`'s draft-and-confirm contract. - -Creation and revision can apply reference packages as artifact themes, and Artifact Explore can compare them. [SKILL.md's Artifact themes contract](SKILL.md#artifact-themes) owns project defaults and explicit artifact overrides. Browsing the catalog must not silently replace resolved project guidance or turn an artifact treatment into project authority. - -### 4. Required shape-gate fields are source-conditional - -Unconditional gate fields — pre-committed visible ingredients, primitive budgets, mandatory signature moves — induce format-induced collapse (Findings-EMNLP 2025: SFT 20.8% → DPO 10.8% diversity drop under structured-output requirements). Primitives follow sections that need them. Signature moves exist only when the source motivates one. Ingredients are derived from sections + primitives, not separately listed. - -### 5. Instructions-only — no exemplars, no project paths, no calibrated numbers - -The skill produces from-scratch using only prose instructions plus the brand profile chain. Pre-loaded exemplar images, project-specific shortIds or file paths, model-generated probability weights (`Font A 60% / Font B 25%`), and specific pixel values in prose (`32–40px padding`) all break the from-scratch premise. If a proposed change needs external state or per-project configuration, the change is wrong-shaped for this skill. - -### 6. Specificity, not novelty - -The *specificity and comprehension pass* assesses whether composition communicates the actual material. A familiar arrangement is acceptable when it serves the reader; structural change corrects a demonstrated deficiency rather than merely differing from a common layout. Cumulative denial across artifacts (avoiding a pattern because it was used before) confuses brand consistency with reflex — brand-consistent work *should* repeat. Only avoid reflexes that are wrong for the brand or source. - -### 7. Two-model design center - -The skill must work for both Claude and Codex (and future models with similar reflex shapes). Claude's reflex is precedent-inheritance — copying whatever sibling artifact exists. Codex's reflex is safe-median picks — cream paper, system-ui, soft shadows, eyebrow-chip mastheads. Procedures and invariants catch both. A proposed change that helps one model and hurts the other is wrong. - -## Reverse-with-caution - -Already tried and rejected — don't reintroduce without confronting the reason: - -- **Cream-paper composition stack as an absolute BAN (in `SKILL.md`).** Considered as BAN 9 (`cream-band background + system-ui + soft shadow on bordered cards`) and BAN 10 (`eyebrow chips + lowercase wordmark + 4-col mono meta strip`) to catch the Codex research-report reflex. Rejected: instance-shaped (model dodges by changing one detail), and absolute bans don't carve out for brands that legitimately live in those aesthetics. The failure is now caught upstream by principle 1 (brand-fidelity lock). -- **Multi-persona generate-then-synthesize at the brand-words step.** Considered for measured diversity gain (Cambridge Design Science multi-persona work). Rejected per principle 7: overlap-rejection competes with brand fidelity — three personas should agree on brand identity moves, not have those rejected as shared reflex. -- **LLM-judged "marker-density" check post-build.** Considered for catching reflex co-occurrence patterns. Rejected: false-positives on legitimate brand-aligned output that uses cataloged moves; false-negatives on unfamiliar combinations. `detect.mjs` stays deterministic regex-based. -- **Verbalized-sampling probability weights.** Considered (Zhang et al. 2025, measured 1.6–2.1× diversity gain). Rejected per principle 5: model-generated weights without calibration are theater. -- **Specific pixel numbers in prose (`32–40px padding`, type ratio `≥ 4x`).** Rejected per principle 5 — bakes one aesthetic into the skill and doesn't travel across registers. - -## Templates - -Templates live at `templates//template.md`. Each template directory contains exactly that one live authoring file. Required frontmatter is `name` and `description`; `shell` is optional and must resolve to `shells//README.md`. Do not add `category`, `patterns`, `optional_patterns`, or `fixtures` frontmatter. - -Template bodies describe selection and composition: Use when, Do not use when, Structure, Creation guidance, Hierarchy contract, Mobile contract, and Failure modes. Keep them compact and specific to reader outcomes. Mention pattern recipes in prose only when the template genuinely benefits from them. - -## Pattern Recipes - -Recipes live at `patterns/.md`. They are creation guidance for recurring rendered patterns, not importable components and not review rubrics. Required frontmatter is `name` and `description`; `variants` is optional. Minimal examples are allowed when they remove ambiguity, but they should not become a shared skin. - -## Shells - -Shells live at `shells//` and exist only for behavior-heavy mechanics: slide navigation, keyboard control, hash sync, scaling, print, or similar. A shell README documents content slots, behaviors, accessibility invariants, print/export expectations, and what an agent may customize. Runtime files are read only when the artifact needs to inline or modify mechanics. - -Artifact Explore follows the same single-file harness shape as a prototype exploration: review controls sit outside same-DOM candidate roots, each direction scopes its CSS to its own root, and width controls never impose a fixed document height. Temporary candidate drafts and screenshots are construction evidence, not fixtures or a publishable package. - -## Fixtures - -Fixtures live under `fixtures/` and are tooling assets. They are not authoring inputs and templates do not reference them. If `fixtures/manifest.json` lists a file, validators must be able to resolve it. Keep fixtures targeted to detector coverage, contrast checks, mobile overflow checks, shell smoke, or human visual inspection. - -## Fixture Styles - -Preview-only CSS belongs in `preview-kit/fixture-styles/` or inside the fixture itself. Do not recreate `components/` as a reusable component library. If a style is required for generated artifacts, it belongs in the artifact, not the preview kit. diff --git a/skills/visualize-lite/NOTICES.md b/skills/visualize-lite/NOTICES.md deleted file mode 100644 index e32d7ebc..00000000 --- a/skills/visualize-lite/NOTICES.md +++ /dev/null @@ -1,37 +0,0 @@ -# Design-system notices - -Design systems under `visualize/design-systems/-style/` are independent -reference implementations inspired by public brand surfaces. Repository-level -notices for vendored runtime bundles are collected here. - -## Brand reference - -Design systems with a `-style` suffix (`stripe-style`, `figma-style`, …) are -independent reference implementations inspired by the public visual language -of well-known brands. They are not affiliated with, endorsed by, or -representative of the named companies. All brand names and trademarks remain -the property of their respective owners. -## Vendored bundles (`scripts/vendor/`) - -The skill vendors a small set of npm-distributed libraries as single-file bundles so the detector and browser-contrast sidecar work after `npx skills add display-dev/visualize` with no `npm install` step. Per-file version, size, and sha256 are recorded in [`scripts/vendor/README.md`](scripts/vendor/README.md). - -- `node-html-parser` — MIT — https://github.com/taoqf/node-html-parser -- `culori` — MIT — https://github.com/Evercoder/culori -- `axe-core` — MPL-2.0 — https://github.com/dequelabs/axe-core -- `puppeteer-core` — Apache-2.0 — https://github.com/puppeteer/puppeteer -- `@puppeteer/browsers` — Apache-2.0 — https://github.com/puppeteer/puppeteer - -The combined `puppeteer.mjs` bundle inlines transitive code under MIT, ISC, and Apache-2.0 — representative packages include `debug`, `ws`, `chromium-bidi`, `tar-fs`, `tar-stream`, `yargs`, `progress`, `streamx`, `b4a`, `fast-fifo`, `cliui`. Legal comments and SPDX identifiers from each upstream package are preserved inline via `esbuild --legal-comments=inline`; consult the bundle directly for the authoritative per-package text. - -## Adapted color guidance - -`reference/color.md` adapts selected procedural principles and color vocabulary, with new wording and scope for Visualize. No external executable code, tools, or third-party reference collection is imported. - -- [pbakaus/impeccable](https://github.com/pbakaus/impeccable/blob/09506a9bb5bd4fc87e696718f0f9c8ba2b60692c/plugin/skills/impeccable/reference/colorize.md), revision `09506a9bb5bd4fc87e696718f0f9c8ba2b60692c`. [Original license](reference/licenses/impeccable-Apache-2.0.txt). Adapted on 2026-08-28. -- [meodai/skill.color-expert](https://github.com/meodai/skill.color-expert/blob/d60f17973cab5bafb6caef605e9592dfe832b90b/SKILL.md), revision `d60f17973cab5bafb6caef605e9592dfe832b90b`. [Original license](reference/licenses/color-expert.txt). Adapted on 2026-08-28. - -Impeccable is by Paul Bakaus, under Apache-2.0. Color Expert is by meodai, under CC BY 4.0 for its original material; third-party reference content has separate terms and was not imported. Color Expert-derived adaptations remain attributed and licensed under CC BY 4.0; the repository MIT license does not replace these source terms. - -## Adapted system-exploration guidance - -`reference/explore-system.md` adapts the distinction between established, incomplete, absent, and replacement visual authority from Paul Bakaus’s [Impeccable new-work guidance](https://github.com/pbakaus/impeccable/blob/f88b2837a7d7c3182e46307bbbb091a1ed547571/skill/reference/new-work.md), revision `f88b2837a7d7c3182e46307bbbb091a1ed547571` (Apache-2.0; [license](reference/licenses/impeccable-Apache-2.0.txt)). Adapted on 2026-08-28 with new wording and Visualize’s comparison, confirmation, and local recovery procedure. No seed engine, decision UI, command structure, or executable code is imported. Shared color adaptations are attributed above. diff --git a/skills/visualize-lite/SKILL.md b/skills/visualize-lite/SKILL.md deleted file mode 100644 index ea026070..00000000 --- a/skills/visualize-lite/SKILL.md +++ /dev/null @@ -1,241 +0,0 @@ ---- -name: visualize-lite -version: 0.8.0 -license: MIT -user-invocable: true -argument-hint: "[teach | explore | simplify | bolder | quieter | animate | polish | review | publish] []" -allowed-tools: - - Bash(node $SKILL_DIR/scripts/detect.mjs*) - - Bash(node $SKILL_DIR/scripts/browser-contrast.mjs*) - - Bash(node $SKILL_DIR/scripts/browser-diagram.mjs*) - - Bash(node $SKILL_DIR/scripts/render-mermaid.mjs*) - - Bash(node $SKILL_DIR/scripts/palette.mjs*) - - Bash(node $SKILL_DIR/scripts/teach.mjs*) - - Bash(node $SKILL_DIR/scripts/generate-image.mjs*) - - Bash(node $SKILL_DIR/scripts/record-image.mjs*) - - Bash(bash $SKILL_DIR/scripts/publish.sh*) - - Bash($SKILL_DIR/scripts/publish.sh*) - - Bash(node */visualize-lite/scripts/detect.mjs*) - - Bash(node */visualize-lite/scripts/browser-contrast.mjs*) - - Bash(node */visualize-lite/scripts/browser-diagram.mjs*) - - Bash(node */visualize-lite/scripts/render-mermaid.mjs*) - - Bash(node */visualize-lite/scripts/palette.mjs*) - - Bash(node */visualize-lite/scripts/teach.mjs*) - - Bash(node */visualize-lite/scripts/generate-image.mjs*) - - Bash(node */visualize-lite/scripts/record-image.mjs*) - - Bash(bash */visualize-lite/scripts/publish.sh*) - - Bash(*/visualize-lite/scripts/publish.sh*) -description: > - Generate beautiful, on-brand HTML artifacts — reports, diagrams, diff - reviews, slide decks, plans, recaps, dashboards. Loads on creation - intents ("create a plan", "make me an implementation plan", - "generate a diagram", "build a slide deck", "draft a recap", - "visualize this", "render this as HTML", "make me a status update") - and exploration intents ("show me three layouts", "explore document - shapes", "compare visual treatments", "explore a new design system", - "compare replacement brand directions") and iteration intents ("make this beautiful", "polish this report", - "make this less generic", "design this", "theme this", "use my - brand", "iterate on this design", "review this artifact", "make - this bolder", "make this quieter", "simplify this design", - "animate this", "improve the look of", "give this a refresh"). - Captures brand from the user's codebase, asking, or live site, then - applies it to every artifact. ---- - -# visualize — beautiful, on-brand HTML artifacts - -Generate self-contained HTML artifacts composed from `templates/*/template.md` contracts against the resolved visual context. The project's `DESIGN.md`, `PRODUCT.md`, and sidecar `tokens.css` provide the default; artifact themes below allow reference systems within the user's approved scope. Shared pattern recipes live in `patterns/` and are read only when the selected template and user ask make them relevant; shell mechanics are loaded automatically only when a template declares `shell: `. - -## Design judgment - -Identify what the reader needs to understand, make the important distinction visible, and retain the evidence and qualifications needed to interpret it correctly. - -Find the artifact's organizing idea before choosing its visual treatment. Look for a relationship, contrast, sequence, decision, or object in the source that the composition itself can make easier to understand. Use that idea to shape the opening, section rhythm, and any explanatory primitive. When the source has no useful spatial or structural idea, prefer a restrained reading experience over manufacturing one. - -Artifact mode describes how the reader consumes the information, not the aesthetic family it must use. A document can use product-native, application-like, spatial, editorial, or technical-drawing grammar when that grammar serves the source, reader, and brand. Do not equate `document` with editorial publishing, or `canvas` with diagram chrome. - -Visual conviction can come from composition, typography, density, spatial rhythm, imagery, interaction, or color. Do not add hues, decoration, or components merely to make an artifact feel designed. A restrained palette can carry a strong point of view when another dimension does the expressive work. - -Before responding to feedback, compare the observation with the artifact's reader, purpose, source, and approved brief where one exists. Distinguish an implementation defect, a design that fails its existing requirements, and evidence that a requirement or assumption has changed. The current composition is a solution, not a constraint. - -Make authorized corrections within the existing brief directly. If feedback implies a change to established requirements that the user has not already approved, explain the proposed change and obtain agreement before editing. When several structural or visual directions remain plausible, recommend `explore artifact` within its supported scope. When the direction is established, use the ordinary creation or refinement workflow. Follow that workflow's approval requirements; a small correction does not need a new exploration. - -Evaluate proposed fixes together against the reader's task. Do not turn every critique finding into an addition or treat a small diff as evidence that the direction is sound. Preserve decision-relevant deferred observations in existing review or task notes. - -## Commands - -`/visualize-lite` invokes the skill. The argument tells the agent what to do. - -**Default (no command, or the first argument is a topic):** `/visualize-lite ` creates a new artifact. The agent picks a template from intent across all of `templates/`, composes against the resolved project or artifact-theme context, writes self-contained HTML. **The flow is gated:** preflight checklist + shape-gate before code, no self-authored briefs. See `reference/create.md`. - -**Named commands:** - -| Category | Command | Argument | What it does | Reference | -|---|---|---|---|---| -| **Build** | `teach` | none | Bootstrap (or refresh) the brand profile. Writes `DESIGN.md` + `PRODUCT.md` at the project root. | `reference/teach.md` | -| **Build** | `explore` | `artifact\|system ` | Compare one artifact's treatment or a new/replacement project system; review before applying. | `reference/explore.md` | -| **Refine** | `simplify` | `` | Strip decoration that isn't earning its pixel. Hands off to `polish`. | `reference/simplify.md` | -| **Refine** | `bolder` | `` | Amplify visual punch on safe / generic artifacts. Hands off to `polish`. | `reference/bolder.md` | -| **Refine** | `quieter` | `` | Tone down over-decoration without going generic. Hands off to `polish`. | `reference/quieter.md` | -| **Refine** | `animate` | `` | Purposeful motion, `prefers-reduced-motion` compliant. Hands off to `polish`. | `reference/animate.md` | -| **Refine** | `polish` | `` | Terminal quality pass — walks every dimension and reports per-dimension findings. The other Refine verbs hand off to this one. | `reference/polish.md` | -| **Evaluate** | `review` | `` | Deterministic detector findings + LLM judgment. Reports; doesn't modify the file. | `reference/review.md` | -| **Publish** | `publish` | `` | Push the artifact to display.dev via MCP / CLI / HTTP cascade. | `reference/publish.md` | - -Invocation forms: `/visualize-lite teach`, `/visualize-lite explore artifact ./report.html`, `/visualize-lite polish ./report.html`, `/visualize-lite publish ./diagram.html`. On hosts without a slash-command form, the skill loads on intent-match from the description above; natural-language is the primary entry there. - -## Routing rules - -1. **No argument**: render the Commands table above as the user-facing menu, grouped by category. Ask what they'd like to do. -2. **First word matches a command**: **load its reference file before doing anything else.** Non-negotiable — the table row + this section is insufficient; the procedure lives in the reference. Everything after the command name is the target. -3. **Natural-language intent**: requests to compare design alternatives load `reference/explore.md`; capturing or reconciling project identity loads `reference/teach.md`. Otherwise load `reference/create.md` for creation or an explicitly requested restyling of an existing artifact. A named theme follows Artifact themes below; it does not require an exploration round. -4. **Relationship-diagram route during creation**: when preflight first identifies a likely relationship diagram, load `reference/diagram.md` and the one tentative non-spatial type reference needed for that figure before presenting the shape gate. For multi-figure artifacts, load one reference for each distinct tentative family: `reference/diagram-flow.md`, `reference/diagram-system.md`, `reference/diagram-sequence.md`, `reference/diagram-state.md`, or `reference/diagram-hierarchy.md`. Spatial figures use `reference/diagram.md` alone. If approval changes a figure's type, load the replacement reference before authoring; do not load unrelated families. -5. **Generated-image route during creation**: when the approved shape requires generated bitmap imagery, load `reference/image-generation.md` before selecting or invoking a route. Use only the active callable inventory or an explicitly selected API adapter. Never infer a route from a key, executable, mount, or model claim, and never retry or fall back after an ambiguous attempt. -6. **Explore scope**: `explore` always loads `reference/explore.md`, which distinguishes artifact treatment from project-system creation or explicit replacement. Comparing treatments for one artifact does not require creating a project design system first. System Explore uses its opt-in authority classification instead of applying the render fallback as an incumbent identity. - -## Hand-off output shape - -After a refine verb (`simplify` / `bolder` / `quieter` / `animate`) finishes its run, summarise to the user in plain markdown. Don't render the summary as a fenced code block — it's conversational output, not machine-readable. Vary phrasing across runs; pick wording per context (engineer reading detector output vs. designer iterating on a brand brief). `polish` has its own output shape — see `reference/polish.md`. - -Every refine run's summary covers four facts: - -- Which verb ran, against which artifact. -- What the verb actually did. Pull the verb-specific content from the per-verb reference's "what to surface" list (focal moment, cuts + audit trail, strategy, what stayed, hero moment, reduced-motion handling — depends on the verb). -- Diff size as a percent of the artifact. -- Which next action makes sense, if any. Polish is the default next step from a successful refine run. - -Variant rules (behaviour, not surface text): - -- **Context source note.** If the run cannot use a persisted `DESIGN.md` / `PRODUCT.md`, mention once which fallback source anchored the work: project instructions, README/docs, generated seed, or Clean defaults. Also nudge the user to run `/visualize-lite teach` so that fallback can become a real brand profile. Clean defaults are the last resort after the Brand profile rules below find no usable project guidance. -- **Diff exceeded the 40% guard.** Stop refinement and report the affected scope. Apply Design judgment to recommend exploration when the structural direction remains unresolved, or a fresh creation pass when it is established. Do not continue into polish. -- **Verb shouldn't have run** (Absolute bans present, no decoration to remove, no focal moment, etc.). Skip the standard run summary entirely. Surface a short refusal that names the reason and routes to the right alternative verb. Each per-verb reference's "When `` is the right verb (and when it isn't)" section enumerates the verb's specific refusal conditions. - -Tone is conversational. Lead with what changed, not with a banner. Don't capitalise words for emphasis. Don't print a ` pass complete ·` header — the summary's content is the proof of completion. - -For `animate` specifically: when the brand declares `motion: none`, a fifth variant applies — see `reference/animate.md`. - -## Absolute bans - -Match-and-refuse. Apply on every command and the default creation flow. These ban specific patterns — not aesthetics, not registers. A brand can declare a font, a hue, or a layout that looks unusual; it cannot redeem any of these patterns by declaring them as identity. An explicit `.visualize-detect.json` skip can waive only a detector-backed ban with a concrete rule ID; it is project policy, not runtime brand suppression, and cannot waive a Layer-2 ban. - -``` -BAN 1 — Gradient text on metrics or headings - PATTERN: background-clip: text + gradient background on , [class*="metric"], [class*="kpi"] - INCLUDES: -webkit-background-clip: text; CSS `background-image: linear-gradient(…)` + `color: transparent` - WHY: AI-essay register tell; reads as 2023-template "make the number pop" - REWRITE: Solid `--primary` (or `--foreground`) at one weight up; emphasis through weight + size, not gradient - -BAN 2 — Side-stripe callout (border-left as accent) - PATTERN: `.callout`, `.tldr`, `.note`, `.tip` with border-left ≥ 3px in --primary / --warning / --destructive - INCLUDES: border-inline-start, border-l-4 utility class, ::before pseudo with positioned bar - WHY: Notion / Confluence template tell; reads as productivity-app default - REWRITE: Full thin border (1px var(--border)), background tint at 4–8% chroma, or no chrome — just a label heading - -BAN 3 — Triple-feature-card grid - PATTERN: 3-column grid of

adjective + noun

+ one paragraph + one icon each - INCLUDES: 4-column variants, "Why us" sections with identical card-icon-heading-blurb shape - WHY: AI-landing canonical pattern; the surest "AI generated this" tell on the visualize corpus - REWRITE: Asymmetric — one hero claim with two supporting facts, OR a numbered list with body prose, OR a single comparison table - -BAN 4 — Hero-metric template - PATTERN: Centered

N×

+

supporting label

+ 3 stat tiles below - INCLUDES: "10× faster", "99.9% uptime", "1M requests/day" hero blocks with the same shape - WHY: SaaS-landing cliché; the most-copied AI-marketing-page shape in 2024-2026 - REWRITE: Specific claim in body prose with the number inline; data tiles only when the artifact is actually a dashboard - -BAN 5 — Ornamental icon-tile section markers - PATTERN: Repeated icon tiles on section headings add chrome without identifying meaningful content distinctions - INCLUDES: Rounded-square, circle, coloured-pill, and gradient-tile variants serving only as decoration - WHY: Repeated chrome makes different sections look alike and competes with their content - REWRITE: Remove ornamental markers; retain useful recognition cues under reference/cognitive-load.md's Cognitive offloading guidance - -BAN 6 — Imperative tricolon - PATTERN: Three short imperative sentences ending in periods, in the same paragraph or hero - INCLUDES: "Ship faster. Build smarter. Scale forever." / "Move fast. Stay safe. Be bold." (any topic) - WHY: AI-copywriting cliché — the structure is identical across thousands of generated landing pages - REWRITE: One specific claim with the verb in the right tense, OR a sentence that names the actor and the action - -BAN 7 — Sycophant / AI-attribution footer - PATTERN: "I hope you find this useful", "Let me know if you have questions", "Best, Claude", "Generated by AI", "As an AI…" - INCLUDES: Footer-line variants, comment-line variants in the HTML source - WHY: The artifact is the work; the model is invisible. Sycophant footers contaminate the artifact register - REWRITE: No footer of this shape. A real footer carries metadata (date, version, source-of-truth link) or is absent -``` - -If you're about to author one of these patterns, stop and rewrite the element with the alternative. The list is shared across creation, every Refine verb, and `polish`. - -## Model-specific operating rules - -These are not brand aesthetics; they are observed model reflexes with required corrective behavior. Apply them after the universal bans, especially during no-brand greenfield creation. - -**Codex-specific rules:** - -- **Border plus big shadow:** avoid pairing a 1px border with a large fuzzy drop shadow on the same card or button. Choose a crisp edge or a small elevation cue. -- **Over-rounded containers:** ordinary cards, sections, inputs, and panels should usually stay at 12-16px radius. Larger radii need an explicit brand reason. -- **Decorative stripe backgrounds:** do not use `repeating-linear-gradient(...)` as body or section filler. Use an intentional texture/rule system, or leave the background plain. -- **Crude SVG fallback:** do not draw rough SVG scenes because no real asset is available. Use generated bitmap imagery, a geometric diagram, or no illustration. -- **Compressed display type:** keep display heading letter-spacing ≥ -0.04em and clamp max ≤ 6rem unless the template is a slide/poster register that explicitly needs larger type. -- **Meta-contrast copy:** avoid "X theater", "actually X", and "not just X, it's Y" framing. Say what the artifact literally does. -- **Color judgment:** use `reference/color.md` to assess character, relationships, roles, distribution, and actual pairings. No hue, hue combination, neutral tint, or accent count is a universal taste gate. Preserve approved identity and explicit artifact exceptions. -- **Palette as a substitute for composition:** assign semantic color roles in the shape gate and use them consistently, but do not add accent tracks merely to manufacture visual interest. Neutral surfaces plus one accent are valid when composition, type, density, imagery, or interaction carries the point of view. Revise when the opening has no expressive dimension, not when it has too few hues. - -**Gemini-specific guard:** - -- Do not animate `` elements on hover. If a card needs hover feedback, animate the card's border, background, or action affordance instead. - -**Metadata / TOC guard:** static document context needs explicit labels, not anonymous rounded chips. Place document context according to [create.md's document-context guidance](reference/create.md); a right rail, split-cover facts column, or standalone metadata panel usually turns context into competing chrome. TOC rails are allowed for genuinely long documents, but they should support navigation rather than dominate the opening. Mobile TOCs must remain navigable lists or deliberate grids, never free-wrapping word clouds. Read `patterns/metadata.md` or `patterns/toc.md` when those structures are part of creation. - -**Table guard:** tables are reading instruments, not hero sections. Keep caption, header, body, row-key, and numeric roles distinct; scope monospace to fixed-width values. Read `patterns/table.md` when the artifact carries dense comparison or evidence. - -**Interactive-state route:** when creation includes functional controls or changes state in place, read `patterns/interactive-states.md`. - -## AI slop test (two altitudes) - -Run both. The second catches what the first misses. - -**First-order**: if someone could guess the template + palette from the topic alone, you've hit the first training-data reflex. Topic stereotypes are prompts to inspect the brief, not color prohibitions: red, purple gradients, or navy-and-gold can fit a specific artifact. Rework unsupported defaults, not approved identity. - -**Second-order**: if someone could guess the aesthetic family from topic-plus-anti-references ("AI workflow tool that's not SaaS-cream → editorial-typographic," "fintech that's not navy-and-gold → terminal-native dark mode"), the second-tier reflex isn't avoided. Currently-saturated families to watch for: editorial-Tufte clone (cream + Georgia + side-margin notes when the topic isn't editorial), brutalist-terminal clone (mono + grid + heavy borders when the topic isn't dev-tool), Swiss-poster clone (uppercase + grotesk + grid when the topic isn't presentation). Rework until both altitudes return "no, you couldn't have guessed." - -## Brand profile - -Every render reads available `DESIGN.md` (visual identity + design tokens) and `PRODUCT.md` (voice, audience, tone) at the project root. `teach` derives custom design tokens for the brand and writes them in `DESIGN.md` as YAML frontmatter (the Google Stitch canonical format: machine-readable tokens) plus a six-section markdown body (Overview / Colors / Typography / Elevation / Components / Do's and Don'ts), with a sidecar `tokens.css` at the project root carrying the CSS-form tokens templates read at render time. Project profiles retain concrete tokens, not a `theme: ` pointer. - -Brand authority is identity-scoped. If the request fixes a named identity but the resolved project profile clearly belongs to another product, do not substitute, relabel, or approximate it. Stop before the shape gate or any artifact edit and request matching authority or explicit permission for an artifact-only override. - -The design systems under `design-systems/` are reference packages with design guidance, tokens, and previews. `teach` and System Explore read them when deriving project identities; artifact creation and exploration can use them as themes under the shared contract below. - -This lite build bundles only `catalog-index.json`, `_template/`, and `clean/` from `design-systems/`. `design-systems/REMOTE.md` holds the base URL of the full catalog at the same version. When a selected package folder is missing, fetch its `README.md`, `DESIGN.md`, and `tokens.css` from that base URL plus `/` with `curl` or the host's web fetch, then use them as if they were local. If a fetch fails, name the package that could not be loaded and continue with the next shortlisted reference or the fallback; never rebuild a package from memory. - -**Fallback when no DESIGN.md exists:** an approved artifact reference supplies the visual baseline where project guidance is absent; do not replace it with a seed or Clean defaults. Otherwise, first read available project instructions (`AGENTS.md`, `CLAUDE.md`, README, docs) for product positioning, visual direction, anti-references, and design principles. If those files contain usable brand/design guidance, treat it as binding brand context while using Clean's token **surface** as the structural floor. Explicit cues in repo guidance override Clean values: named fonts become the artifact font stack, named palette roles become the artifact color tokens, and named anti-references rule out the corresponding fallback register. If the guidance points at concrete token files, stylesheet files, logo components, or design guidelines, read those referenced files and use their values before approximating from prose. Using Clean's token surface means keeping the semantic variable shape, not copying Clean's fonts, monochrome values, or warm/cream defaults. Only run `node $SKILL_DIR/scripts/palette.mjs --from ""` when the project has no usable visual direction at all. When a seed is used, compose the temporary `--primary`, `--background`, `--foreground`, `--accent`, chart, and state tokens from the seed + artifact brief; do not copy Clean's monochrome palette unchanged. Use the script's `derived` block (real contrast numbers, per-hue chroma budgets, light + dark lightness ladders) instead of estimating those values; if the seed's register fights the brief, re-run with `--not ` or `--vary ` and note the veto in the shape gate. After composing, validate with `node $SKILL_DIR/scripts/palette.mjs --check --strict` and fix error findings (exit 2) before render. Nudge `/visualize-lite teach` once per session so repo guidance or the temporary seed can become a real brand profile. - -**Refine verbs on missing persisted brand files:** `simplify` / `bolder` / `quieter` / `animate` / `polish` use the same fallback hierarchy as creation. Project instructions, README/docs, or other already-loaded context can be a valid qualitative brand anchor. Clean defaults are only the anchor when no usable project guidance exists. Whenever a fallback source is used, mention that source once and nudge `/visualize-lite teach` so the fallback can become a real brand profile. - -**Refine verbs on artifacts that carry ≥3 Absolute bans:** `bolder` / `quieter` / `animate` / `polish` refuse and route. State in the run summary: `"REFUSED — N Absolute bans present (listed). The artifact needs structural cleanup before applies. Recommend /visualize-lite simplify first (removes the ban patterns), then re-evaluate."` Simplify accepts ban-carrying input as its core case; the other Refine verbs assume a clean baseline and must not run on ban-heavy artifacts. - -## Artifact themes - -A theme is a reference design system applied to one artifact, not a new stored object or a project-wide adoption. Use the current project system by default. Resolve binding project choices, approved artifact exceptions, and open design dimensions before borrowing from a reference. A named reference or a request to make an artifact more expressive does not silently override binding project choices; clarify conflicts that the request leaves unresolved. Explicit permission to replace visual styling applies only to the named artifact and dimensions. Do not invent constraints where the project is silent. - -Read the selected `design-systems//README.md` when present, then its `DESIGN.md` and `tokens.css`. README is the package entry point; DESIGN owns suitability, identity, and adaptation guidance. Use previews to understand the treatment, not as required layouts. If the user wants alternatives, propose a small relevant shortlist rather than loading the entire catalog. A missing README does not make a package unavailable. - -Apply a named treatment through ordinary creation or revision; compare unresolved treatments through `explore artifact`. Neither requires a new layout or a project profile first. For new content, derive structure from the reader's task and source. For existing artifacts, preserve content, order, assets, and behavior unless those changes were requested. A broad restyling uses `reference/create.md` with that preservation scope, not a narrow refine verb forced past its diff guard. Existing approval gates still apply; an explicit request already covering the proposed changes does not need reconfirmation. - -Translate the reference's hierarchy, spacing, surfaces, and component treatment as well as its palette and type. Keep non-overridden project choices. Embed the resolved tokens, including designed light/dark and OS-dark behavior, in the artifact; do not replace the project's token source. Verify against the resolved artifact context, not the overridden project values. Describe partial borrowing and retained constraints rather than claiming an unchanged reference application. Keep the approved exception and its scope in existing task notes or a short artifact comment so later refinement can preserve it; unexplained styling drift alone is not approval. - -For project-wide capture or direct derivation, use `teach`. To compare new or explicitly requested replacement systems, use `explore system`. Neither route is a prerequisite for artifact themes. - -## Universal laws - -Apply on every command, every template, every prompt. These override contextual taste findings, not non-contextual correctness constraints: - -1. **Resolved context and voice trump contextual taste rules.** A finding that merely conflicts with the approved register is a category error. Context cannot suppress an unskipped Absolute ban, an artifact fossil or placeholder, an accessibility requirement, or an honesty/data-truth failure. Permission to refine an artifact does not authorize changes to the project's brand profile or shared token source. Propose necessary changes to those authorities separately unless the user has already authorized them. -2. **Commands interpret, not regenerate.** Iteration commands return a targeted diff, not a fresh draft. If the diff is >40% of the artifact, the command has misfired. -3. **Ship-blockers and taste-calls are different.** A11y and contrast must be fixed; taste-calls are negotiable. -4. **Severity follows context, including aggregate.** An `info` rule can ship-block in aggregate; an `error` rule can be a taste-call only when it is context-sensitive and the brand whitelists it. The non-contextual constraints in law 1 do not demote. -5. **Don't strip-mine into invisibility.** After a command runs, the artifact should still feel anchored to the resolved context. -6. **The reader is the judge.** Every finding maps to: does this make the reader's job harder, or just satisfy a rule? -7. **Detector output is defect-evidence, never done-evidence.** A clean `detect.mjs` result is not proof the artifact is strong — it's proof the mechanical floor passes. The detector catches what regex can identify; verb work covers everything else. Don't declare done on a clean detector reading. -8. **Rendered mobile beats desktop theory.** A desktop-strong composition that merely stacks on phone can still be worse than a plain document. Judge the first two mobile screens as a finished reading state: heading scale, metadata wraps, TOC placement, card chrome, and body rhythm all have to feel intentional at narrow width. -9. **Metadata should explain itself.** Bare chips such as `display.dev strategy` or `Updated May 29, 2026` force the reader to infer the field. Prefer explicit labels: `Memo type / display.dev strategy`, `Updated / May 29, 2026`, `Input / method, vision, competitor refresh`. Place labelled context according to [create.md's document-context guidance](reference/create.md); not every metadata field belongs in the opening. -10. **Clean is not enough.** Mechanical correctness and visual polish do not establish comprehension. A visual artifact needs deliberate composition that helps the reader understand its source. Assess substantial explanations beyond the first viewport as well as the opening; follow the [Specificity and comprehension pass](reference/create.md#specificity-and-comprehension-pass). A familiar arrangement is valid when it serves the reader; change structure to correct a demonstrated deficiency, not merely to avoid a common layout. diff --git a/skills/visualize-lite/bin/_import-common.mjs b/skills/visualize-lite/bin/_import-common.mjs deleted file mode 100755 index 2344b049..00000000 --- a/skills/visualize-lite/bin/_import-common.mjs +++ /dev/null @@ -1,390 +0,0 @@ -#!/usr/bin/env node -// _import-common.mjs — frontmatter and token helpers. -// -// Used by ../scripts/detect.mjs and schema checks for catalog DESIGN.md -// frontmatter parsing. Kept here rather than inlined because the parser is -// narrowly tuned to the catalog's frontmatter shape and not worth duplicating. -// -// Self-contained Node script. Colour conversion delegates to the -// vendored culori at ../scripts/vendor/culori.mjs (MIT, 4.0.2). - -import { readFileSync, writeFileSync, existsSync } from 'node:fs'; -import { parse as parseColor, converter } from '../scripts/vendor/culori.mjs'; - -const toOklch = converter('oklch'); - -// ============================================================ -// Slug derivation -// ============================================================ - -// claude → claude-style (the nominative-use slug carrier). -export function brandToSlug(brand) { - const base = String(brand).toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, ''); - if (!base) throw new Error(`Empty slug derived from brand: ${brand}`); - return base.endsWith('-style') ? base : `${base}-style`; -} - -// Extract the brand portion from a legacy `name:` frontmatter field. -// Convention: `-design-analysis` or `-Inspired-design-analysis`. -// Returns null if the input doesn't look like a brand-bearing name. -export function extractBrandFromUpstreamName(name) { - if (!name || typeof name !== 'string') return null; - const cleaned = name - .replace(/-Inspired-design-analysis$/i, '') - .replace(/-design-analysis$/i, '') - .replace(/-design-system$/i, '') - .trim(); - return cleaned || null; -} - -// claude → Claude-style. Prefer source-name casing when available so -// IBM / BMW-M / PlayStation / RunwayML don't collapse to title-case. -export function brandToDisplayName(brand, upstreamName = null) { - return `${brandToReference(brand, upstreamName)}-style`; -} - -// claude → Claude. Prefer source `name:` casing when the lowercased -// source brand matches the path-derived brand (preserves IBM / -// RunwayML / PlayStation / BMW-M casing). When they disagree -// (e.g. source `Stripi-...` vs path `stripe/`), trust the path — -// source typos shouldn't propagate into our slug + display name. -export function brandToReference(brand, upstreamName = null) { - const cleaned = String(brand).trim(); - const upstreamBrand = extractBrandFromUpstreamName(upstreamName); - if (upstreamBrand && upstreamBrand.toLowerCase() === cleaned.toLowerCase()) { - return upstreamBrand; - } - // Title-case the path; hyphenated brands keep the hyphen. - return cleaned.charAt(0).toUpperCase() + cleaned.slice(1).toLowerCase(); -} - -// ============================================================ -// HEX → OKLCH conversion (via vendored culori) -// ============================================================ - -// Parse any CSS colour string (hex / rgb / rgba / hsl / oklch / named) -// to OKLCH. Preserves alpha when source is non-opaque. -export function hexToOklch(input) { - const parsed = parseColor(String(input).trim()); - if (!parsed) throw new Error(`Bad colour: ${input}`); - const c = toOklch(parsed); - const result = { L: c.l, C: c.c, H: c.c < 0.0001 || c.h === undefined ? 0 : c.h }; - if (parsed.alpha !== undefined && parsed.alpha < 1) result.alpha = parsed.alpha; - return result; -} - -// Format OKLCH matching clean/tokens.css convention: -// `oklch(1 0 0)` — integers stay integer -// `oklch(0.1450 0 0)` — 4-decimal L, zero-chroma omits hue -// `oklch(0.5770 0.2450 27.3250)` — 4-decimal L/C/H -// `oklch(0.8100 0.1000 252)` — integer hue stays integer -// `oklch(0.5 0 0 / 0.5)` — non-opaque alpha appended -function fmtComponent(n) { - if (!Number.isFinite(n)) return '0'; - const fixed = n.toFixed(4); - return /\.0000$/.test(fixed) ? String(Math.round(n)) : fixed; -} - -export function formatOklch({ L, C, H, alpha }) { - const Lstr = fmtComponent(L); - const body = C < 0.0001 ? `${Lstr} 0 0` : `${Lstr} ${fmtComponent(C)} ${fmtComponent(H)}`; - if (alpha !== undefined && alpha < 1) return `oklch(${body} / ${fmtComponent(alpha)})`; - return `oklch(${body})`; -} - -export function hexToOklchString(hex) { - return formatOklch(hexToOklch(hex)); -} - -// Synthesize dark-mode counterpart by inverting OKLCH lightness while -// preserving hue + chroma (and alpha). Lossy synthesis (won't match a -// brand's actual dark-mode palette) but produces a working dark surface -// set without a TODO punt. -export function invertLightness({ L, C, H, alpha }) { - return { L: 1 - L, C, H, ...(alpha !== undefined ? { alpha } : {}) }; -} - -export function invertLightnessOklchString(oklchString) { - const m = oklchString.match(/^oklch\(\s*([-\d.]+)\s+([-\d.]+)\s+([-\d.]+)(?:\s*\/\s*([\d.]+))?\s*\)$/); - if (!m) throw new Error(`Cannot parse OKLCH: ${oklchString}`); - const L = parseFloat(m[1]); - const C = parseFloat(m[2]); - const H = parseFloat(m[3]); - const alpha = m[4] !== undefined ? parseFloat(m[4]) : undefined; - return formatOklch({ L: 1 - L, C, H, alpha }); -} - -// ============================================================ -// Minimal YAML reader (narrow to catalog frontmatter shape) -// ============================================================ -// -// Supports: -// - Top-level `key: value` (string or scalar) -// - Top-level `key:` followed by 2-space-indented nested mapping -// - Single-line values (no block scalars) -// - Double- and single-quoted strings (quotes stripped, escape \" handled) -// - Comments (`# ...`) on their own line -// -// Does NOT support: arrays, flow style, anchors, multi-line values, -// merge keys, custom tags. None of those appear in current catalog frontmatter — vendor a real parser if shapes grow. - -export function parseFrontmatter(content) { - const m = String(content).match(/^---\r?\n([\s\S]*?)\r?\n---\r?\n?([\s\S]*)$/); - if (!m) throw new Error('No YAML frontmatter found (expected leading --- block)'); - return { data: parseYaml(m[1]), body: m[2] }; -} - -function parseYaml(text) { - const root = {}; - const stack = [{ obj: root, indent: -1 }]; - const lines = text.split(/\r?\n/); - - for (let i = 0; i < lines.length; i++) { - const raw = lines[i]; - if (/^\s*$/.test(raw)) continue; - if (/^\s*#/.test(raw)) continue; - - const indent = (raw.match(/^(\s*)/)[1] || '').length; - const line = raw.slice(indent); - const colon = line.indexOf(':'); - if (colon < 0) throw new Error(`YAML line ${i + 1}: missing colon — ${raw}`); - - const key = line.slice(0, colon).trim(); - const rest = line.slice(colon + 1).trim(); - - while (stack.length > 1 && indent <= stack[stack.length - 1].indent) stack.pop(); - const parent = stack[stack.length - 1].obj; - - // Block scalar (`|`, `>`, with optional chomping `-`/`+`). - // Some DESIGN.md files use `description: |` followed by - // indented prose — silently parsing those as `"|"` would corrupt the - // brand description, so explicit support is required. - if (/^[|>][+-]?$/.test(rest)) { - const folded = rest.startsWith('>'); - const strip = rest.includes('-'); // `-` = strip final newline; default keeps one. - const blockLines = []; - let blockIndent = -1; - while (i + 1 < lines.length) { - const next = lines[i + 1]; - if (/^\s*$/.test(next)) { blockLines.push(''); i++; continue; } - const nextIndent = (next.match(/^(\s*)/)[1] || '').length; - if (nextIndent <= indent) break; - if (blockIndent < 0) blockIndent = nextIndent; - blockLines.push(next.slice(blockIndent)); - i++; - } - // Drop trailing blank lines (they're not part of the block content). - while (blockLines.length > 0 && blockLines[blockLines.length - 1] === '') blockLines.pop(); - let value; - if (folded) { - value = blockLines.join(' ').replace(/\s+/g, ' ').trim(); - } else { - value = blockLines.join('\n'); - if (!strip) value += '\n'; - } - parent[key] = value; - continue; - } - - if (rest === '') { - const child = {}; - parent[key] = child; - stack.push({ obj: child, indent }); - } else { - parent[key] = parseScalar(rest); - } - } - return root; -} - -function parseScalar(raw) { - if ((raw.startsWith('"') && raw.endsWith('"')) || (raw.startsWith("'") && raw.endsWith("'"))) { - return raw.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\'); - } - return raw; -} - -// ============================================================ -// YAML writer (matches clean/ DESIGN.md frontmatter shape) -// ============================================================ - -export function emitFrontmatter(data, body = '') { - const yaml = emitMapping(data, 0); - const bodyOut = body ? (body.startsWith('\n') ? body : '\n' + body) : '\n'; - return `---\n${yaml}---${bodyOut}`; -} - -function emitMapping(obj, indent) { - const pad = ' '.repeat(indent); - let out = ''; - for (const [k, v] of Object.entries(obj)) { - if (v === null || v === undefined) { - out += `${pad}${k}: null\n`; - continue; - } - if (typeof v === 'object' && !Array.isArray(v)) { - // Empty mapping → emit `key: {}` so the round-trip is honest. - if (Object.keys(v).length === 0) { - out += `${pad}${k}: {}\n`; - continue; - } - out += `${pad}${k}:\n${emitMapping(v, indent + 1)}`; - continue; - } - // Multi-line strings → folded block scalar (`>-`). The simple quoted-scalar - // shape would embed literal newlines that parseFrontmatter can't read back. - if (typeof v === 'string' && v.includes('\n')) { - const childPad = ' '.repeat(indent + 1); - const trimmed = v.replace(/\n+$/, ''); - out += `${pad}${k}: >-\n`; - for (const line of trimmed.split('\n')) { - out += `${childPad}${line}\n`; - } - continue; - } - out += `${pad}${k}: ${formatYamlScalar(v)}\n`; - } - return out; -} - -function formatYamlScalar(v) { - if (typeof v === 'number') return String(v); - if (typeof v === 'boolean') return v ? 'true' : 'false'; - const s = String(v); - // Plain numeric (with optional unit suffix) — unquoted. - if (/^-?\d+(\.\d+)?([a-z%]+)?$/i.test(s)) return s; - // Identifier-like (no special chars) — unquoted. - if (/^[A-Za-z_][A-Za-z0-9_-]*$/.test(s)) return s; - // Everything else (hex, oklch, prose with punctuation, etc.) — double-quoted. - return `"${s.replace(/\\/g, '\\\\').replace(/"/g, '\\"')}"`; -} - -// ============================================================ -// NOTICES.md upsert (idempotent, sorted, deduped) -// ============================================================ - -const NOTICES_HEADER = `# Third-party design-system attributions - -Design systems under \`visualize/design-systems/-style/\` are derived -from third-party sources. Per-entry attribution lives in each DESIGN.md -\`source:\` block; repository-level license notices are collected here. - -## Brand reference - -Design systems with a \`-style\` suffix (\`stripe-style\`, \`figma-style\`, …) are -independent reference implementations inspired by the public visual language -of well-known brands. They are not affiliated with, endorsed by, or -representative of the named companies. All brand names and trademarks remain -the property of their respective owners. -`; - -// Per-source section templates. The script appends only the section -// being inserted into on first write — avoids stale dated claims about -// sources that have no entries yet. -const NOTICES_SECTIONS = { - 'nexu-io/open-design (Apache-2.0)': ` -## nexu-io/open-design (Apache-2.0) - -Repository: https://github.com/nexu-io/open-design -License: Apache-2.0 - -No NOTICE file present in upstream (verified at first OD import); §4(b) -is satisfied by per-file copyright preservation and per-file -modification markers in each DESIGN.md frontmatter. - -Derived design systems: -`, -}; - -// Insert `slug` into the "Derived design systems:" list under `sectionHeader`. -// Creates NOTICES.md (with only the requested section's template) if the file -// doesn't exist. Appends the section template if the file exists but the -// section is missing. Returns whether the entry was already present. -export function upsertNotices(noticesPath, sectionHeader, slug) { - const template = NOTICES_SECTIONS[sectionHeader]; - if (!template) throw new Error(`Unknown NOTICES section: ${sectionHeader}`); - - let body = existsSync(noticesPath) ? readFileSync(noticesPath, 'utf8') : NOTICES_HEADER; - - const headerLine = `## ${sectionHeader}`; - if (!new RegExp(`^${escapeRegex(headerLine)}$`, 'm').test(body)) { - body = body.replace(/\n*$/, '\n') + template; - } - - // Section bounds: line-anchored to avoid matching `## ` strings in prose. - const headerMatch = body.match(new RegExp(`^${escapeRegex(headerLine)}$`, 'm')); - const sectionStart = headerMatch.index; - const afterHeader = sectionStart + headerLine.length; - const nextHeaderMatch = body.slice(afterHeader).match(/^## /m); - const sectionEnd = nextHeaderMatch ? afterHeader + nextHeaderMatch.index : body.length; - let section = body.slice(sectionStart, sectionEnd); - - const listMarker = 'Derived design systems:'; - const markerIdx = section.indexOf(listMarker); - if (markerIdx < 0) throw new Error(`"Derived design systems:" marker missing in section ${sectionHeader}`); - const afterMarker = markerIdx + listMarker.length; - - // Collect all existing `- ` items inside the section. - const restOfSection = section.slice(afterMarker); - const items = [...restOfSection.matchAll(/^- (.+)$/gm)].map((m) => m[1].trim()); - - const alreadyPresent = items.includes(slug); - if (!alreadyPresent) items.push(slug); - items.sort(); - const uniq = [...new Set(items)]; - - // The "list region" ends at the end of the last `- ` line; if there are no - // items yet, the region collapses to just the marker (no chars to replace). - let listEnd = afterMarker; - let lastItem = null; - for (const m of restOfSection.matchAll(/^- .+$/gm)) lastItem = m; - if (lastItem) listEnd = afterMarker + lastItem.index + lastItem[0].length; - - const newListBlock = '\n' + uniq.map((s) => `- ${s}`).join('\n'); - section = section.slice(0, afterMarker) + newListBlock + section.slice(listEnd); - - const newBody = body.slice(0, sectionStart) + section + body.slice(sectionEnd); - writeFileSync(noticesPath, newBody); - return { alreadyPresent, total: uniq.length }; -} - -function escapeRegex(s) { - return s.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'); -} - -// ============================================================ -// Review block printer -// ============================================================ - -export function printReview(stream, fields) { - const w = (s) => stream.write(s + '\n'); - w(''); - w('Review:'); - for (const [k, v] of Object.entries(fields)) { - if (v === null || v === undefined) continue; - if (typeof v === 'string' && v.includes('\n')) { - w(` ${k}:`); - for (const line of v.split('\n')) w(` ${line}`); - } else { - w(` ${k}: ${v}`); - } - } - w(''); -} - -// ============================================================ -// CLI arg parsing -// ============================================================ - -export function parseArgs(argv) { - const args = { positional: [], force: false, dryRun: false }; - for (let i = 0; i < argv.length; i++) { - const a = argv[i]; - if (a === '--force') args.force = true; - else if (a === '--dry-run') args.dryRun = true; - else if (a === '-h' || a === '--help') args.help = true; - else if (a.startsWith('--')) throw new Error(`Unknown flag: ${a}`); - else args.positional.push(a); - } - return args; -} diff --git a/skills/visualize-lite/bin/jq-linux-amd64 b/skills/visualize-lite/bin/jq-linux-amd64 deleted file mode 100755 index 37a7a668..00000000 Binary files a/skills/visualize-lite/bin/jq-linux-amd64 and /dev/null differ diff --git a/skills/visualize-lite/bin/jq-linux-arm64 b/skills/visualize-lite/bin/jq-linux-arm64 deleted file mode 100755 index e93932ca..00000000 Binary files a/skills/visualize-lite/bin/jq-linux-arm64 and /dev/null differ diff --git a/skills/visualize-lite/bin/jq-macos-amd64 b/skills/visualize-lite/bin/jq-macos-amd64 deleted file mode 100755 index 4fbf7ae7..00000000 Binary files a/skills/visualize-lite/bin/jq-macos-amd64 and /dev/null differ diff --git a/skills/visualize-lite/bin/jq-macos-arm64 b/skills/visualize-lite/bin/jq-macos-arm64 deleted file mode 100755 index 8842adb7..00000000 Binary files a/skills/visualize-lite/bin/jq-macos-arm64 and /dev/null differ diff --git a/skills/visualize-lite/bin/jq-windows-amd64.exe b/skills/visualize-lite/bin/jq-windows-amd64.exe deleted file mode 100755 index 70b52888..00000000 Binary files a/skills/visualize-lite/bin/jq-windows-amd64.exe and /dev/null differ diff --git a/skills/visualize-lite/bin/jq.LICENSE b/skills/visualize-lite/bin/jq.LICENSE deleted file mode 100644 index c162125a..00000000 --- a/skills/visualize-lite/bin/jq.LICENSE +++ /dev/null @@ -1,136 +0,0 @@ -jq is copyright (C) 2012 Stephen Dolan - -Permission is hereby granted, free of charge, to any person obtaining -a copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, sublicense, and/or sell copies of the Software, and to -permit persons to whom the Software is furnished to do so, subject to -the following conditions: - -The above copyright notice and this permission notice shall be -included in all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, -EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND -NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE -LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION -OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION -WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - - - -jq's documentation (everything found under the docs/ subdirectory in -the source tree) is licensed under the Creative Commons CC BY 3.0 -license, which can be found at: - - https://creativecommons.org/licenses/by/3.0/ - -The documentation website includes a copy of Twitter's Bootstrap and -relies on Bonsai, Liquid templates and various other projects, look -them up for detailed licensing conditions. - - - -jq incorporates David M. Gay's dtoa.c and g_fmt.c, which bear the -following notices: - -dtoa.c: -The author of this software is David M. Gay. - -Copyright (c) 1991, 2000, 2001 by Lucent Technologies. - -Permission to use, copy, modify, and distribute this software for any -purpose without fee is hereby granted, provided that this entire notice -is included in all copies of any software which is or includes a copy -or modification of this software and in all copies of the supporting -documentation for such software. - -THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED -WARRANTY. IN PARTICULAR, NEITHER THE AUTHOR NOR LUCENT MAKES ANY -REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY -OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. - -g_fmt.c: -The author of this software is David M. Gay. - -Copyright (c) 1991, 1996 by Lucent Technologies. - -Permission to use, copy, modify, and distribute this software for any -purpose without fee is hereby granted, provided that this entire notice -is included in all copies of any software which is or includes a copy -or modification of this software and in all copies of the supporting -documentation for such software. - -THIS SOFTWARE IS BEING PROVIDED "AS IS", WITHOUT ANY EXPRESS OR IMPLIED -WARRANTY. IN PARTICULAR, NEITHER THE AUTHOR NOR LUCENT MAKES ANY -REPRESENTATION OR WARRANTY OF ANY KIND CONCERNING THE MERCHANTABILITY -OF THIS SOFTWARE OR ITS FITNESS FOR ANY PARTICULAR PURPOSE. - - - -jq uses parts of the open source C library "decNumber", which is distributed -under the following license: - - -ICU License - ICU 1.8.1 and later - -COPYRIGHT AND PERMISSION NOTICE - -Copyright (c) 1995-2005 International Business Machines Corporation and others -All rights reserved. - -Permission is hereby granted, free of charge, to any person obtaining a -copy of this software and associated documentation files (the -"Software"), to deal in the Software without restriction, including -without limitation the rights to use, copy, modify, merge, publish, -distribute, and/or sell copies of the Software, and to permit persons -to whom the Software is furnished to do so, provided that the above -copyright notice(s) and this permission notice appear in all copies of -the Software and that both the above copyright notice(s) and this -permission notice appear in supporting documentation. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT -OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR -HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL -INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING -FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, -NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION -WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - -Except as contained in this notice, the name of a copyright holder -shall not be used in advertising or otherwise to promote the sale, use -or other dealings in this Software without prior written authorization -of the copyright holder. - -Portions Copyright (c) 2016 Kungliga Tekniska Högskolan -(Royal Institute of Technology, Stockholm, Sweden). -All rights reserved. - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions -are met: - -1. Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS -FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE -COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, -INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) -HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, -STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED -OF THE POSSIBILITY OF SUCH DAMAGE. - diff --git a/skills/visualize-lite/design-systems/AUTHORING-FLOW.md b/skills/visualize-lite/design-systems/AUTHORING-FLOW.md deleted file mode 100644 index 7322663f..00000000 --- a/skills/visualize-lite/design-systems/AUTHORING-FLOW.md +++ /dev/null @@ -1,525 +0,0 @@ -# Authoring flow — end-to-end brand-style creation - -The operational workflow for adding a brand-style entry to the catalog (new-brand mode) or rewriting an existing entry so its files become the catalog's own work (refresh mode). Each step carries a dispatchable subagent prompt + pre-/post-dispatch checklist. - -For rules and conventions referenced from these steps — brand colours are sacrosanct, token naming conventions, pattern, dark-mode strategy, polish pass — see [`AUTHORING.md`](AUTHORING.md). This document is operational; that one is normative. - -## Context: live-first, no auto-import - -The catalog now uses a live-first authoring workflow. Brand-style entries are written from first-party brand surfaces, with self-authored `DESIGN.md` prose and token sidecars reconciled against the current public brand. - -**Brand systems are hand-authored from the live brand.** Existing entries are refreshed through the same workflow when live surfaces drift; refreshed systems keep only live-verification metadata. - -## Entry-point instruction - -For one-sentence dispatch, the user-facing instruction is one of: - -> Author a new brand-style entry for `` at `` per `AUTHORING-FLOW.md`. *(live-source mode)* - -> Refresh the brand-style entry for `` from `` per `AUTHORING-FLOW.md`. *(refresh mode)* - -> Author a new design system `` from the spec at `` and reference imagery at `` per `AUTHORING-FLOW.md`. *(spec-derived mode)* - -The main-session agent reads this file, identifies the mode from the instruction shape, locates the four steps below, runs the mode-appropriate pre-dispatch checklist, then dispatches the §Step 1 subagent. After each step completes and is verified, the main-session agent moves to the next step. - -## Three modes - -All three modes share Steps 2–4. They differ in Step 1's source: where the `DESIGN.md` content is derived from. - -- **New-brand live-source mode** — the slug doesn't exist in the catalog yet and a public live brand site exists to sample. Step 1 derives the `DESIGN.md` from a Chrome MCP / WebFetch sweep of ``. All four steps run. Most B2B-SaaS and consumer-brand entries land here (Stripe, Airbnb, Notion, Spotify, Cloudflare, etc.). -- **Refresh live-source mode** — the slug exists, has a public live brand site, and is being rewritten to drop third-party prose and capture drift. Step 1 re-derives from the live site. Step 2 (`tokens.css`) re-runs only if Step 1's drift findings warrant it. Step 3 (`preview-template.html`) typically doesn't re-run unless drift is structural (new component vocabulary surfaced). -- **Spec-derived mode** — the slug doesn't exist and has no public live brand site to sample. The system is a register-family invention (`bento`, `blueprint`, `brutalist`, `dithered`, `glassmorphism`, `luxury`, `paper-ink`, `riso`, `sketch`, `terminal`, `whitepaper`, `ide`) or a documented design system whose only public artefact is a spec or portfolio piece (e.g., a Superdesign-style design with no live deployment). Step 1 derives the `DESIGN.md` from spec text + reference imagery + design principles that the dispatcher provides, not from a live site. All four steps run; only Step 1's input set differs. - -Spec-derived mode never has a refresh variant. The "live site changed, our `DESIGN.md` is stale" failure mode doesn't apply when the source is a frozen spec. If the spec itself is revised, that's a fresh authoring cycle, not a drift-driven refresh. - ---- - -## Step 1 — DESIGN.md authoring - -### Required reading (subagent reads first) - -Read [`AUTHORING.md`](AUTHORING.md), especially: - -- **"Brand colours are sacrosanct — fix the surface, not the colour"** — governs how chromatic values are described and never modified to win contrast. -- **"Token naming conventions — the two-layer contract"** — the shadcn-semantic-core vs `--brand-*` boundary, plus the forbidden-name-shape list. -- **"Verify the live brand"** — the verification posture this step operationalises. -- **"Surface selection: look past the homepage hero"** — the brand's common canvas across multiple pages, not just the homepage marquee. -- **"Selection metadata vocabulary"** — the frontmatter routing metadata that lets agents shortlist catalog references without reading every full `DESIGN.md`. - -[`_template/DESIGN.md`](_template/DESIGN.md) is the structural reference. Open it to see the exact section / frontmatter / per-component-entry shape your output must follow. - -### Subagent prompt (substitute and dispatch) - -Copy from "Goal" through "Report" verbatim into the subagent dispatch. Substitute `` and ``. - -#### Goal - -Author a `DESIGN.md` at `visualize/design-systems//DESIGN.md`, derived entirely from live observation of `` (and any other documented first-party brand surfaces you can reach). The output must be your own original prose — not a paraphrase or adaptation of any third-party design-system catalog, blog post, or pre-existing `DESIGN.md` file in this repo. - -#### Constraint: no third-party catalog content - -Your prose must be original — derived from the live brand, not from any catalog that already characterised the brand. - -**You MUST NOT read or open:** -- `visualize/design-systems//DESIGN.md` (in refresh mode, the existing file should be moved aside before dispatch; if it's still at the canonical path, that's a bug — don't open it) -- Any other brand's `DESIGN.md` in this repo as a "reference" — cross-contamination risk -- Any file under `intra-hosting/scratch/visualize/` or similar working-doc folders -- Third-party design-system catalog content for this brand, including mirrors, archives, or web caches - -**You MUST NOT search the web for:** -- ` design system markdown` or similar phrases that would surface third-party imports - -**You MAY use:** -- Chrome MCP tools (`mcp__chrome-devtools__*` preferred) to navigate `` and brand-owned URLs, take screenshots, run `evaluate_script` for `getComputedStyle` sweeps -- `WebFetch` on `` and related first-party brand surfaces (the brand's own docs site, their design-system site if they publish one — `polaris.shopify.com`, `carbondesignsystem.com`, etc.) -- In refresh mode: `visualize/design-systems//tokens.css` and `preview-template.html` — useful as post-authoring cross-checks (not as sources for prose) -- The required-reading sections of `AUTHORING.md` - -If you're uncertain whether a source counts as "third-party catalog content," treat it as off-limits. - -#### Process - -1. **Identify the live brand entry points — sample across surface types, not just marketing.** A single marketing-surface skin is the wrong basis for canonical-canvas decisions. Most brands have multiple surfaces that may pick different polarities; the brand identity is what they ship across all of them, not what the homepage happens to render today. - - Sample across at least three surface types: - - - **Marketing surface**: `https:///` plus the common secondary marketing pages — `/product`, `/pricing`, `/enterprise`, `/customers`, `/about`. - - **Editorial / content surface**: blog, newsroom, careers — these usually render at the brand's "text-first" canvas which often differs from the marketing skin. Cloudflare ships dark marketing + white blog; Stripe ships gradient-mesh marketing + clean white docs; many brands have a similar split. - - **Product / app surface**: dashboard, app shell, console, customer portal. Often the most polarity-aware surface (theme toggle, OS-preference detection). Requires login for most brands — sample as far as the marketing landing for the app (`/dashboard`, `/app`, `/login`) and read CSS files referenced from the landing page if the actual chrome is gated. - - **First-party design system if published**: search for ` design system site:` and equivalent. If the brand publishes documented tokens with both polarities (Cloudflare's Kumo, IBM's Carbon, Shopify's Polaris, Mongo's LeafyGreen, Atlassian's, Spotify's Encore on github), **that takes precedence over the marketing-skin choice**. The marketing site is one render of the brand; the design system is what the brand ships. - - The `canonical-canvas` decision rule: light if the brand ships *only* light surfaces; dark if the brand ships *only* dark surfaces; **both** if the brand ships any mix — even one surface with a different polarity from the marketing site means `both`. Don't declare a brand single-polarity based on the marketing skin alone. - -2. **Load chrome-devtools tools first — required, do not skip.** This is the single most common subagent failure on this step: `mcp__chrome-devtools__*` tools are *deferred* (not in your default context), and `mcp__claude-in-chrome__*` tools may also be deferred but more visible from system reminders. Subagents that skip this load step default to claude-in-chrome and lose persistent screenshots, breaking Step 3's ability to re-read them. - - **Run this exact `ToolSearch` call before any browser navigation:** - - ``` - ToolSearch query="select:mcp__chrome-devtools__navigate_page,mcp__chrome-devtools__emulate,mcp__chrome-devtools__take_screenshot,mcp__chrome-devtools__evaluate_script" - ``` - - Confirm the response loads schemas for all four tools. **Do NOT use `mcp__claude-in-chrome__*` instead** — even if those tools are available in your inventory, they return inline images with no `filePath` persistence, and the catalog requires persistent capture. If `ToolSearch` doesn't surface the chrome-devtools tools (some sandboxes don't ship them), skip the screenshot capture and go directly to the `WebFetch` fallback paragraph below in this step — do not substitute claude-in-chrome. - - Then capture: - - ```sh - mcp__chrome-devtools__emulate colorScheme=light - mcp__chrome-devtools__navigate_page url="https:///" - mcp__chrome-devtools__take_screenshot filePath="temp/brand-refs/-live-light.png" fullPage=true - - mcp__chrome-devtools__emulate colorScheme=dark - mcp__chrome-devtools__navigate_page url="https:///" - mcp__chrome-devtools__take_screenshot filePath="temp/brand-refs/-live-dark.png" fullPage=true - ``` - - Repeat for at least 4 secondary surfaces. - - **If Chrome MCP is blocked** (some brands refuse headless-Chrome traffic), fall back to `WebFetch` on the same URLs. Note in §Known gaps which surfaces are screenshot-deferred. - -3. **DOM-sample for actual values.** `mcp__chrome-devtools__evaluate_script` with `getComputedStyle` over the full set of distinct components — buttons, links, CTAs, pills, badges, headers, footer chips, body text. Convert hex / rgb to OKLCH (vendored culori at `visualize/scripts/vendor/culori.mjs`). - -4. **Author `DESIGN.md`.** Copy `_template/DESIGN.md` and fill each section. Frontmatter, §1 Canonical canvas, §2 Palette, §3 Typography, §4 Component vocabulary, §5 Surface inventory, §6 Notes, §Known gaps. - - **§4 Component vocabulary** is the largest section. Document every distinct component pattern the brand ships — one entry per distinct pattern, not one entry per category. Walk this checklist on each sampled surface: - - - Buttons — primary CTA, secondary, ghost / outline, icon-only, destructive, link-styled. Each variant + each state. - - Cards — content, feature, testimonial, pricing, product. - - Forms — text input, textarea, select, checkbox, radio, switch, slider. Each with focus / error / disabled. - - Navigation — top nav (desktop + mobile), side nav, breadcrumbs, tabs, pagination, mega-menu, footer link blocks. - - Pricing — tier card layouts, comparison table rows, featured-tier treatment, calculator inputs. - - Badges / chips / pills — status, count, category, filter, tag. - - Media — image cards, video embeds, gallery thumbnails, avatar shapes. - - Editorial — blockquote, callout, alert, banner, list styles, code blocks, inline link styles. - - Date / time — date picker (default / selected / range / disabled), time picker, calendar grid. - - Feedback — toast, modal, drawer, popover, tooltip, snackbar. - - Data — table, chart wrapper, stat tile, sparkline, KPI card. - - Brand-specific — anything the brand exposes outside the categories above. Each gets its own entry. - - Target 15-25 entries for a typical brand; B2B SaaS marketing sites often have 25-35; single-product brands may have 12-20. Cite each component's live source (URL + DOM selector). - - **Mark legacy components.** If a component you'd expect isn't on the live site, record it with `status: retired (last seen: )` or `status: not-observed-`. Don't silently omit. - -5. **Expect drift, and record it.** - - Refresh mode: if a value in `tokens.css` doesn't match the live brand, flag it in §2 under "Drift vs `tokens.css`" with live evidence and a reconciliation suggestion. - - New-brand mode: there's no prior `tokens.css`; your `DESIGN.md` becomes the authoritative source for the brand's first `tokens.css`. - -6. **Original-prose self-audit.** Re-read your draft. If a sentence uses a stock phrase that feels too polished (e.g., "the brand's voltage moment"), rewrite in your own voice. - -#### Output - -A `DESIGN.md` at `visualize/design-systems//DESIGN.md`. Frontmatter: - -```yaml ---- -slug: -name: -source: live-verified -verified-at: -verified-by: subagent-via-chrome-mcp -verified-urls: - - https:/// - - https:/// - - https:/// -canonical-canvas: -selection: - mood: [, ] - tone: [, ] - formality: - density: - canonical_canvas: - best_for: | - - avoid_for: | - ---- -``` - -`selection.canonical_canvas` must exactly mirror `canonical-canvas`. Use only the vocabulary in AUTHORING.md; do not coin new mood or tone labels. - -In refresh mode: write fresh live-verification metadata; do not carry over stale provenance blocks. - -#### Report - -Under 400 words: live URLs sampled (or spec-derived inputs cited), key findings per section, anything ambiguous, and (refresh mode) the cross-check against current `tokens.css`. Do NOT compare against any previous version of the file. - -### Spec-derived variant (no live brand site) - -When the design system has no public live brand site to sample — register-family inventions like `bento` or `brutalist`, documented designs without deployed surfaces, archived design publications — Step 1's §Process changes shape. The §Goal, §Output frontmatter (with `source: spec-derived` instead of `live-verified`), §Constraint, and §Report all hold; §Process replaces the live-capture steps with spec-derived equivalents. - -**Dispatcher provides** (in the dispatch prompt or as referenced file paths): -- A spec text describing the system's principles, vocabulary, and intended register -- Reference imagery — screenshots, photographs, scans of design publications, portfolio pieces -- Optionally: a name reference (e.g., "Superdesign's High Contrast") with attribution detail for §1 of the DESIGN.md - -**Process — spec-derived variant**: - -1. **Read every provided input.** The spec text + reference images + any additional principles. Don't search the web for adjacent / related design systems; the inputs the dispatcher provided are the source. - -2. **Skip the live-capture steps** (steps 1-3 of the live-source process). There is no `` to navigate. Don't substitute another design system's live site as a proxy — that contaminates the system you're authoring with a different brand's choices. - -3. **Derive the palette from the spec + imagery.** If the spec declares hex values, use those. If the spec describes the system principally through imagery, sample colours from the reference images (use image-inspection tools or note the values inline). Mark each value with its source: `Live: spec §X` or `Live: reference image temp/refs/-.png — visible element`. Convert hex to OKLCH via vendored culori. - -4. **Derive typography + components similarly.** From the spec when declared, from imagery when not. The §4 Component vocabulary exhaustive expectation still holds — if the spec doesn't enumerate components, the author derives a reasonable vocabulary from the principles (e.g., "Brutalist register implies hard-edged buttons with no hover lift; surface elevation is hairline-only"). - -5. **Drift section is irrelevant** — there is no `tokens.css` to drift against in new-brand mode, and no live brand to drift away from. - -6. **Original-prose self-audit still applies.** Even with a spec as input, don't paraphrase any third-party catalog's writeup of the same system. Write the prose in your own voice from the provided source materials. - -**Output frontmatter — spec-derived variant**: - -```yaml ---- -slug: -name: -source: spec-derived -verified-at: -verified-by: subagent -reference-materials: - - spec: - - imagery: - - - - - - principles: -canonical-canvas: -selection: - mood: [, ] - tone: [, ] - formality: - density: - canonical_canvas: - best_for: | - - avoid_for: | - ---- -``` - -Replace `verified-urls` with `reference-materials`. The `verified-by` value can drop the `-via-chrome-mcp` qualifier since Chrome MCP wasn't used. - -### Pre-/post-dispatch checklist (user-side) - -**New-brand live-source mode**: -1. `mkdir -p visualize/design-systems/` -2. Confirm folder is empty. -3. Dispatch the subagent with the prompt above. -4. After report: read the new `DESIGN.md`, verify live citations, and run `node dev-scripts/catalog-index.mjs --check` after regenerating the index if needed. Commit. Proceed to Step 2. - -**Refresh live-source mode**: -1. `mkdir -p temp/design-system-originals && mv visualize/design-systems//DESIGN.md temp/design-system-originals/-DESIGN.md` -2. Confirm only `tokens.css` and `preview-template.html` remain in the brand folder. -3. Dispatch the subagent with the prompt above. -4. After report: diff against the moved-aside original to confirm no verbatim sections survived, and run `node dev-scripts/catalog-index.mjs --check` after regenerating the index if needed. If clean: commit, delete the moved-aside copy, drop the brand from `visualize/NOTICES.md`'s third-party-attribution list. If contaminated: discuss, refine, retry. - -**Spec-derived mode**: -1. `mkdir -p visualize/design-systems/` -2. Confirm folder is empty. -3. Gather the spec text + reference imagery. Place imagery under `temp/refs//` or another stable path. Note all source paths. -4. Dispatch the subagent with the prompt above *plus* the spec inputs (inline text + file paths to the imagery + any additional principles). Tell the subagent explicitly: "Spec-derived mode, no live brand site. Sources are: \, \, \." -5. After report: read the new `DESIGN.md`, verify the citations point at the spec / imagery (not at hypothetical URLs the agent might have synthesised), and run `node dev-scripts/catalog-index.mjs --check` after regenerating the index if needed. Commit. Proceed to Step 2. - ---- - -## Step 2 — tokens.css authoring - -### Required reading (subagent reads first) - -Read [`AUTHORING.md`](AUTHORING.md), especially: - -- **"Brand colours are sacrosanct — fix the surface, not the colour"** — the rule that governs every chromatic token value. No darkening for contrast, no synthesizing variants. -- **"Token naming conventions — the two-layer contract"** — shadcn-semantic-core slots are untouchable; brand-extras follow the surface-named taxonomy and the forbidden-name-shape list. -- **"Dark-mode strategy"** — most brands ship single chromatic identity across modes; only canvas + neutrals reliably flip. - -[`_template/tokens.css`](_template/tokens.css) is the scaffold. - -### Subagent prompt (substitute and dispatch) - -#### Goal - -Author a `tokens.css` at `visualize/design-systems//tokens.css`, derived entirely from the palette + typography documented in the brand's `DESIGN.md` (which was authored against the live brand in Step 1). - -#### Inputs - -- `visualize/design-systems//DESIGN.md` — the authoritative palette + typography for this brand, with each value cited against a live source -- `visualize/design-systems/_template/tokens.css` — the scaffold carrying shadcn-core slots + brand-extras taxonomy + commented examples - -#### Constraint: no synthesis - -Every chromatic value in the output must trace to a `DESIGN.md` §2 Palette entry. No darker/lighter variants invented for contrast. No `--brand--ink`, `--brand--on-dark`, `--brand--deep` / `-pressed` / `-N00` tokens unless the brand actually documents that ladder and `DESIGN.md` cites the source. - -#### Process - -1. **Read the brand's `DESIGN.md`.** Build a mental table of (token name → OKLCH value → live source). Note which values are observed-on-live vs `(synthesised)`-utility-neutrals. - -2. **Copy the scaffold.** `cp visualize/design-systems/_template/tokens.css visualize/design-systems//tokens.css`. The scaffold's structure stays — shadcn-core slots → brand-extras → dark-mode block → @media block. Fill placeholders, don't restructure. - -3. **Fill shadcn-semantic-core slots** in `:root` from `DESIGN.md` §2 Canvas + neutrals + Brand primary. Set the same OKLCH value on every primary-family token (`--primary`, `--ring`, `--sidebar-primary`, `--sidebar-ring`). Update the comment at the top of `tokens.css` with the brand source URL + verification date. - -4. **Fill brand-extras** from `DESIGN.md` §2's documented secondary brand colours, polarity-locked surfaces, hairlines, and brand-specific lineage tokens. Surface-named only — verify against AUTHORING.md "Token naming conventions" forbidden-name-shape list before adding any token. - -5. **Fill the `[data-theme="dark"]` block** — mirror the canvas + neutrals from `DESIGN.md`'s dark-mode citations. **Default chromatic brand colours stay at `:root` value** (don't override unless the brand documents a dark variant — IBM Carbon's Blue 60 → Blue 50 is the canonical example). - -6. **Mirror the `@media (prefers-color-scheme: dark)` block** verbatim from the `[data-theme="dark"]` block. - -7. **Run audit greps before considering the file done:** - - ```sh - # Forbidden name shapes - grep -nE '^\s+--brand-(red|blue|green|coral|amber|sale|success|rausch|orange|purple|violet|teal|cyan|magenta|pink|yellow|lime)-(ink|on-dark|deep|pressed|darker|lighter|N?[0-9]{3,4}|[0-9])\b' visualize/design-systems//tokens.css - - # Comment smells (contrast math justifying a value) - grep -nE '\b(AA|WCAG|[0-9]\.[0-9]+:1|lifted (from|to)|darkened (from|to)|brighten\w* (for|to)|sub-AA)\b' visualize/design-systems//tokens.css - ``` - - Both should return empty unless a value cites a documented brand exception (Carbon-style theme swap, generic-theme tuning). - -8. **Pre-flight the three known sub-AA contrast pairs.** These pairs fail in ~90% of fresh tokens.css files when not checked at this step — `_template/tokens.css` carries inline comments next to each slot warning about the trap. The fix is upstream from Step 3's detect-clean and from the contrast-fix sweep that catches the pattern after the fact; doing it here prevents both downstream costs. - - For each pair, compute the contrast ratio at the OKLCH values you set in `:root` and in `[data-theme="dark"]`: - - | Pair | Floor | Common failure | First-line fix | - |---|---|---|---| - | `--muted-foreground` × `--background` | 4.5:1 (body) | shadcn-default `oklch(0.55 0 0)` on near-black canvas lands ~4.17:1 | retune `--muted-foreground` (chroma ≈ 0 = utility-neutral, retunable per AUTHORING.md) until clear | - | `--primary-foreground` × `--primary` | 4.5:1 (body) | cream-on-red (`oklch(0.93 0.03 93)` × `oklch(0.55 0.22 25)`) lands ~4.1:1 | swap `--primary-foreground` to white (utility-neutral, NOT a brand chroma) | - | `--destructive-foreground` × `--destructive` | 4.5:1 (body) | same shape as primary | same fix | - - You can compute ratios inline in JS with vendored culori (`/Users/carl/Development/visualize/visualize/scripts/vendor/culori.mjs`) by converting OKLCH → sRGB → WCAG relative-luminance, or eyeball the OKLCH lightness gap (|L_fg − L_bg| ≥ 0.50 is a reasonable proxy for AA body on neutral-vs-neutral pairs). When a pair lands sub-AA: - - **First** try retuning the chroma-0 utility neutral (`--muted-foreground`, `--primary-foreground`, `--destructive-foreground`) — AUTHORING.md §"Brand identity vs utility neutrals" explicitly allows this. - - **Only if the foreground is brand-chromatic** (rare — typically chromatic foregrounds are tinted-paper registers where the chromatic value IS the brand mark), defer the fix to Step 3 where you can wrap consumers in scoped CSS custom properties or qualify them at large-text-bold floor (≥19px @ 700). DO NOT retune the brand chroma. - - Report your pre-flight findings in the §Report section: each pair, the OKLCH values, the computed ratio (or "AA-clear by lightness gap"), and any utility-neutral retunes applied. - -#### Output - -A `tokens.css` at `visualize/design-systems//tokens.css`. The header comment cites the brand source + verification date + the `DESIGN.md` companion file. - -#### Report - -Under 200 words: list of shadcn-core slot values filled, list of brand-extras tokens defined, dark-mode strategy decision (mirror :root chromatic / brand documents a swap / etc.), and the audit-grep results. - -### Pre-/post-dispatch checklist (user-side) - -1. Confirm Step 1 has shipped — `DESIGN.md` exists at the canonical path and has been verified + committed. -2. Confirm `_template/tokens.css` is current. -3. Dispatch the subagent with the prompt above. -4. After report: read the new `tokens.css`, verify the forbidden-name-shape grep is clean, rebuild previews (`bash dev-scripts/build-previews.sh`), screenshot-verify (`bash dev-scripts/screenshot-previews.sh `), open the resulting PNGs and confirm text contrast still reads against the rebuilt surfaces — Step 2 value swaps can break contrast on the existing preview-template. Commit only after visual verification. Proceed to Step 3. - ---- - -## Step 3 — preview-template.html authoring - -### Required reading (subagent reads first) - -Read [`AUTHORING.md`](AUTHORING.md) in full. The most load-bearing sections for this step: - -- **"Pattern: token-surface showcase, not site clone"** — the canonical shape (header → one signature mockup → swatches → type → components → footer). The hardest rule: ONE brand-flavoured signature mockup, token demos for everything else. -- **"Content shape: Halcyon is a name only"** — Halcyon naming convention, no the host product surface references, no brand-X lift in mockup content. -- **"Dark-mode strategy"** — polarity-locked tokens, single-polarity brands, dark-canonical brands. -- **"Detect-clean checklist"** — `detect.mjs --strict` and `browser-contrast.mjs` both clean before declaring done. -- **"Polish pass"** — mechanical floor + quality dimensions before shipping. - -There is no `_template/preview-template.html` scaffold — preview shells are too brand-specific for a single starter. Study 2-3 existing brand previews of similar register as compositional references (e.g., for a developer-tool brand, look at `linear-style/`, `vercel-style/`; for a consumer brand, look at `airbnb-style/`, `nike-style/`). Reference the **structural composition** — never copy prose, never copy mockup content verbatim, never use another brand's brand-flavoured signature surface. - -### Subagent prompt (substitute and dispatch) - -#### Goal - -Author a `preview-template.html` at `visualize/design-systems//preview-template.html` that renders a token-surface showcase for `` — one signature mockup that captures the brand's recognizability, plus token demos for everything else. The shell consumes the brand's `tokens.css` and the placeholder substitution machinery in `dev-scripts/build-previews.sh`. - -#### Inputs - -- `visualize/design-systems//DESIGN.md` — the brand's design vocabulary -- `visualize/design-systems//tokens.css` — the token values the template will consume -- 2-3 adjacent brand previews of similar register, for compositional patterns only - -#### Constraints - -- **One brand-flavoured signature mockup, plus token demos for the rest.** Not three feature sections from the brand's homepage. Pick the single surface that defines recognizability (per `DESIGN.md` §4 — the most distinct component pattern the brand ships). -- **Halcyon naming convention** for mockup content. The placeholder `__DESIGN_SYSTEM_NAME__` is substituted at build time. No real customer names, no the host product brand names in copy. -- **No brand-X lift.** Even if the brand's live site uses real product names, model numbers, customer logos — invented neutral content only. -- **Six type roles maximum** in the typography demo. The brand's `DESIGN.md` §3 lists the documented roles. -- **All colour usage routes through tokens** — `var(--primary)`, `var(--brand-canvas-night)`, etc. No literal hex / rgb / oklch in CSS unless it's a documented scoped local custom property (rare; see `spacex-style` / `ferrari-style` for legitimate uses). - -#### Process - -1. **Read `DESIGN.md` + `tokens.css`.** Identify: the canonical canvas (light / dark / both), the brand's voltage moment (the §4 component that's most distinct), the secondary brand colours, the polarity-locked surfaces. - -2. **Pick the signature mockup.** One only. Examples from the catalog: Notion's workspace kanban, Cursor's IDE mockup, Raycast's command palette, Mistral's sunset gradient hero, PostHog's file-tree workspace, Stripe's gradient mesh hero. Whichever component pattern from `DESIGN.md` §4 carries the brand's recognizability gets one full section in the preview. - -3. **Compose the canonical shape:** - - ``` - header nav + hero (brand-flavoured decorative moment, no content sections inside) - section one signature mockup (the brand's defining surface) - section colour swatch band (token demo) - section surface elevation ladder (token demo) - section typography roles (6 roles max) - section component vocabulary (buttons, badges, inputs, cards in brand register) - section optional stats / KPI band (token demo) - footer brand-flavoured closing band - ``` - -4. **Token discipline.** Every chromatic surface routes through a `tokens.css` var. The build script inlines `tokens.css` at render time and substitutes placeholders (`__DESIGN_SYSTEM_NAME__`, `__ROOT_ATTR__`, etc.). - -5. **Iterate against the polish pass** (AUTHORING.md): focus rings, touch-target floors, reduced-motion overrides, tinted neutrals, hairline-vs-shadow elevation, sticky-overflow handling. - -6. **Run detect-clean AND screenshot-verify** before declaring done: - - ```sh - bash dev-scripts/build-previews.sh - node visualize/scripts/detect.mjs --strict visualize/design-systems//preview.html visualize/design-systems//preview-dark.html - node visualize/scripts/browser-contrast.mjs visualize/design-systems//preview.html visualize/design-systems//preview-dark.html - bash dev-scripts/screenshot-previews.sh - ``` - - `detect.mjs --strict`: must return `exit 0 · 0 error`. - - `browser-contrast.mjs`: `exit 0 · 0 error` is necessary but **not sufficient**. axe returns `needs-review` (warnings) on gradients, pseudo-element backgrounds, single-char glyphs, and `→` arrows — those are gaps where axe couldn't measure, not confirmations that contrast is OK. For every `needs-review` warning whose selector contains text (`h1`, `p`, button labels, `figcaption`, eyebrow / lede / headline classes), open the rendered PNG produced by `screenshot-previews.sh` and visually check the text against the actual background. Reposition mesh stops, extend bottom-fades, or add washes if text bleeds into gradient. - - `screenshot-previews.sh`: produces `/tmp/preview-shots/-{light,dark}.png`. Open both and eyeball the hero region, the signature mockup, and any pseudo-element-decorated text. Decorative pseudo-elements without text content (single-char glyphs, arrow markers) are safe to leave; text-containing elements are not. - -#### Output - -A `preview-template.html` at `visualize/design-systems//preview-template.html`. Header carries a one-line comment citing the brand source and the signature mockup choice. - -#### Report - -Under 300 words: signature mockup chosen + rationale (why this is the brand's voltage moment), token discipline check (no literal hex / rgb anywhere; everything routes through tokens), detect-clean result, polish-pass items addressed. - -### Pre-/post-dispatch checklist (user-side) - -1. Confirm Steps 1 + 2 have shipped — `DESIGN.md` + `tokens.css` exist and are verified. -2. Dispatch the subagent with the prompt above. -3. After report: read the new `preview-template.html`, screenshot via `dev-scripts/screenshot-previews.sh `, open BOTH PNGs (`/tmp/preview-shots/-{light,dark}.png`), and visually verify: hero text reads against the actual rendered background (not just against axe's measurement, which skips gradients + pseudo-elements), the signature mockup matches the brand's recognizability, no text bleeds into mesh / gradient / glassmorphism surfaces, no contrast cliffs at section transitions. If contrast bleeds anywhere a text element sits, that's a fix-before-shipping. If the signature mockup misses the brand, send feedback and retry. Proceed to Step 4 only after visual verification clears. - ---- - -## Step 4 — wire-up - -Mechanical steps after the three artefacts ship. Not a subagent prompt; the main-session agent runs these directly. - -1. **Add the brand to `dev-scripts/build-previews.sh`'s `DESIGN_SYSTEMS` array.** Keep the array alphabetical. - -2. **Build:** - - ```sh - bash dev-scripts/build-previews.sh - ``` - - Generates `preview.html` and `preview-dark.html` for every brand including the new one. - -3. **Catalog-wide contrast check:** - - ```sh - node visualize/scripts/browser-contrast.mjs --strict visualize/design-systems/*/preview.html visualize/design-systems/*/preview-dark.html - ``` - - Must return `exit 0 · 0 error`. If the new brand introduces errors, investigate at the brand's `tokens.css` or `preview-template.html` level — never globally relax the check. - -4. **Sync mounts:** - - ```sh - bash bin/sync-mounts.sh - ``` - -5. **Commit** with explicit paths (4 files × 4 mounts = 16 paths for a new brand, plus `dev-scripts/build-previews.sh`): - - ```sh - git commit -m "visualize: — preview + tokens + DESIGN" -- \ - visualize/design-systems//DESIGN.md \ - visualize/design-systems//tokens.css \ - visualize/design-systems//preview-template.html \ - visualize/design-systems//preview.html \ - visualize/design-systems//preview-dark.html \ - hermes/design/visualize/design-systems//... \ - pi/agent/skills/visualize/design-systems//... \ - skills/visualize/design-systems//... \ - dev-scripts/build-previews.sh - ``` - - Per-brand commit cadence: one commit per brand for clean blame + revert grain. - -## Refresh mode summary - -For an existing brand entry: - -- **Step 1 always runs** — the whole point is to author a fresh `DESIGN.md` from live. Pre-dispatch moves the old `DESIGN.md` to `temp/design-system-originals/`; post-dispatch diffs to confirm originality. -- **Step 2 runs only if Step 1 surfaces drift** — the `DESIGN.md`'s "Drift vs `tokens.css`" subsection lists discrepancies; if any are substantive (real brand-colour changes, not noise), re-author `tokens.css` per Step 2's prompt. -- **Step 3 rarely runs** — the brand's `preview-template.html` is typically still valid from the prior audit cycle. Re-author only if Step 1 surfaces structural changes (new signature surface on the live brand, retired component the preview currently demos prominently). -- **Step 4 runs** — sync mounts, commit. -- **Post-refresh attribution drop**: once `DESIGN.md` is original prose, drop the brand from `visualize/NOTICES.md`'s third-party attribution list. - -## Batch authoring - -When a single dispatcher cycle ships N>1 design systems (typical of spec-derived batches — a curated set of register inventions, a Superdesign import, etc.), the per-system Steps 1-4 contract still applies in full to each system. **The temptation when batching is to amortise expensive steps across systems** — run build once across the whole batch, run detect-clean once at the end, skip the per-system polish pass. Don't. - -### The shortcut that doesn't work - -The specific shortcut that fails: telling each per-system subagent *"Do NOT run build or detect — dispatcher runs both centrally after all N subagents complete."* The reasoning is plausible (avoid N subagents racing on `dev-scripts/build-previews.sh` and the build outputs), and it is exactly what produced the May 2026 batch where 18 of 20 systems shipped with the same shape of `--muted-foreground × --background` sub-AA error in lockstep. The error class is common (`_template/tokens.css` ships shadcn-style muted-foreground at `oklch(0.55 0 0)` which lands ~4.17:1 on near-black canvases), `detect-clean` catches it deterministically per-system, and skipping the per-subagent loop ate the only signal that would have caught it before the batch landed. - -The skip saves ~30 seconds of subagent wall-clock per system. It costs an 18-system contrast-fix sweep dispatched after the fact. Net cost ~30× the original spend. - -### The rule - -**Each per-system subagent runs its own Step 3 detect-clean and contrast check before declaring done.** The race condition is real but the fix is not skipping the check — the fix is letting each subagent edit its `preview-template.html`, then invoking the build for *only its own slug* via a single-element `DESIGN_SYSTEMS` array override OR by inlining the render function inline-bash. Either pattern keeps the per-subagent feedback loop intact without races. - -When the build-script-race risk is genuinely intractable (large N, slow disks, hooks that fight back), the fallback is **dispatch in serialized batches of 1-2 systems at a time, not full parallelism**. Slower wall-clock, same correctness. - -### Pre-batch checklist - -Before dispatching a batch (any N>3), confirm: - -1. **Each subagent prompt includes its own Step 3 detect-clean step.** Grep your dispatch template for "do not run build" or "skip detect" — if it's there, you've already failed. -2. **Step 2 contrast pre-flight is in each per-subagent prompt.** Step 2 §8 in this doc enumerates the three pairs (`--muted-foreground × --background`, `--primary-foreground × --primary`, `--destructive-foreground × --destructive`). The single-system flow catches these; the batch flow only catches them if the per-subagent prompt requires the pre-flight explicitly. -3. **The `_template/tokens.css` is current** — it carries inline trap-warnings next to the three known sub-AA pairs. Subagents that read the scaffold (which they all do) see the warnings before they pick OKLCH values. -4. **Central Step 4 wire-up runs once at the end** (build all, sync mounts, commit). This is the only step that legitimately amortises across the batch. - -### Recovery when the rule was broken - -If a batch shipped with the per-subagent detect-clean skipped and `browser-contrast.mjs --strict` returns errors across multiple systems: - -1. Identify the dominant error pattern (the May 2026 batch was uniformly `--muted-foreground` × `--background` at body sizes — one pattern across 18 of 20 systems). -2. Dispatch one fix-contrast subagent per affected system, in parallel — each runs the contrast check, identifies failing selectors, applies "fix the surface, not the colour" (re-route consumers to a higher-contrast token, bump font-size to qualify large-text floor, retune chroma-0 utility neutrals). Brand chroma stays sacrosanct. -3. Central rebuild + catalog-wide contrast check. -4. Manual surgical fixes for the long tail of errors detect-clean still surfaces. - -This is the recovery path. Use it when you've already broken the rule. Use the rule (each subagent runs its own check) when you can. diff --git a/skills/visualize-lite/design-systems/AUTHORING.md b/skills/visualize-lite/design-systems/AUTHORING.md deleted file mode 100644 index d55ecc92..00000000 --- a/skills/visualize-lite/design-systems/AUTHORING.md +++ /dev/null @@ -1,431 +0,0 @@ -# Authoring per-system preview-templates - -A companion reference for adding a custom `preview-template.html` to a design system under `visualize/design-systems//`. Pairs with the "Adding a per-system preview-template" section in [`CONTRIBUTING.md`](../../CONTRIBUTING.md), which carries the file-drop + placeholder + build-script details. This doc covers what makes a good shell: pattern references, dark-mode strategy, and the detect-clean checklist. - -**For the end-to-end workflow to add a new brand or refresh an existing entry — including dispatchable subagent prompts for each step — see [`AUTHORING-FLOW.md`](AUTHORING-FLOW.md).** This document covers the rules and conventions those steps reference: pattern, brand colours sacrosanct, token naming, dark-mode strategy, polish pass. - -## Pattern: token-surface showcase, not site clone - -Each per-system shell is a **token-surface showcase rendered on Halcyon-themed neutral content** — not a clone of the brand's live marketing page. The single hardest rule: - -> **One brand-flavored signature mockup, plus token demos for everything else.** A preview with three or more page-shape sections from the brand's homepage (workspace mockup + feature card grid + assistant banner + CTA card) is a re-skin, not a showcase. Pick the *one* surface that defines the brand's recognizability (Notion's workspace kanban, Cursor's IDE mockup, Raycast's command palette, Mistral's sunset gradient hero, PostHog's file-tree workspace, Stripe's gradient mesh hero). Everything else is token demos — color swatches, surface ladder, typography roles, component vocabulary, optional stats band — plus brand-flavored decorative bookends (hero + closing band). - -The canonical shape: - -``` -header nav + hero (brand-flavored decorative moment, no content sections inside) -section one signature mockup (the brand's defining surface, ONE only) -section color swatch band (token demo) -section surface ladder (token demo) -section typography roles (token demo) -section component vocabulary (token demo) -section brand-flavored decorative closer (optional — cream interlude, sunset stripe, etc.) -footer standard -``` - -Sections to avoid as page-shape clones: - -- **Customer-logo strips** — never include. Lifts brand-X's actual customer logos OR fakes a customer-validation moment that doesn't belong in a token preview. -- **Feature card grids** ("3 features with icons + headlines + body") — generic AI-landing slop. Replace with color/surface/type/component demos. -- **Marketing-copy assistant banners** ("Ask your on-demand assistants") — clones brand-X's marketing surface. -- **CTA cards / closing CTAs** ("Get started with Halcyon") — page-marketing-shape. Use a brand-flavored decorative band instead. -- **Pricing tables** — see the "Pricing details: never the host product's surface" rule below. -- **Deployed-in-production customer-story bands** — clones brand-X's customer-proof pattern. - -**Pattern reference templates** — read at least one before authoring: - -| Template | Notable for | -|---|---| -| `stripe-style/preview-template.html` | Canonical token-showcase shape — gradient-mesh hero + stats + colors + type + components + cream interlude + footer. NO customer logos, NO feature cards, NO pricing, NO CTA card. | -| `claude-style/preview-template.html` | Multi-surface (cream → coral callout → dark code mockup → dark footer). | -| `editorial/preview-template.html` | Minimal native pattern at 238 lines if the brand calls for restraint. | -| `mistral-ai-style/preview-template.html` | The session 2026-05-23 rework — sunset hero (signature) + color swatches + surface ladder + pricing (genericized) + type/components + sunset closing stripe (signature). | - -Don't reuse real product copy from the brand's live site. Headlines like "Edge revenue, settled at the close of business." (Stripe register) or "An editorial canvas for agent-assisted work." (Claude register) are brand-flavored aphorisms on Halcyon content — that's the bar. - -### Pricing details: never the host product's surface - -If a preview includes pricing (which is borderline page-marketing-shape — consider dropping entirely), the tier names, prices, and feature lists must NOT reveal the host product's actual product surface: - -- **Tier names**: don't use the host product's literal Free / Solo / Pro / Enterprise. Use abstract: Hobby / Team / Org-wide, or Personal / Standard / Custom, or genuinely brand-specific names. -- **Prices**: don't use the host product's $49. Either omit a number (use "Free / Standard / Custom") or use clearly illustrative values that vary across brands. -- **Feature lists**: NEVER use the host product's actual tier limits: - - ❌ "1 GB / 10 GB / 100 GB storage" (the host product's storage ladder) - - ❌ "1 user / Up to 10 users / Unlimited users" (the host product's seat model — though the host product is per-workspace not per-seat, the per-user ladder is still recognizable) - - ❌ "Google + Microsoft SSO" + "SAML + SCIM" + "Audit log" + "Custom domains" as the Pro/Enterprise differentiators (the host product's exact tier features) - - ✅ "Personal use" / "Team workspaces" / "Org-wide deployment" - - ✅ "Standard quota" / "Increased quota" / "Custom quota + region" - - ✅ "Best-effort support" / "Priority support" / "Dedicated success" - - ✅ "Production SLA" / "Procurement-ready paperwork" (generic-enterprise-shape) - -If the brand's actual pricing tier names and features are recognizable (Notion's Free / Plus / Business / Enterprise; Cursor's Hobby / Individual / Teams; Raycast's Free / Pro / Pro for Teams) — also don't use those. They're lifts (per the "no brand-X verbatim content" rule). - -Generic-SaaS-shape pricing that resembles neither the host product nor the brand-X site is the only safe shape. **When in doubt, drop the pricing section entirely** and replace with another token demo (extra color swatches, expanded component vocabulary, a brand-signature decorative element). - -## Verify the live brand — DESIGN.md may have drifted - -`DESIGN.md` is a point-in-time live verification. The brand can evolve after verification: new default color scheme, different CTA register, redesigned surfaces, renamed components. Cursor's `DESIGN.md` documents `button-primary` as Cursor Orange, but cursor.com today uses dark-ink `button-download` as the actual primary CTA — orange is wordmark-only ("used scarcely"). Notion's `DESIGN.md` documents the navy hero band as the homepage signature, but five of six documented pages use white/cream canvas. Both gaps are silent in the spec; visible in the live brand. - -**Always capture the homepage in both light and dark browser preferences** — sites with both modes will misread as dark-canonical (or vice versa) if you only sample one: - -```sh -mcp__chrome-devtools__emulate colorScheme=light -mcp__chrome-devtools__navigate_page url="https://.com" -mcp__chrome-devtools__take_screenshot filePath="temp/brand-refs/-live-light.png" fullPage=true - -mcp__chrome-devtools__emulate colorScheme=dark -mcp__chrome-devtools__navigate_page url="https://.com" -mcp__chrome-devtools__take_screenshot filePath="temp/brand-refs/-live-dark.png" fullPage=true -``` - -**When you find drift**, override the tokens / template to match the live brand. Document the divergence in a comment inside `tokens.css` at the override site so the next agent knows why the file disagrees with `DESIGN.md`. Don't edit `DESIGN.md` itself — frontmatter preserves the upstream import per the gotcha at the bottom of this doc. Intentional drift, surfaced and signed. - -## Brand colours are sacrosanct — fix the surface, not the colour - -A brand colour that fails AA in a given surface is not a colour bug, it's a surface bug. The OKLCH values in `tokens.css` come from `DESIGN.md` (or from the live-brand override you just signed in) and *are* the brand identity. Do NOT: - -- Lift / darken / retint `--primary`, `--accent`, or any chromatic `--brand-*` token in `:root` or `[data-theme="dark"]` to win contrast. Ferrari's Rosso Corsa, Stripe's purple, Notion's link blue, PostHog's stat-card accents, Nike's red/green, MongoDB's pressed-green are not yours to retune. -- Synthesise variant tokens (`--brand-X-ink`, `--brand-X-on-dark`, `--brand-eyebrow-ink`) that hold the brand colour at a lifted / darkened lightness — just so the brand colour can be used as text on dark where it would otherwise fail. That's the same mistake one indirection later: you've still changed the brand's deployed colour, the new token name just hides it. - -**The fix is the surface.** Re-home the consuming element to `--foreground` (white on dark, near-black on light), or route to a documented secondary brand colour (Ferrari Cavallino yellow, Notion's brand-charcoal, etc.). If the brand doesn't use that colour as text on dark on its live site, the preview shouldn't either. - -**Brand identity vs. utility neutrals.** Lifting near-zero-chroma gray utility tokens for AA is fine — `--brand-muted-soft`, `--brand-mute`, `--brand-stone`, `--brand-slate`, `--brand-ash`, `--brand-ink-tertiary/subtle/mute-N`, `--brand-on-dark-faint`, `--primary-foreground` swaps from white→dark when the primary is light. They're muted body-text neutrals or contrast-pair choices, not brand identity. The boundary: chroma > ~0.05 = brand identity, hands off; chroma ≈ 0 = utility neutral, retune for AA as needed. - -**Verify the live site before treating a contrast failure as "needs a brighter X."** Open the brand's website with `mcp__chrome-devtools__*` and look at where they actually put that colour. Ferrari Rosso Corsa is exposed as `--f-color-accent-100` and a gradient stop, never as body text on dark — we discovered this only after first lifting `--primary` (wrong) then synthesising `--brand-red-ink` (also wrong); the clean fix was to route eyebrows / hover / standings highlights to white and Cavallino yellow instead. The same audit applies before you reach for any brand-colour modification. - -**When a contrast failure persists after re-homing**, the question is "what does the brand do here?", not "how do I bend the colour." Options that don't touch brand identity: -- Add a background fill (red CTA card with white text on dark, instead of red text on dark). -- Use a secondary brand colour at full lightness. -- Use `--foreground` and carry differentiation via weight, size, tracking, or underline. -- Use a brand-documented neutral (`--brand-on-light` / `--brand-on-dark`). - -## Token naming conventions — the two-layer contract - -Every brand in this catalog ships `tokens.css` with two layers. Keep them clean and the audit-grep patterns below stay useful. - -### Layer 1 — shadcn-semantic core (names are the contract, values are yours) - -Universal primitive vocabulary that downstream shadcn components read directly. Brand-agnostic NAMES, brand-specific VALUES. Never rename, add to, or delete from this set per-brand. Setting different OKLCH values per brand is exactly what these slots are for. - -| Group | Tokens | -|---|---| -| Surfaces | `--background`, `--foreground`, `--card`, `--card-foreground`, `--popover`, `--popover-foreground`, `--muted`, `--muted-foreground`, `--accent`, `--accent-foreground` | -| Interactive | `--primary`, `--primary-foreground`, `--secondary`, `--secondary-foreground`, `--destructive`, `--destructive-foreground` | -| Chrome | `--border`, `--input`, `--ring` | -| Sidebar | `--sidebar`, `--sidebar-foreground`, `--sidebar-primary`, `--sidebar-primary-foreground`, `--sidebar-accent`, `--sidebar-accent-foreground`, `--sidebar-border`, `--sidebar-ring` | -| Data viz | `--chart-1` through `--chart-5` | -| Radii | `--radius` plus `--radius-sm`, `--radius-md`, `--radius-lg`, `--radius-xl` | - -### Layer 2 — brand-extras (`--brand-*`, surface-named) - -The brand's own design-system vocabulary. Lightness ladders, secondary palettes, polarity-locked surfaces, hairlines, semantic palette, decorative tokens — whatever shadcn-core doesn't model. - -Token names encode **surface role**, never colour-with-modifier. - -| Suffix family | Meaning | Examples | -|---|---|---| -| `-bg`, `-canvas`, `-canvas-{night,light}`, `-surface-N`, `-surface-{elevated,strong,soft}` | a surface fill | `--brand-canvas-night`, `--brand-surface-1`, `--brand-surface-elevated` | -| `-ink`, `-ink-{strong,mute,subtle}`, `-on-dark{,-soft,-faint}`, `-on-light{,-strong}` | a foreground locked to a canvas polarity (role, not colour) | `--brand-on-dark`, `--brand-on-light`, `--brand-ink-strong`, `--brand-ink-mute` | -| `-hairline`, `-hairline-{soft,strong}` | divider / border weight | `--brand-hairline-soft` | -| `-radius-*` | radii in the brand's scale | `--brand-radius-pill`, `--brand-radius-xs` | -| `-body`, `-body-{muted,strong}` | body text colour | `--brand-body`, `--brand-body-muted` | -| `-accent-` | documented secondary brand colour at full chroma | `--brand-accent-yellow`, `--brand-action-blue` | -| Brand-primary lineage | brand's own documented ladder for the primary hue | `--brand-primary-deep`, `--brand-primary-press`, `--brand-primary-soft` | -| `-mute`, `-muted-soft`, `-stone`, `-slate`, `-ash` (chroma ≈ 0) | utility neutrals for muted body text — these CAN be AA-tuned per the rule | `--brand-muted-soft`, `--brand-stone` | - -### Forbidden name shapes (the synthesis anti-pattern in name form) - -Don't introduce these. Audit catches them. - -- **`--brand--ink`** paired with a chromatic hue: `--brand-coral-ink`, `--brand-rausch-ink`, `--brand-red-ink`. The `-ink` suffix on a colour name signals "brand colour at a different lightness for text use" — that's the synthesis pattern. (Hue-anchored `--brand-ink-mute` / `--brand-ink-strong` are fine: they name the body-text role, not a chromatic modification.) -- **`--brand--on-dark`**: `--brand-blue-on-dark`, `--brand-coral-on-dark`. Names "the brand colour but lifted for dark canvases." Same pattern. -- **`--brand--deep`** paired with a chromatic hue: `--brand-blue-deep`, `--brand-brand-green-deep`. Almost always a synthesised "darker variant" with no documented brand-doc value. (`--brand-primary-deep` is fine when the brand actually documents the ladder — see stripe-style.) -- **Surface-named but colour-modified**: `--brand-eyebrow-ink`, `--brand-stat-delta-up`, `--brand-stat-delta-down`. The surface name is right; baking a lifted/darkened colour value into a single-purpose token is the smell. - -### Auditable greps - -Forbidden name shapes: - -```sh -grep -nE '\-\b(red|blue|green|coral|amber|sale|success|rausch|orange|purple|violet|teal|cyan|magenta|pink|yellow|lime)-\b(ink|on-dark|deep|pressed|darker|lighter)\b' \ - visualize/design-systems/*/tokens.css -``` - -Comment smells (token value justified by contrast math — the rationale-form of the same anti-pattern): - -```sh -grep -nE '\b(AA|WCAG|[0-9]\.[0-9]+:1|lifted (from|to)|darkened (from|to)|brighten\w* (for|to)|≥4\.5:1|≥3:1|sub-AA)\b' \ - visualize/design-systems/*/tokens.css -``` - -Both should return empty across the catalog, with two known legitimate exceptions documented in their tokens.css headers: -- **ibm-style** cites Carbon's documented Blue 60 → Blue 50 theme swap (`carbon-design-system/carbon packages/themes/src/g100.js`). -- **ide** declares itself a generic synthesised theme with no external brand authority. - -### Primary-family asymmetry - -Within each context block (`:root`, `[data-theme="dark"]`, `@media (prefers-color-scheme: dark)`), the four primary-family tokens — `--primary`, `--ring`, `--sidebar-primary`, `--sidebar-ring` — should carry the same OKLCH value. A diff on just `--primary` is a retroactive contrast patch; a brand-truth update would have moved the whole family together. Same applies to the `--brand-primary-*` ladder if the brand carries one: every member should track a documented value, not a synthesised stop. - -## Surface selection: look past the homepage hero - -The brand's recognizable signature is rarely the homepage hero alone. Some brands open with a marquee-only surface that doesn't repeat on `/pricing`, `/enterprise`, `/product/*`, `/startups` — Notion's deep navy "Meet the night shift" hero band is the canonical example. Five of the six pages documented in Notion's `DESIGN.md` (everything except the homepage) use a white/cream canvas with pastel feature cards; only the homepage carries the navy band. - -Anchoring the preview shell on a homepage marquee misrepresents the system — the preview reads as "Notion homepage clone" rather than "Notion design system." The common style — the surface that appears across most documented pages — should drive the shell. Marquee-only signatures appear as accent surfaces (a single section card later on the page, a punctuation band before the footer), not as the headline. - -**Before authoring, capture 2–3 secondary pages** from the list in `DESIGN.md` §1 / the import provenance, not just the homepage: - -```bash -# Notion case — DESIGN.md lists homepage + enterprise + product/ai + product/agents + startups + pricing -mcp__chrome-devtools__navigate_page url="https://www.notion.com/product/ai" -mcp__chrome-devtools__take_screenshot filePath="temp/brand-refs/notion-product-ai.png" fullPage=true -mcp__chrome-devtools__navigate_page url="https://www.notion.com/pricing" -mcp__chrome-devtools__take_screenshot filePath="temp/brand-refs/notion-pricing.png" fullPage=true -mcp__chrome-devtools__navigate_page url="https://www.notion.com/enterprise" -mcp__chrome-devtools__take_screenshot filePath="temp/brand-refs/notion-enterprise.png" fullPage=true -``` - -If those pages share a canvas the homepage doesn't, the homepage is the marquee — design the shell around the shared canvas instead. - -### Selection metadata vocabulary - -Every catalog `DESIGN.md` carries a `selection:` frontmatter block so agents can shortlist reference systems without reading 100+ full files. Keep it descriptive, not promotional. The block is catalog routing metadata; it is not rendered in the preview. - -```yaml -selection: - mood: [minimal, productivity, technical] - tone: [calm, pragmatic, precise] - formality: medium - density: high - canonical_canvas: light - best_for: | - Product surfaces that need restrained structure, readable tables, and quiet workflow chrome. - avoid_for: | - Campaign pages that need theatrical motion, oversized imagery, or high-emotion lifestyle cues. -``` - -Allowed values: - -| Field | Values | -|---|---| -| `mood` | `austere`, `automotive`, `brand-system`, `cinematic`, `command-line`, `commerce`, `cyberpunk`, `data-rich`, `developer`, `editorial`, `enterprise`, `gradient`, `high-contrast`, `industrial`, `luxury`, `minimal`, `monochrome`, `organic`, `playful`, `productivity`, `retro-tech`, `spatial`, `tactile`, `technical` | -| `tone` | `authoritative`, `bold`, `calm`, `confident`, `dramatic`, `energetic`, `experimental`, `fast`, `friendly`, `irreverent`, `optimistic`, `polished`, `pragmatic`, `precise`, `premium`, `serious`, `utilitarian`, `warm` | -| `formality` | `low`, `medium`, `high` | -| `density` | `low`, `medium`, `high` | -| `canonical_canvas` | `light`, `dark`, `both` | - -`selection.canonical_canvas` must match the top-level `canonical-canvas`. When changing either one, run: - -```sh -node dev-scripts/catalog-index.mjs --check -``` - -The generated `catalog-index.json` is deterministic and committed. Do not hand-edit it; update `DESIGN.md` metadata, then regenerate with `node dev-scripts/catalog-index.mjs`. - -## Animation patterns - -Per-system preview templates should use the shared motion vocabulary in [`animation-patterns.md`](animation-patterns.md). That file owns entrance, hover, background, and reduced-motion conventions for catalog previews. Use those patterns before inventing custom keyframes; if a brand needs bespoke motion, keep the names and timing compatible with the catalog vocabulary so future audits can reason about it. - -## Content shape: Halcyon is a name only — no the host product product surface, no brand-X lift - -The single hardest rule in this catalog: - -> **Halcyon is a fictional company NAME with NO defined product.** It is not the host product. It does not publish artifacts. It does not gate publishing. It does not run an MCP comment loop. It does not version + audit artifacts. Anything that names the host product's actual product surface IS a leak, regardless of whether the word "Halcyon" is wrapped around it. - -This is unintuitive: "Halcyon publishes artifacts behind company auth" *sounds* like Halcyon content, but it's literally the host product's marketing copy. Renaming the host product → Halcyon doesn't make the surface fictional; it just gives the host product a fake name. - -The opposite trap also applies: scrubbing Halcyon and lifting brand X's marketing — "Pro for Teams" as a tier name, "Notion AI" as a feature card, "Cursor Tab" as a code-completion section, "by @raycast" on extensions, real customer logos in the logo strip — turns the preview into an unauthorized re-skin. - -The right shape is **Halcyon as a placeholder name, Halcyon's TEAM USING the brand for general knowledge work**, with brand-X-shape mockups carrying neutral SaaS-team content. - -### Halcyon thread-through (always keep) - -These names are established convention and identify the catalog's fictional brand: - -- Topnav brand mark: `__DESIGN_SYSTEM_NAME__` (interpolates the slug, e.g. "notion-style") or literal "halcyon" -- URLs / handles: `halcyon.dev`, `app.halcyon.dev`, `hlc.so`, `@halcyon/cli` -- Engineering universe: `halcyon-api`, `halcyon-worker`, `halcyon-webapp`, `halcyon-cli`, internal Slack "Halcyon HQ" -- People: `team@halcyon.dev`, generic team members -- Footer tagline, customer-logo strip wordmarks - -### People: never real names - -Avatars, inbox senders, byline names, db-row owners, deploy attribution, "shared by" tags — every place the template renders a person's name — must be **invented**. Never use a real person's name, including: - -- The current operator's name or initials (read from git config, CLAUDE.md, memory, or any in-session context) -- Anyone on the host product's actual team -- Real customers, investors, advisors, or any known living person -- Email addresses that route to real people (anything matching the operator's local-part) - -Use generic invented names + invented initials. Suggested pool: `Jordan Kim` / `JK`, `Morgan Lee` / `ML`, `Avery Park` / `AP`, `Riley Chen` / `RC`, `Sam Walker` / `SW`, `Taylor Reyes` / `TR`. These are gender-neutral, not associated with any known person, and rotate across mockups so no single name dominates. - -The leak pattern is subtle: the agent often reaches for the operator's name as a "plausible team member" when filling in a mockup. It isn't. It's a PII leak that ships into a public preview catalog. If you're authoring a per-brand template and any human name appears in the body, swap it for one from the invented pool above before committing. - -### Forbidden vocabulary (the host product product surface) - -Any of these appearing anywhere in a preview is a leak — even if wrapped in Halcyon language: - -- `publish` / `publishing` / `publish artifact` / `gated publishing` -- `artifact` / `artifacts` (the host product's primitive) -- `comment thread` / `resolve comment` / `comments → MCP loop` / `MCP comment` -- `custom domain` / `custom apex` / `tenant subdomain` (when described as a product feature) -- `version history` / `versioned + audited` / `version log` -- `SSO gating` / `audit log` (when described as headline product features) -- `chrome injection` / `edge-served chrome` -- `view-token` / `view freshness` / `KV fail-open` - -The fix is never "rephrase the leak more cleverly." The fix is: describe **how Halcyon's team uses the brand**, with neutral SaaS-team content (engineering practices, team rituals, weekly metrics, generic SaaS surfaces). - -### Per-register Halcyon-team content (no product features) - -| Register | Halcyon-team content (no product surface) | -|---|---| -| Command launcher (Raycast, Warp, Superhuman) | Halcyon's team using generic launcher tools — Hacker News reader, Slack channel jump, GitHub PR list, window management, clipboard history. Generic dev-team tooling, NOT extensions that map to the host product infra | -| IDE (Cursor) | Halcyon engineering code in any file — `tenant.ts` (multi-tenant provisioning), `auth.ts` (SSO bootstrap), route handlers, config — anything a generic B2B SaaS engineer writes. NOT `publish.ts` with `halcyon.publish()` | -| Workspace tool (Notion, ClickUp, Coda) | Halcyon team rituals — engineering specs, design crits, Q3 planning, customer interviews, sprint cycles. NOT "Artifact pipeline" or any the host product product structure | -| Project tracker (Linear, Asana, Jira) | Halcyon engineering tickets, design tasks, sprint cycles | -| Dashboard (PostHog, Mixpanel, Datadog) | Generic SaaS metrics — DAU, signup funnel, request latency, deploy frequency. NOT "artifact publish latency" | -| Drive / KB (Airtable, Coda, Quip) | Halcyon's customer CRM, vendor list, hiring pipeline, design tokens table | -| Calendar (Cal.com) | Halcyon sprint cycles, design crit, customer demos, all-hands | -| AI chat (Claude, ChatGPT, Perplexity) | Halcyon-business prompts — fundraising memos, market sizing, strategy questions, customer interview synthesis | -| Email (Superhuman, Hey, Front) | Halcyon team threads, vendor invoices, customer reply drafts | -| Transactions table (Stripe) | halcyon.dev / app.halcyon.dev as customer rows paying Stripe for general B2B SaaS billing | - -### Per-register Halcyon-team content for marketing surfaces (hero, feature cards, pricing) - -Marketing surfaces (hero, feature cards, pricing) shouldn't describe Halcyon's product (since Halcyon has none — see the rule). They should describe **how Halcyon's team uses the brand**, in the brand's voice register: - -| Surface | What goes here | -|---|---| -| Hero copy | Brand-flavored aphorism about the brand's value to a SaaS team. NOT "Halcyon publishes artifacts behind company auth" | -| Feature cards | Engineering / team / workflow aphorisms — "Reviews land by lunch", "Threads close themselves", "Page history explains itself". Brand-token demonstrations (color swatches, type scale, component vocabulary) also work — see the shipped 7 templates for that pattern | -| Pricing tiers | Generic SaaS tier shapes (Free / Solo / Pro / Enterprise) with generic SaaS features (storage, users, support level, basic SSO). NOT the host product's specific differentiators ("Named-email sharing", "Custom apex", "Audit log") and NOT brand X's actual tier structure | -| Stats band | Generic team metrics — pages updated, PRs opened, deploys this week, comments resolved, cross-team adoption. NOT "artifacts in flight" / "publish latency" | -| Customer-logo strip | halcyon.dev / hlc.so / @halcyon/cli wordmarks. NOT real third-party brands (Stripe / Linear / Vercel / NVIDIA) | - -### The two-minute litmus test before shipping - -Read the preview's text out loud. Ask: - -1. **Does anything I just read describe the host product's actual product surface?** (publishing, artifacts, gated, comment loop, version log, audit, chrome injection) — if yes, it's a leak even when wrapped in "Halcyon." -2. **Does anything I just read appear on the brand's actual marketing site verbatim?** (tier names, branded product features, real customer logos) — if yes, it's a lift. -3. **Does the body copy literally name the brand?** ("Halcyon's team uses Cursor", "Notion's bold yellow tint", "Try Claude" button label, model cards named "Halcyon Sonnet / Opus / Haiku") — if yes, it's a soft lift. The brand identifies itself through its visual chrome, never through being named in the prose. The topnav brand mark is the catalog slug (`__DESIGN_SYSTEM_NAME__`), not "Cursor" or "Notion." -4. **If I replaced "Halcyon" with "Acme" everywhere, would the preview still describe the same fictional company?** If swapping the name changes what the preview is "about," the name was carrying real-product meaning — that's a leak. - -A clean preview passes all four. - -### Prose rhythm: no aphorism stacks, no fragment chains - -Two AI-rhythm tells that show up under pressure to sound profound: - -**Stacked parallel-construction aphorisms**: "The agent stays in the loop, the editor stays in the keyboard, and the codebase stays close to the question." Three "X stays Y" clauses in a row reads as empty pseudo-poetry — each clause sounds like it means something, the stack means nothing. Same trap: "Pages own their topic; the team owns the page; the comments own the gaps." If you find yourself writing three parallel-structured clauses in a row to fill a sentence, the sentence isn't carrying a real claim. - -**Sequential period-fragment chains**: "Diff. Accept. Ship." or "Pages, calendars, database views." The X. Y. Z. cadence reads as an AI rhythm tell, not Carl-voice (per `feedback_fragment_chain_ai_slop` memory). Single fragments in isolation are fine; chained fragments are slop. - -The fix in both cases: replace with a concrete two-sentence prose statement that says one thing per sentence. "Halcyon's engineering team ships multiple PRs a day. The agent drafts the diff and runs the tests before a human signs off at the merge boundary." Two sentences, one verb-driven claim each, no parallel-structure decoration. - -## Dark-mode strategy - -Decide before authoring. Read `DESIGN.md` §1 to determine the brand's canonical canvas: - -| Brand canonical canvas | Dark-mode strategy | -|---|---| -| Light-canonical with a real product dark mode (Stripe, Apple, Claude, Vercel) | Hand-edit `[data-theme="dark"]` to the brand's actual dark surface. Look for `--brand-surface-dark` / `--brand-canvas-night` / equivalent brand-extras tokens. Override brand-extras (`--brand-canvas-soft`, `--brand-ink-mute`, etc.) so interlude bands and helper inks read on dark. | -| Light-canonical, no documented dark mode | Synthesise a plausible dark — near-black canvas, light foreground, brand accent stays. Match the brand's actual **product** dark mode (the app, the dashboard), not a marketing-marquee surface from one page. Notion's product dark is warm-near-black; the homepage navy is marketing-only. Extending a marketing-marquee color across the dark canvas misrepresents the brand. | -| Dark-canonical (Linear, SpaceX, Ferrari) | Both modes mirror `:root`. If `:root` encodes a light shop variant (SpaceX case), update `:root` to the dark marketing variant and mirror in `[data-theme="dark"]`. | - -The synthesised OKLCH lightness inversion (the catalogue's import-time default) breaks for nearly every brand — either text becomes the same lightness as the inverted bg, or warm tints flip to wrong-temperature cool. Always hand-edit. - -Marquee surfaces (the Notion navy band, etc.) should still appear in the dark preview — but as a single accent card with brand-extras navy tokens preserved, not as the canvas itself. The navy CTA card in `notion-style/preview-template.html` is the canonical pattern. - -### Polarity-locked tokens: never lift in dark - -If a brand-extras token names an always-dark ink for always-light surfaces (e.g. `--brand-pricing-ink`, `--brand-canvas-night`, `--brand-on-light-strong`), do NOT add a dark-mode override that lifts it. The token's surface doesn't flip with theme, so the ink shouldn't either — lifting it inverts the contrast against the locked surface and breaks every consumer downstream. Airtable's first iteration lifted `--brand-pricing-ink` to a light value in dark mode and broke 26 browser-contrast checks across the db grid, swatches, pricing tiles, topnav, and footer — every one of those surfaces was polarity-locked white. The fix: leave polarity-locked tokens at their `:root` value; only lift tokens used on theme-flipping surfaces (e.g. `--brand-body` for editorial text on canvas that does flip). - -The symmetric case: a token named for always-light surfaces (e.g. `--brand-on-dark` = white) doesn't need an override — it's already always-light by definition. The trap is the unnamed assumption that "all `--brand-*` should lift in dark." They shouldn't. - -### Single-polarity brands: only lock signature voltage - -For brands whose canonical canvas is single-polarity (light-canonical, no documented dark — Airtable is the worked example), don't apply shopify's multi-section `.canvas-cream` / `.canvas-night` pattern. That pattern works because shopify's two-track polarity is documented in the brand. For single-polarity brands, only the truly polarity-fixed signature surface (the brand's voltage moment — Airtable's white database grid, Stripe's gradient mesh hero, Notion's navy marketing band) stays canvas-locked in the dark variant. Demo sections (color swatches, surface ladder, pricing, typography, components) must flip with theme — otherwise the dark variant reads as "mostly light with three dark accents" rather than a coherent dark mode. - -Rule of thumb: count the locked sections in your dark screenshot. More than one or two and the page no longer reads as "dark mode of brand X" — it reads as "light page with an inverted hero band." Airtable's first iteration locked four sections; unlocking three (swatches, ladder, pricing) and keeping only the database grid locked was the fix. - -## Detect-clean checklist - -After `bash dev-scripts/build-previews.sh`, run both detectors: - -```sh -# Structural CSS lint (slop / meta / non-contrast rules) -node visualize/scripts/detect.mjs --strict visualize/design-systems//preview.html visualize/design-systems//preview-dark.html - -# Computed contrast via headless Chrome + axe-core (the canonical -# contrast check — resolves var() colors, cross-rule cascade, and -# per-theme computed styles; detect.mjs's a11y/low-contrast rule -# is structurally blind to var() and skips silently). -node visualize/scripts/browser-contrast.mjs visualize/design-systems//preview.html visualize/design-systems//preview-dark.html -``` - -Expected: -- `detect.mjs --strict`: `OK · N rules clean across 2 file(s)`. -- `browser-contrast.mjs`: `OK · 1 rule clean across 2 file(s)`. **Errors fail the check. Warnings are verification gaps, not passes.** axe returns `needs-review` rather than `fail` when it can't measure the background — gradients, pseudo-element backgrounds, single-char glyphs, `→` arrows. That means axe couldn't tell whether contrast is OK; it does NOT mean contrast is OK. Each `needs-review` warning on a text-containing element is a task: open the rendered PNG (`bash dev-scripts/screenshot-previews.sh `) and look at the text against the actual background. If the text reads, the warning is closed. If it doesn't, fix it (reposition stops, extend a fade, add a wash) — do not ship. - -Single-char glyphs (`★`, `→`, `›`) and pure-decoration pseudo-elements that contain no readable text are safe to leave; those warnings are genuine axe limits. Text-containing elements (`h1`, `p`, button labels, `figcaption`) are not. - -Renamed `detect-computed.mjs` → `browser-contrast.mjs` in commit `30aed2a`. Older docs reference the prior name. - -## Polish pass - -After the preview is detect-clean but before you ship the batch, run a polish pass. **Polish is NOT idempotent on first authoring** — there is always something to improve if you actually look. The mechanical floor (spacing ladder, favicon, OG meta) converges quickly; the quality dimensions below never do. - -### Mechanical floor (first pass — fix without thinking) - -1. **Spacing ladder.** Snap every margin / padding / gap to `0 4 8 12 16 20 24 32 40 48 64 80 96 128` (rem: `0 0.25 0.5 0.75 1 1.25 1.5 2 2.5 3 4 5 6 8`). Off-scale values authoring tends to reach for: `0.625rem` (10px), `0.875rem` (14px), `1.125rem` (18px), `1.75rem` (28px), `0.375rem` (6px), `0.125rem` (2px). - - ```sh - grep -nE '(0\.(125|375|625|875)|1\.(125|375|625|875)|1\.75|2\.25|2\.75|3\.5|5\.5)rem' \ - visualize/design-systems//preview-template.html - ``` - - Font-size `clamp()` declarations are exempt — fluid type scales follow their own ramp. - -2. **Favicon + OG meta present** in the per-system `` (per-system templates don't inherit `preview-kit/template.html`). - -3. **Detect-clean on both modes.** `node scripts/detect.mjs --strict ` returns `0 error 0 warning`. - -### Quality dimensions (second pass — actually look) - -These are the dimensions where authoring routinely leaves gaps. Walk each one before declaring done — there is almost always something to fix. - -| Dimension | What to check | -|---|---| -| **Focus-visible** | Every `.btn` (and any other interactive element) has a visible `:focus-visible` outline. Keyboard users see nothing when this is missing. | -| **Touch targets** | Buttons have `min-height: 44px` (WCAG AA touch-target floor). Default 14px text + 0.5rem padding lands around ~38px — under floor. | -| **Reduced motion** | If any hover state transforms / animates, wrap a `@media (prefers-reduced-motion: reduce)` override that disables the motion. | -| **Dark-mode shadows** | Hard offset-shadow elements (`box-shadow: 2px 2px 0 0 var(--brand-charcoal)`) are invisible in dark mode when the shadow color stays dark. Either flip via a local CSS var (`--preview-button-shadow` pattern) or use `var(--foreground)` so it inverts with the canvas. Same for borders on cards. | -| **Neutral temperature** | Follow the system’s documented identity. Neutral gray and warm/cool tints are all valid; inspect foreground/background pairs rather than forcing a tint. | -| **Typography hierarchy** | Same elements use same sizes/weights across the page. Hero / section title / card title scale stays consistent. | -| **Contrast** | Body text passes WCAG AA on both canvases. Muted labels stay above 4.5:1. | -| **Inline styles** | If you reach for `style="margin-top:0.5rem"` more than 2-3 times, the spacing was wrong somewhere — fix the source. | -| **Sticky scroll regions / overflow** | Tree-cards, IDE mockups, palette mockups — anything with `overflow: hidden` should also handle long content gracefully (text-overflow / max-width). | -| **Opacity-blended labels** | `opacity: 0.55-0.7` on always-dark ink against an always-light surface typically lands around 3.3-4.0:1 — below WCAG AA 4.5:1 for body-size labels. Bump to 0.8-0.85, or replace the opacity blend with a fixed mid-tone token. Airtable's first iteration had `0.55/0.6/0.7` opacities on db-header, ladder-num, section-eyebrow that all failed; bumping to `0.7/0.85` cleared every one. | -| **Signature-palette chip contrast** | Each chip background in the brand's pastel palette needs its text-color contrast verified. Mid-saturation palettes (mustard, ochre, mid-orange) are the trap — they look "dark enough" for white text but typically land at 2-3:1. Default to the brand's always-dark ink (`--brand-pricing-ink`, `--brand-canvas-night`) on light chips; only flip to `--brand-on-dark` for genuinely-dark chips (coral, forest, navy). | - -### When the polish pass produces a diff - -That's the norm, not the exception. Every brand authored this session got real polish-pass improvements after the initial detect-clean: focus rings added, touch-target floors enforced, dark-mode shadow flips, hover-state reduced-motion overrides. The mechanical floor (spacing ladder) is the part that converges to zero diffs across iterations — the quality dimensions never do. - -Common findings + fixes: - -- **`meta/missing-favicon`** — add an inline-SVG favicon to ``, typically a brand-mark glyph on the brand canvas color. -- **`meta/missing-og`** — add `og:title`, `og:description`, `twitter:card` meta tags. -- **`slop/non-token-color`** — flagged when hex literals appear in CSS that otherwise uses var refs. Usual cause: `var(--token, #000)` fallback form. Strip the fallback (`var(--token)` is fine when the token is guaranteed defined by the inlined tokens.css). For decorative gradient stops without a token home (mesh atmospherics, photographic backdrops), scope local CSS custom properties at `:root` inside the template's ` - - - - - - -
- -
- internal/platform/status -
- -

Q1 platform reliability — week 21 update

-

All-services SLO held this week. One soft incident on Tuesday (KV propagation backlog), recovered without paging.

- - - -
- TL;DR - SLOs green across all four services. KV propagation backlog hit 18s p99 for ~7 minutes on Tuesday after the bulk re-key job; absorbed by the 30s SLO. No follow-up needed; the rate-limit guard prevented escalation. -
- -
-
-
Edge p50
-
38ms
-
-
-
Origin SLO
-
99.94%+0.02
-
-
-
Errored requests
-
142/2.1M
-
-
- -

What changed

-
-

Two ship-events this week: the data-theme attribute cutover (W5) landed Tuesday morning and rolled through 100 PoPs over ~14 minutes. No regressions in artifact serve latency or comment widget mount. The pre-cutover artifacts that lose dark-mode toggle until republished are tracked in the W5 retro.

-

The KV bulk re-key (migrate-slug-index) ran Tuesday afternoon during a low-traffic window. Backlog peaked at 18.2s for ~7 minutes — below the 30s SLO ceiling but visible in the dashboard. Worth tightening the rate-limit on next run.

-
- -

Where to look

-
$ bslog tail api-prod -f --since 1h
-$ bslog log-errors api-prod --since 24h
-$ open https://grafana.internal/d/api-latency
- - - -
- internal / platform-team - visualize · clean -
- -
- - diff --git a/skills/visualize-lite/design-systems/clean/preview-template.html b/skills/visualize-lite/design-systems/clean/preview-template.html deleted file mode 100644 index c5d6b534..00000000 --- a/skills/visualize-lite/design-systems/clean/preview-template.html +++ /dev/null @@ -1,219 +0,0 @@ - - - - - -__DESIGN_SYSTEM_NAME__ — preview - - - - - - - - -
- -
- internal/platform/status -
- -

Q1 platform reliability — week 21 update

-

All-services SLO held this week. One soft incident on Tuesday (KV propagation backlog), recovered without paging.

- - - -
- TL;DR - SLOs green across all four services. KV propagation backlog hit 18s p99 for ~7 minutes on Tuesday after the bulk re-key job; absorbed by the 30s SLO. No follow-up needed; the rate-limit guard prevented escalation. -
- -
-
-
Edge p50
-
38ms
-
-
-
Origin SLO
-
99.94%+0.02
-
-
-
Errored requests
-
142/2.1M
-
-
- -

What changed

-
-

Two ship-events this week: the data-theme attribute cutover (W5) landed Tuesday morning and rolled through 100 PoPs over ~14 minutes. No regressions in artifact serve latency or comment widget mount. The pre-cutover artifacts that lose dark-mode toggle until republished are tracked in the W5 retro.

-

The KV bulk re-key (migrate-slug-index) ran Tuesday afternoon during a low-traffic window. Backlog peaked at 18.2s for ~7 minutes — below the 30s SLO ceiling but visible in the dashboard. Worth tightening the rate-limit on next run.

-
- -

Where to look

-
$ bslog tail api-prod -f --since 1h
-$ bslog log-errors api-prod --since 24h
-$ open https://grafana.internal/d/api-latency
- - - -
- internal / platform-team - visualize · __DESIGN_SYSTEM_NAME__ -
- -
- - diff --git a/skills/visualize-lite/design-systems/clean/preview.html b/skills/visualize-lite/design-systems/clean/preview.html deleted file mode 100644 index ba118340..00000000 --- a/skills/visualize-lite/design-systems/clean/preview.html +++ /dev/null @@ -1,1365 +0,0 @@ - - - - - -clean — preview - - - - - - - - -
- -
- internal/platform/status -
- -

Q1 platform reliability — week 21 update

-

All-services SLO held this week. One soft incident on Tuesday (KV propagation backlog), recovered without paging.

- - - -
- TL;DR - SLOs green across all four services. KV propagation backlog hit 18s p99 for ~7 minutes on Tuesday after the bulk re-key job; absorbed by the 30s SLO. No follow-up needed; the rate-limit guard prevented escalation. -
- -
-
-
Edge p50
-
38ms
-
-
-
Origin SLO
-
99.94%+0.02
-
-
-
Errored requests
-
142/2.1M
-
-
- -

What changed

-
-

Two ship-events this week: the data-theme attribute cutover (W5) landed Tuesday morning and rolled through 100 PoPs over ~14 minutes. No regressions in artifact serve latency or comment widget mount. The pre-cutover artifacts that lose dark-mode toggle until republished are tracked in the W5 retro.

-

The KV bulk re-key (migrate-slug-index) ran Tuesday afternoon during a low-traffic window. Backlog peaked at 18.2s for ~7 minutes — below the 30s SLO ceiling but visible in the dashboard. Worth tightening the rate-limit on next run.

-
- -

Where to look

-
$ bslog tail api-prod -f --since 1h
-$ bslog log-errors api-prod --since 24h
-$ open https://grafana.internal/d/api-latency
- - - -
- internal / platform-team - visualize · clean -
- -
- - diff --git a/skills/visualize-lite/design-systems/clean/tokens.css b/skills/visualize-lite/design-systems/clean/tokens.css deleted file mode 100644 index 4ba8b8e7..00000000 --- a/skills/visualize-lite/design-systems/clean/tokens.css +++ /dev/null @@ -1,189 +0,0 @@ -/** - * shadcn-html — Token Layer - * Semantic CSS custom properties matching the tweakcn.com export format. - * OKLCH color space. Full surface/foreground pair model. - * - * Export a theme from tweakcn.com, replace the :root and [data-theme="dark"] blocks, - * and every component rethemes instantly. - */ - -:root { - color-scheme: light; - - /* -- Surface / Foreground pairs ------------------------------ */ - --background: oklch(1 0 0); - --foreground: oklch(0.1450 0 0); - - --card: oklch(1 0 0); - --card-foreground: oklch(0.1450 0 0); - - --popover: oklch(1 0 0); - --popover-foreground: oklch(0.1450 0 0); - - --primary: oklch(0.2050 0 0); - --primary-foreground: oklch(0.9850 0 0); - - --secondary: oklch(0.9700 0 0); - --secondary-foreground: oklch(0.2050 0 0); - - --muted: oklch(0.9700 0 0); - --muted-foreground: oklch(0.5560 0 0); - - --accent: oklch(0.9700 0 0); - --accent-foreground: oklch(0.2050 0 0); - - --destructive: oklch(0.5770 0.2450 27.3250); - --destructive-foreground: oklch(1 0 0); - - /* -- Utility tokens ------------------------------------------- */ - --border: oklch(0.9220 0 0); - --input: oklch(0.9220 0 0); - --ring: oklch(0.7080 0 0); - - /* -- Sidebar tokens ------------------------------------------- */ - --sidebar: oklch(0.9850 0 0); - --sidebar-foreground: oklch(0.1450 0 0); - --sidebar-primary: oklch(0.2050 0 0); - --sidebar-primary-foreground: oklch(0.9850 0 0); - --sidebar-accent: oklch(0.9700 0 0); - --sidebar-accent-foreground: oklch(0.2050 0 0); - --sidebar-border: oklch(0.9220 0 0); - --sidebar-ring: oklch(0.7080 0 0); - - - /* -- Chart tokens --------------------------------------------- */ - --chart-1: oklch(0.8100 0.1000 252); - --chart-2: oklch(0.6200 0.1900 260); - --chart-3: oklch(0.5500 0.2200 263); - --chart-4: oklch(0.4900 0.2200 264); - --chart-5: oklch(0.4200 0.1800 266); - - /* -- Typography tokens ---------------------------------------- */ - --font-sans: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; - --font-serif: ui-serif, Georgia, Cambria, "Times New Roman", Times, serif; - --font-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace; - --font-display: var(--font-sans); - - /* -- Type scale tokens ---------------------------------------- */ - /* Theme-level type-scale defaults. Templates reference these; */ - /* per-template-overrides may clamp them to honour floors. */ - --text-display: clamp(2rem, 5vw, 3rem); - --text-heading: 1.75rem; - --text-title: 1.1875rem; - --text-base: 16px; - --leading-tight: 1.2; - --leading-normal: 1.6; - - /* -- Radius --------------------------------------------------- */ - --radius: 0.625rem; - /* Derived — matches tweakcn/shadcn scale */ - --radius-sm: calc(var(--radius) - 4px); - --radius-md: calc(var(--radius) - 2px); - --radius-lg: var(--radius); - --radius-xl: calc(var(--radius) + 4px); - - /* -- Spacing -------------------------------------------------- */ - --spacing: 0.25rem; - - /* -- Tracking ------------------------------------------------- */ - --tracking-normal: 0em; - - /* -- Shadow scale --------------------------------------------- */ - --shadow-2xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05); - --shadow-xs: 0 1px 3px 0px hsl(0 0% 0% / 0.05); - --shadow-sm: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10); - --shadow: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 1px 2px -1px hsl(0 0% 0% / 0.10); - --shadow-md: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 2px 4px -1px hsl(0 0% 0% / 0.10); - --shadow-lg: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 4px 6px -1px hsl(0 0% 0% / 0.10); - --shadow-xl: 0 1px 3px 0px hsl(0 0% 0% / 0.10), 0 8px 10px -1px hsl(0 0% 0% / 0.10); - --shadow-2xl: 0 1px 3px 0px hsl(0 0% 0% / 0.25); -} - -/* -- Dark mode -------------------------------------------------- */ -[data-theme="dark"] { - color-scheme: dark; - - --background: oklch(0.1450 0 0); - --foreground: oklch(0.9850 0 0); - - --card: oklch(0.2050 0 0); - --card-foreground: oklch(0.9850 0 0); - - --popover: oklch(0.2690 0 0); - --popover-foreground: oklch(0.9850 0 0); - - --primary: oklch(0.9220 0 0); - --primary-foreground: oklch(0.2050 0 0); - - --secondary: oklch(0.2690 0 0); - --secondary-foreground: oklch(0.9850 0 0); - - --muted: oklch(0.2690 0 0); - --muted-foreground: oklch(0.7080 0 0); - - --accent: oklch(0.3710 0 0); - --accent-foreground: oklch(0.9850 0 0); - - --destructive: oklch(0.7040 0.1910 22.2160); - --destructive-foreground: oklch(0.9850 0 0); - - --border: oklch(0.2750 0 0); - --input: oklch(0.3250 0 0); - --ring: oklch(0.5560 0 0); - - --sidebar: oklch(0.2050 0 0); - --sidebar-foreground: oklch(0.9850 0 0); - --sidebar-primary: oklch(0.4880 0.2430 264.3760); - --sidebar-primary-foreground: oklch(0.9850 0 0); - --sidebar-accent: oklch(0.2690 0 0); - --sidebar-accent-foreground: oklch(0.9850 0 0); - --sidebar-border: oklch(0.2750 0 0); - --sidebar-ring: oklch(0.4390 0 0); - - --chart-1: oklch(0.8100 0.1000 252); - --chart-2: oklch(0.6200 0.1900 260); - --chart-3: oklch(0.5500 0.2200 263); - --chart-4: oklch(0.4900 0.2200 264); - --chart-5: oklch(0.4200 0.1800 266); -} - -@media (prefers-color-scheme: dark) { - :root:not([data-theme="light"]):not([data-theme="dark"]) { - color-scheme: dark; - - --background: oklch(0.1450 0 0); - --foreground: oklch(0.9850 0 0); - --card: oklch(0.2050 0 0); - --card-foreground: oklch(0.9850 0 0); - --popover: oklch(0.2690 0 0); - --popover-foreground: oklch(0.9850 0 0); - --primary: oklch(0.9220 0 0); - --primary-foreground: oklch(0.2050 0 0); - --secondary: oklch(0.2690 0 0); - --secondary-foreground: oklch(0.9850 0 0); - --muted: oklch(0.2690 0 0); - --muted-foreground: oklch(0.7080 0 0); - --accent: oklch(0.3710 0 0); - --accent-foreground: oklch(0.9850 0 0); - --destructive: oklch(0.7040 0.1910 22.2160); - --destructive-foreground: oklch(0.9850 0 0); - --border: oklch(0.2750 0 0); - --input: oklch(0.3250 0 0); - --ring: oklch(0.5560 0 0); - - --sidebar: oklch(0.2050 0 0); - --sidebar-foreground: oklch(0.9850 0 0); - --sidebar-primary: oklch(0.4880 0.2430 264.3760); - --sidebar-primary-foreground: oklch(0.9850 0 0); - --sidebar-accent: oklch(0.2690 0 0); - --sidebar-accent-foreground: oklch(0.9850 0 0); - --sidebar-border: oklch(0.2750 0 0); - --sidebar-ring: oklch(0.4390 0 0); - - --chart-1: oklch(0.8100 0.1000 252); - --chart-2: oklch(0.6200 0.1900 260); - --chart-3: oklch(0.5500 0.2200 263); - --chart-4: oklch(0.4900 0.2200 264); - --chart-5: oklch(0.4200 0.1800 266); - } -} diff --git a/skills/visualize-lite/patterns/README.md b/skills/visualize-lite/patterns/README.md deleted file mode 100644 index 716989bd..00000000 --- a/skills/visualize-lite/patterns/README.md +++ /dev/null @@ -1,7 +0,0 @@ -# Pattern Recipes - -Pattern recipes are creation-time guidance for recurring rendered structures. They are not importable components, a CSS library, or review/polish rubrics. - -Each recipe uses frontmatter with required `name` and `description`; `variants` is optional. Do not add `applies_to` or `requires_browser_check`. The body should explain decision rules, semantic shape, type roles, spacing, color relationships, mobile behavior, variants, and concrete failure modes. - -CSS examples are illustrative and minimal. They exist to remove ambiguity, not to mandate one skin across all design systems. Mechanics belong in shells. Template-specific structure belongs in template specs. diff --git a/skills/visualize-lite/patterns/callout.md b/skills/visualize-lite/patterns/callout.md deleted file mode 100644 index 600b763f..00000000 --- a/skills/visualize-lite/patterns/callout.md +++ /dev/null @@ -1,38 +0,0 @@ ---- -name: Callout -description: Bounded notes, risks, decisions, quotes, or status messages that need emphasis without taking over the page. -variants: - - neutral - - success - - risk - - quote - - decision ---- - -# Callout - -## Use when - -Use a callout when a reader needs to notice a bounded note, risk, decision, quote, or implication before continuing. - -## Do not use when - -Do not box ordinary paragraphs or make every section begin with a callout. - -## Semantic shape - -Use `aside` for supplementary notes, `blockquote` for quotes, and `role="alert"` only for urgent status. Include a short label when tone might be ambiguous. - -## Color and tone - -Border, fill, label, and icon tone must imply the same state. Avoid side-stripe callouts; use a full hairline border, quiet tint, or pure type treatment. - -## Mobile behavior - -Keep callouts compact enough that they do not delay the body. Long callouts should become normal sections. - -## Failure modes - -- Warning border with neutral fill or success icon. -- Side-stripe default note. -- Callout used to compensate for weak hierarchy. diff --git a/skills/visualize-lite/patterns/interactive-states.md b/skills/visualize-lite/patterns/interactive-states.md deleted file mode 100644 index 7c54abf3..00000000 --- a/skills/visualize-lite/patterns/interactive-states.md +++ /dev/null @@ -1,86 +0,0 @@ ---- -name: Interactive states -description: Artifact-level controls, overlays, feedback, and changing content that need coherent semantics and behavior. -variants: - - actions - - tabs-and-disclosure - - forms-and-validation - - overlays - - status-and-feedback ---- - -# Interactive states - -## Use when - -Use this recipe when a self-contained artifact has functional controls or changes content in place: filters, tabs, disclosures, forms, dialogs, popovers, toasts, loading, empty, success, or error states. - -## Do not use when - -Do not add controls to make a static document look like an application. Do not simulate a backend, authentication, persistence, or navigation the artifact cannot perform. - -This recipe owns artifact-level semantic and state behavior. It is not an importable component library. [motion.md](../reference/motion.md) and [animate.md](../reference/animate.md) own timing and animation; this recipe defines what remains understandable when motion does not run. - -## Shared contract - -Prefer native HTML elements and their built-in behavior. Every control needs an accessible name, a visible `:focus-visible` indicator, and the same outcome from keyboard and pointer. Use DOM order as tab order; never add positive `tabindex`. State and meaning need a persistent text, icon, shape, or position cue rather than color or motion alone. On coarse pointers, keep targets comfortably tappable and do not hide essential behavior behind hover. - -Keep the state model small and truthful. Author only states the artifact can reach, preserve entered data and focus across updates, and leave a stable region in the DOM when assistive technology needs to hear changing content. - -## Buttons and action hierarchy - -- Use ` - - - - - - - - -
- Primary - Secondary - Destructive - Outline - small - small outline -
- -
-
-
-

Default card

-

Standard surface — card, border, padding, gap.

-
-

Cards inherit --card + --card-foreground and stack content with the design system's spacing rhythm.

-
-
-
-

Elevated card

-

Shadow over surface, no border.

-
-

Uses --shadow-* scale. Lifts off the page; suits floating CTAs or modal previews.

-
-
-
-

Interactive card

-

Hover + focus state — keyboard reachable.

-
-

Hover swaps to --accent + --accent-foreground. Focus ring uses --ring.

-
-
- -
-
- -
-

Heads up

-

An informational alert. Uses surface and foreground tokens; no semantic colour.

-
-
-
- -
-

Something went wrong

-

A destructive alert. Borders and text take --destructive.

-
-
-
- -
- - - - - - - - - - - - - - - - - - -
ArtifactStatusVisibilityViews
Q3 roadmapPublishedOrg1,204
Onboarding flowDraftPrivate—
API rewriteReviewOrg87
Incident 042ArchivedPublic3,991
Total5,282
-
- - - - - - -
-

Tokens

-

Chart palette

-

Five chart tokens (--chart-1…--chart-5) drive every series colour.

-
- -
-
- -
-

Vocabulary

-

Prose & code

-

Prose hierarchy and code rendering. The body inherits --font-sans; code.inline + .code-block render in --font-mono.

- -

Subheading at title scale

-

Hover a link to confirm link colour resolves to --primary. Inline code such as getComputedStyle(root) picks up the muted background.

- -
"A token surface is the smallest set of CSS custom properties a design system commits to. Everything downstream — components, templates, artifacts — composes against it." — design-systems/_doc.md
- -
    -
  • List items use the body type role.
  • -
  • Nested rhythm stays at --leading-normal.
  • -
  • Bullets inherit body colour.
  • -
- -
// build-previews.sh — substitute placeholder with tokens.css
-awk -v ds="clean" -v tokens="design-systems/clean/tokens.css" '
-  /__TOKENS_PLACEHOLDER__/ {
-    while ((getline line < tokens) > 0) print line
-    close(tokens); next
-  }
-  { print }
-' preview-kit/template.html
-
- - - - -
- -
- - - - diff --git a/skills/visualize-lite/reference/.gitkeep b/skills/visualize-lite/reference/.gitkeep deleted file mode 100644 index e69de29b..00000000 diff --git a/skills/visualize-lite/reference/animate.md b/skills/visualize-lite/reference/animate.md deleted file mode 100644 index 4af8c38e..00000000 --- a/skills/visualize-lite/reference/animate.md +++ /dev/null @@ -1,166 +0,0 @@ -# `animate` — purposeful motion, `prefers-reduced-motion` compliant - -Before following the instructions below, apply the shared rules in SKILL.md. - -`/visualize-lite animate ` covers all motion in the artifact — both adding motion where the artifact is jarring or lifeless, and removing or fixing motion that violates the doctrine (bounce easing, missing `prefers-reduced-motion`, scroll-jacking, parallax-as-decoration). Hands off to `polish` at the end. - -A hero moment is optional. Reserve it for an infrequent focal event that benefits from emphasis; feedback and state transitions can be the whole successful pass. Don't stagger everything — every-list-with-50ms-delays adds perceived load and reads as decoration. - -## Before you start - -1. **Read the artifact.** Where would motion *clarify* something — a state change, an arrival, an emphasis moment? Where is the artifact currently jarring (instant state flips with no perceived feedback)? -2. **Read [motion.md](motion.md) end-to-end.** Native-platform-first doctrine, two canonical easing curves, duration ranges, `prefers-reduced-motion` shape. Don't author bespoke curves or new durations. -3. **Translate the user's ask into motion vocabulary.** If the user asks in plain language ("make it smoother", "items one by one", "show where this came from", "make it feel clickable"), use [animation-patterns.md](../design-systems/animation-patterns.md) § User intent mapping to name 1-2 candidate patterns and their tradeoffs before editing. -4. **Name any motion constraint from the resolved context.** If the context declares no motion, removal-only mode applies. -5. **Load state semantics only when needed.** When editing feedback on functional controls, read the [interactive-state recipe](../patterns/interactive-states.md). It owns what users must perceive and how controls remain understandable without motion; Animate owns only whether and how that state change moves. - -## When animate is the right verb (and when it isn't) - -Animate is the right verb when motion would *clarify* or the artifact's existing motion is broken. Use animate when: - -- The artifact has instant state changes that feel abrupt (show / hide, route changes, form submits). -- The artifact has existing motion that violates the doctrine (bounce easing, missing `prefers-reduced-motion`, parallax, scroll-jacking). -- A hero element would land harder with a measured entrance. - -Special mode (not a refusal): when the resolved context declares `motion: none`, animate runs in removal-only mode — strip every keyframe / `transition:` / `animation:` declaration, then end. The motion-budget / hero-moment sections below do NOT apply; use the no-motion completion variant in the hand-off section. This is a valid successful run, not a refusal. - -Refuse and route when: - -- The artifact carries ≥3 Absolute bans. Motion on ban-shaped elements doesn't fix the shape. Recommend `/visualize-lite simplify` first. -- No interactive elements exist AND the artifact is static one-shot HTML AND there's no jarring transition to smooth. Animate has nothing to do — recommend `/visualize-lite polish` or surface that no motion is warranted. - -## Gate each motion choice - -Run candidate motion through these questions in order before choosing a primitive or duration: - -1. **Frequency.** Known high-frequency command paths stay instant. Establish frequency from measured usage, documented workflow, the user's brief, or the interaction's role; when it is inferred from role rather than measured, say so. Measured use around 100+ times per day is strong calibration evidence, not a count to invent or a universal threshold. Ordinary keyboard activation of an accessible control such as a button or tab is not itself a disqualifier. Repeated navigation, hover, and data-work interactions get at most a near-imperceptible feedback transition. Standard motion belongs to occasional state changes; expressive motion belongs only to rare or first-time moments. -2. **Purpose.** Name one job: feedback, spatial consistency, state indication, preventing a jarring change, or explanation. Delight is valid only for a rare moment. If no job fits, do not add the motion. -3. **Restraint.** Use the least motion that preserves the job. Keep a static cue after it finishes, and do not create a hero animation merely to satisfy the verb. -4. **Interruption.** Rapid, reversible, or gesture-driven changes must retarget from their current state. Prefer transitions for ordinary state changes; use velocity-preserving motion only when the interaction and resolved context justify it. Do not use restart-from-zero keyframes for toggles, toasts, or repeated actions. -5. **Physicality.** Reject motion that lacks a plausible source, destination, or interruption path. Apply [motion.md's physical-continuity contract](motion.md#physical-continuity) for the mechanics. - -Only choices that clear the gate proceed to [motion.md](motion.md) for tool, property, curve, duration, and reduced-motion decisions. Repairing or removing existing bad motion does not need to justify a replacement animation. - -The gate decides whether a state cue animates, not whether the interface communicates the state. Every relevant interaction still needs an immediate visible outcome. When motion is rejected, apply the final state without interpolation and omit displacement or transforms that existed only to animate it. - -## Plan state and motion coverage - -Use at most one hero moment. When one earns its place, state it in the run summary; otherwise state that the pass is feedback, transition, or repair only. Layer supporting motion sparingly, while preserving immediate feedback states even when the animation count is zero. - -**Tiebreaker for hero moment**: when multiple eligible elements exist, the headline / hero element / topmost h1 wins. Reader's eye lands there first; supporting elements borrow attention from the hero, not the other way around. - -Three coverage layers: - -1. **Hero moment** (0–1 element): an optional signature animation. Page-load entrance for the headline, scroll-driven reveal of a key metric, or a coordinated transition between two states. Never add one when the gate says the artifact is better without it. -2. **Feedback-state layer** (every interactive element): communicate each relevant hover, focus, active or selected, busy, and disabled state. The state cue is required; animation is optional. When the gate rejects motion, preserve the final cue and change to it immediately. A control does not need states it cannot enter, and hover-only feedback never substitutes for touch or keyboard feedback. -3. **Transition layer** (state changes): show/hide, expand/collapse, success/error confirmations. Only where the state change would otherwise be jarring. - -Don't add a fourth layer. "Delight moments" / "easter eggs" / "decorative ambient motion" are not animate's job — that's a separate creation pass, if at all. - -### Page-entrance choreography - -When multiple elements animate in at page-load, the orchestration matters more than each individual animation. Three shapes; **default to "none"** for most artifacts. - -- **None** (default): no page-load entrance at all. Right for any artifact the reader returns to repeatedly (dashboards, status pages, runbooks, reference docs), and for any product-register surface where the reader is in a task. First-paint motion gets old fast; it's a load-tax on every visit. Per [motion.md](motion.md), decorative load-stagger is a failure mode. -- **Hero-first**: the focal element lands first (300–400ms fade + slide), one supporting element may follow with a 100–150ms delay. Reader's eye locks on the hero, then absorbs context. Right only for one-shot theatrical-lane artifacts (release announcements, pitch deck title slides, manifestos) where one element should dominate the first impression *and* the artifact is read once, not returned to. -- **Ambient-first**: rare. The surrounding chrome appears first (header, section frames at 200ms), then content lands as a single coordinated reveal. Use when the chrome's appearance carries meaning (a status console booting up; a dashboard initializing) — not as decoration on a static report. Same first-paint-tax caveat applies if the reader returns repeatedly. - -Don't combine shapes in one artifact — pick one. Don't stagger every element individually (the per-element table covers element-level motion; page-entrance is about the orchestration above it). Cap the total entrance budget at 600ms. **If in doubt, default to none** — animate's job is to *fix* existing motion and add purposeful motion, not to invent a page-entrance where none belongs. - -## Execute systematically - -See [motion.md](motion.md) for native primitives, easing, durations, and reduced-motion policy. Animate.md doesn't restate any of it. - -**One additional orchestration constraint** beyond motion.md's rules: `Content scroll-driven reveal` is one hero reveal per artifact, never decoration on body sections. Motion.md owns the duration row; animate.md owns the per-artifact cap. - -## NEVER - -Motion failure modes (parallax, bounce on every interaction, decorative spin, missing `prefers-reduced-motion`, scroll-jacking, stagger choreography on every list, animating layout properties, decorative scroll-driven on prose, global `will-change`, animation hiding slow loading) live in [motion.md](motion.md)'s "Failure modes the agent reaches for" block as the canonical taxonomy. Animate adds these verb-specific constraints on top: - -- **Stagger choreography on lists with >5 items.** Animate-specific orchestration constraint — motion.md's "stagger on every list" failure mode covers the general case; animate sets the per-artifact threshold at 5. -- **Animate a banned shape.** If the target element is a SKILL.md banned shape, adding motion doesn't fix it. Route to `/visualize-lite simplify` first. -- **Add motion where the resolved context declares `motion: none`.** Removal-only mode applies — context wins, this verb runs as a stripping pass. - -## Per-template-category notes - -- **Pitch deck** (`pitch-deck`, `slide-deck`): enter transitions per slide (300–400ms, fade + slight slide) and no parallax. Slide decks don't scroll; scroll-driven primitives don't apply. -- **Long-form prose** (`whitepaper`, `report`, `case-study`, `research-brief`): motion is dangerous on reading-heavy artifacts. Reveal animations on body sections compete with reading flow. Limit to one hero moment on the title / hero element. -- **Dashboard** (`dashboard`, `data-explorer`, `status-page`): motion belongs on *data updates* (number ticks, chart re-renders), not on chrome. Don't enter-animate the dashboard frame — users return to it constantly; first-paint motion gets old fast. -- **Diagram** (`diagram`, `architecture-overview`, `org-chart`): scroll-driven reveals can work to introduce a complex diagram piece-by-piece. Use sparingly; static is often clearer. - -## Per-element patterns - -For interactive surfaces (dashboards, data-explorers, status-pages), first define the non-motion outcome for each relevant feedback state. The table offers transition treatments only after that motion clears the gate. When it does not, preserve the final text, icon, color, shape, position, or focus cue immediately and omit motion-only transforms or displacement. Use these as starting points; tune to the brand. Touch / pointer media-aware adjustments live in [responsive-design.md](responsive-design.md). - -| Element | Pattern | Duration / easing | -|---|---|---| -| Button hover | `transform: scale(1.02)` + `--surface` lift one step | 120ms · `cubic-bezier(0.4, 0, 0.2, 1)` | -| Button press / active | `transform: scale(0.97)` + opacity 0.9 | 80ms · `cubic-bezier(0.4, 0, 0.2, 1)` | -| Input focus | Border color transition to `--ring` + 2px ring inset | 150ms · `cubic-bezier(0.4, 0, 0.2, 1)` | -| Input validation (error) | Border to `--destructive` + inline message reveal. Shake is dated and a vestibular trigger — use only if the resolved context explicitly carries playful register, and disable fully under `prefers-reduced-motion`. | 200ms border · `cubic-bezier(0.4, 0, 0.2, 1)` | -| Toggle switch | Thumb `translateX()` + track `background-color` cross-fade | 200ms · `cubic-bezier(0.4, 0, 0.2, 1)` | -| Checkbox / radio | SVG stroke-dasharray draw + `transform: scale(0.95→1)` | 180ms · `cubic-bezier(0.16, 1, 0.3, 1)` | -| Accordion / `
` | Behind `@supports (interpolate-size: allow-keywords) and selector(::details-content)`: `interpolate-size: allow-keywords` on `:root` + `transition-behavior: allow-discrete` + `overflow: clip` on `details::details-content` for smooth `height: auto` + chevron 180° rotate. Fallback (unsupported readers): instant open / close. | 250ms · `cubic-bezier(0.4, 0, 0.2, 1)` | -| Tab switch | Indicator `translateX()` + content cross-fade (opacity, no slide) | 200ms · `cubic-bezier(0.4, 0, 0.2, 1)` | -| Tooltip / popover | `@starting-style` opacity 0 → 1 + 4px slide on the open axis | 150ms · `cubic-bezier(0.16, 1, 0.3, 1)` | -| Dialog open | `@starting-style` opacity + `transform: scale(0.97→1)` + backdrop fade-in | 220ms · `cubic-bezier(0.16, 1, 0.3, 1)` | -| Toast / snackbar | Slide-up from below + fade-in; exit through the same edge | 220ms in / 165ms out · `cubic-bezier(0.16, 1, 0.3, 1)` | -| Skeleton loading | Subtle `background-position` shimmer (4s loop, low contrast) | 4s · `linear` | -| Data update (number tick) | `requestAnimationFrame` interpolate displayed number; max 500ms | up to 500ms · `cubic-bezier(0.16, 1, 0.3, 1)` | -| Chart re-render | Fade out old data (150ms) → fade in new (200ms). No path-morph unless context declares it. | 350ms total · `cubic-bezier(0.4, 0, 0.2, 1)` | -| Copy-to-clipboard | Icon swap (clipboard → check) + brief `background-color` flash on the source element | 180ms · `cubic-bezier(0.16, 1, 0.3, 1)` | -| Drag handle | `cursor: grab` → `grabbing` on press + `transform: scale(1.02)` on the dragged element + drop-zone outline | 100ms · `cubic-bezier(0.4, 0, 0.2, 1)` | -| Sortable table header | Arrow icon swap (▲ / ▼) + `background-color` flash on the sort target column | 150ms · `cubic-bezier(0.4, 0, 0.2, 1)` | -| Table row hover | `background-color` to `--accent` at low alpha + cursor change | 100ms · `cubic-bezier(0.4, 0, 0.2, 1)` | -| Table row selection | Persistent `background-color` + left-edge accent rule on the selected row | 120ms · `cubic-bezier(0.4, 0, 0.2, 1)` | -| Filter chip toggle | `background-color` swap + `border-color` swap; no scale (chips snap) | 120ms · `cubic-bezier(0.4, 0, 0.2, 1)` | -| Search result count update | Inline number tick on the result-count label; no full re-render flash | 250ms · `cubic-bezier(0.16, 1, 0.3, 1)` | -| Empty state appearance | Fade + 8px slide-up on the empty-state illustration / message | 250ms · `cubic-bezier(0.16, 1, 0.3, 1)` | -| Live status pulse | Slow opacity oscillation (0.6 ↔ 1.0) on the status dot for "monitoring" state | 1500ms loop · `cubic-bezier(0.4, 0, 0.6, 1)` | -| Progress / upload | Determinate bar with linear width animation; indeterminate uses platform `` | width animation matches actual progress · `linear` | -| Drill-down reveal (non-`
`) | Behind `@supports (interpolate-size: allow-keywords)`: expanding row using `interpolate-size: allow-keywords` on `height: auto` + content fade-in. Fallback: instant expand. For `
`-based accordions see the Accordion row above. | 280ms · `cubic-bezier(0.4, 0, 0.2, 1)` | - -**Patterns to skip unless the resolved context explicitly calls for them**: confetti / particle bursts (reads as celebration template), like-button heart-explosion (consumer-app register only), elastic / bounce easing on any of the above (toy register). - -**`prefers-reduced-motion` coverage for the table.** Follow [motion.md](motion.md)'s reduced-motion policy. The patterns above use keyframes, `transition:` declarations, AND JS-driven animations (number ticks, chart re-renders), so cover all three surfaces: - -```css -@media (prefers-reduced-motion: reduce) { - /* Name only selectors whose state transitions this pass introduced. */ - .button, .dialog, .toast { transition-duration: 0.01ms !important; } - /* Disable infinite loops outright */ - .status-pulse, .skeleton-shimmer { animation: none !important; } -} -``` - -Do not use a universal `*` reduced-motion override. It can erase unrelated behavior and third-party state cues outside the animation's scope. - -For number ticks driven by `requestAnimationFrame`, gate the interpolation in JS: when `window.matchMedia('(prefers-reduced-motion: reduce)').matches`, set the final value directly without interpolating. State transitions stay; the *animation* of them disappears. - -## Verify before declaring done - -1. **Report the gate result.** Name the frequency posture and its basis—`Measured`, `Documented`, `User-stated`, or `Inferred from role`—plus the purpose for each retained motion family. Never report an inferred daily-use count. Name the hero moment when one exists; otherwise confirm that feedback, transitions, repair, or removal were the intended scope. -2. **Verify feedback outcomes.** Exercise relevant pointer, keyboard, and programmatic states. Classify each state family as `Animated`, `Instant`, or `Not applicable`; both animated and instant outcomes must remain legible without relying on motion alone. -3. **Test with `prefers-reduced-motion`.** Toggle the OS setting (macOS: System Settings → Accessibility → Display → Reduce motion) or DevTools (Rendering → Emulate CSS media feature `prefers-reduced-motion`). Animations disabled cleanly? No layout shift on the fallback? -4. **No motion on data-density lists.** Re-walk the artifact for >5-item lists; confirm no stagger. -5. **Scope check.** Animate should touch CSS + one or two elements' enter behavior. If the diff is large, the verb misfired — surface this in the hand-off. -6. **Re-run the two-altitude AI slop test** from SKILL.md. Did the motion you added push the artifact into a reflex aesthetic (marketing-template stagger, parallax-as-depth, decorative spin)? If yes, strip and retry. - -## Hand off to polish - -Per SKILL.md's hand-off output shape: summarise in plain markdown (not fenced) and lead with what changed. - -Animate-specific content to surface: - -- **Hero moment.** Which element animates, and how (the shape of the motion — easing curve type, what triggers it, duration). "No hero moment; motion was limited to approved feedback and state transitions" is a valid value. -- **Frequency basis.** For each retained or rejected motion family, name whether frequency was measured, documented, user-stated, or inferred from the interaction's role, and cite the available evidence without inventing a numeric count. -- **Feedback states.** Count the relevant interactive elements covered and split their state families into animated and instant outcomes. "0 animated, 8 instant" is valid when the gate rejects motion; "0 (no interactive elements present)" is valid for a static one-shot artifact. -- **Transition layer.** Count of state changes smoothed. "0 (static one-shot HTML, no state)" is a valid value too. -- **Existing motion fixed.** List the bounce / missing-reduced-motion / parallax fixes applied to motion that was already in the artifact. -- **prefers-reduced-motion handling.** What shape was used (full reduce, partial reduce + alternative cue, etc.). -- **Diff size** as a percent of the artifact. -- **Next step.** Polish checks the dimensions animate didn't change. - -See SKILL.md `Hand-off output shape` for the shared variant rules. Animate's refusal case fires when the artifact has no jarring transitions, no broken motion, and no interactive elements — route to `/visualize-lite polish` instead. - -**`motion: none` variant.** When the resolved context declares `motion: none`, the pass is removal-only — there's no hero moment and no animated feedback or transition layer to add. Preserve existing immediate state cues, surface what motion was removed (counts of keyframes / `transition:` declarations / `animation:` declarations stripped), name that the context declared `motion: none`, then route to polish. Same conversational shape — don't fence the summary as a separate templated variant. diff --git a/skills/visualize-lite/reference/bolder.md b/skills/visualize-lite/reference/bolder.md deleted file mode 100644 index 34d54f23..00000000 --- a/skills/visualize-lite/reference/bolder.md +++ /dev/null @@ -1,143 +0,0 @@ -# `bolder` — amplify visual punch on safe / generic artifacts - -Before following the instructions below, apply the shared rules in SKILL.md. - -`/visualize-lite bolder ` increases visual impact through stronger hierarchy, more committed scale, decisive type, and brand-true color amplification. Hands off to `polish` at the end. - -Bold means distinctive, not "more effects." Don't reach for gradient text or glassmorphism — they're the most-trained-on visual reflex and read as anonymous. Every Absolute ban in SKILL.md applies during bolder. - -## Before you start - -1. **Read the artifact.** Where does the eye land first? On nothing in particular? Is everything medium-sized, medium-weight, similar-spaced? That's the diagnosis. -2. **Name the available amplification range.** Use the resolved context to decide whether the artifact should amplify through precision, scale, type discipline, contrast, or another specific lane. - -## When bolder is the right verb (and when it isn't) - -Bolder is the right verb when the artifact is safe, generic, medium-everything — nothing the eye lands on first, no committed color, no scale contrast. Use bolder when: - -- The artifact reads as defaulted (system fonts, basic palette, single body register). -- The resolved context carries a stronger identity than the artifact currently expresses. -- There's a hero moment the artifact *should* have but doesn't. - -Refuse and route when: - -- The artifact carries ≥2 Absolute bans. Bolder on a ban-carrying artifact amplifies bans (a 144px gradient-text headline is BAN 1 worse, not better). Recommend `/visualize-lite simplify` first. -- Every candidate focal moment is already over-amplified (gradient text + hero metric + tricolon CTA all competing). Recommend `/visualize-lite quieter` first. -- The artifact is short-form prose (whitepaper / report) — bolder usually means stronger typographic hierarchy here, not theatrics. If you'd reach for spatial / color amplification on prose, you're misapplying the verb. - -## Plan the amplification - -Pick ONE focal moment. Bolder = ONE hero claim, not ten amplified elements. State the focal moment in the run summary. **If you can't name a single focal moment** — because the artifact is too noisy to pick one — refuse the verb and route to `quieter` (see above). - -### Step 1: pick a lane - -Before picking axes, pick a personality direction. Bolder without a lane is amplification in five inconsistent directions — and reads as chaos. Five lanes; the resolved context + template category suggest which one fits: - -- **Theatrical** — display-grade type at the top of the scale, dramatic spacing (theatrical density per [spatial.md](spatial.md)), one surface committed to brand color, hero element escaping its container. Pitch decks, release announcements, conference materials. -- **Restrained-bold** — single weight-step amplification, one accent surface, tighter contrast on color. The brand has identity but is product-register (Stripe, Linear). Bolder here means clearer hierarchy, not theatrics. -- **Editorial-dramatic** — large display type (often serif), aggressive scale contrast between display and body, generous leading, pull-quote treatment. Whitepapers, long-form essays, manifestos. -- **Committed-mono** — single typeface (display + body from one family), single accent color, type doing all the work. Dev-tool brands, technical reports, infrastructure docs. -- **Data-native / operational-bold** — amplification through *signal* density: status hierarchy (live / degraded / down with distinct chroma), anomaly contrast (the one out-of-band metric draws the eye), table priority (the column that matters first), one focal KPI elevated above the supporting grid. Dashboards, status pages, data-explorers, NOC views. Bolder here is "the alarming row is unmissable," not "the page is louder." - -State the lane in the run summary. The lane bounds the axes you pick in Step 2 — *restrained-bold* never reaches for theatrical spatial drama; *committed-mono* never reaches for committed color beyond a single accent; *data-native* never reaches for full-bleed decorative surfaces. - -### Step 2: pick axes - -Three amplification axes (pick at most two): - -- **Type scale**: dramatic display heading, weight contrast (200 ↔ 700), generous leading or tight tracking depending on the brand. -- **Spatial drama**: extreme scale jumps (3–5x between hero and supporting), asymmetric layout, hero element escaping its container. -- **Committed color**: one brand color carrying 30–60% of a surface (the resolved palette's color). Or a single dramatic dark / light surface. - -Don't amplify across all three axes at once. That's not bolder, that's chaos. - -## Execute systematically - -### Typography -Consult [typography.md](typography.md). - -- Display heading at the top of the type scale (or one step above the existing top). Display-grade glyph if the brand carries both grades. -- Weight contrast: pair 700 with 400 (or 900 with 200 on brands that carry the range). Avoid 600 + 400 — too close to read as deliberate. -- Variable fonts: lean into the axes (weight, optical-size, slant) the brand declares. -- Cap line length at 65–75ch even when amplifying. Wider amplified type is wall-of-text, not bold. - -### Color -Consult [color.md](color.md). - -- Lift `--primary` saturation if the brand currently runs muted (within OKLCH chroma 0.18–0.22 range — don't exceed; high chroma at lightness extremes reads garish). -- Commit one surface to the brand color (hero section background, or sidebar, or footer band). -- High-contrast accent on the focal moment only — not on every CTA. -- Dark surface for emphasis if the resolved context supports it. Dark is a deliberate move, not a default — pick by writing one concrete sentence of who-reads-this-where-and-when (an SRE on a 27" monitor at 2am in a dim room *forces* dark; a clinician on a tablet in a fluorescent-lit ward *forces* light). If the sentence doesn't force the answer, it's not concrete enough. - -### Spatial -Consult [spatial.md](spatial.md). Full-bleed at viewport edges + reflow at mobile: [responsive-design.md](responsive-design.md). - -- Extreme scale jumps on the focal moment (3–5x between hero and supporting). -- Asymmetric layout: 70/30 or 80/20 splits, not 50/50. -- One element escapes the grid — a hero claim, a key diagram, or a focal KPI tile breaks the column or runs into the margin. (Not every artifact has a "hero metric" — that shape is specifically the dashboard-KPI escape; on other registers the focal element is a claim, an illustration, or a quote.) -- Intentional overlap — layer two elements (hero illustration over a section divider, oversized type bleeding across a card edge) for depth. Different from glassmorphism: overlap is structural composition, not decorative blur. Theatrical and editorial-dramatic lanes only. **On data graphics** (charts / KPI tiles): overlap is allowed *only* when it labels a data mark and never occludes the mark, never changes perceived proportion, and never adds blur or translucency — per [data-viz.md](data-viz.md), annotation recedes and data dominates. Decorative overlap on chart plot areas is out-of-scope for bolder. -- Full-bleed surface at `100vw` works on theatrical lane only — `width: 100vw` with padding overflows on mobile (per [responsive-design.md](responsive-design.md)); use `width: 100%` with the parent set to `max-width: none` for the bleed section. -- Generous spacing around the focal element (theatrical register); compact spacing on the supporting context for contrast. - -### Motion -Consult [motion.md](motion.md). Bolder ≠ animated — keep motion sparingly, then `polish` will confirm. - -- One signature enter animation on the focal moment, not a stagger across every list. -- Easing: `cubic-bezier(0.16, 1, 0.3, 1)` for confident-and-fast. Never bounce. - -### Visual effects (beyond color / scale / spatial) - -When the three primary axes are spent and the artifact still needs more presence, reach for *texture-grade* effects — surface treatments that add visual weight without adding decorative chrome. Use sparingly; one effect per artifact, never stacked. - -- **Noise / grain texture** — fine SVG noise overlay (alpha 4–8%) on a hero surface or across the whole page. Adds tactile weight. Reads as letterpress / printed on editorial registers; reads as analog accent on theatrical. Don't confuse with mesh gradients (see below). -- **Mesh gradients** — multi-stop CSS gradients producing a "soft mesh" surface. Reads as 2020s SaaS-digital by default — useful for brands that *want* that register (consumer-product launches, modern web-editorial), wrong-register for technical, institutional, or editorial brands. Pair with a tinted neutral foreground, not the gradient stops as text color. -- **Halftone** — `background-image` dot pattern or SVG halftone on a single accent surface. Works on editorial / publishing / print-adjacent registers; off-register on dev-tool brands. -- **Duotone** — two-color photo treatment via `mix-blend-mode` or SVG filter. Useful on hero imagery for brands with a single committed accent color. -- **Custom illustrative elements** — hand-drawn, woodcut, or geometric custom artwork in the brand's voice. Distinct from ornamental icon-tile section markers (BAN 5 in SKILL.md); illustrations carry information or atmosphere, while ornamental tiles add chrome without meaningful distinctions. -- **Atmospheric blur / backdrop-filter** — sparingly, on a single hero surface, never as decoration on every card. Glassmorphism-on-every-panel is generic AI-design slop; one purposeful backdrop-filter on a focal moment is craft. -- **Full-bleed surface** — break the column at the focal section, let one element occupy 100vw with a dramatic surface (gradient, photo, illustration). Theatrical lane only. - -Effects compose with the lane (the defaults — resolved context can override): - -- *Theatrical* → noise, mesh, custom illustration, full-bleed, backdrop-filter all in play. -- *Editorial-dramatic* → grain, halftone, duotone, custom illustration. Skip mesh (off-register). -- *Restrained-bold* → at most ONE *very subtle* effect (low-alpha noise, single duotone hero). Restraint is the point; if you're picking from this list, ask whether the design actually needs it. -- *Committed-mono* → grid / scanline / terminal-cursor / diagrammatic linework can read in-register; texture effects from the list above usually don't (the type is the effect). -- *Data-native* → none. The visual amplification IS the data hierarchy; surface textures distract from signal. - -## NEVER - -- Reach for any SKILL.md Absolute ban. Bolder amplifies banned shapes instead of redeeming them. -- Amplify across all three axes (type + spatial + color) at once. Pick one or two; the rest stays restrained. -- Sacrifice readability for impact. Body prose still wraps at 65–75ch. Contrast still meets WCAG AA. -- Use bounce / elastic easing. Reads as toy, not bold. -- Add decoration without a focal moment. Bolder without a hero element is just louder. -- Replace approved palette choices merely to amplify intensity. Adjust color coverage, placement, or contrast within the brief; no hue is inherently bolder or more generic. - -## Per-template-category notes - -- **Pitch deck** (`pitch-deck`, `slide-deck`): bolder usually means committing to typographic drama — display-grade headings on 2–3 hero slides, supporting slides quieter. Don't try to make every slide a hero. -- **Long-form prose** (`whitepaper`, `report`, `case-study`): bolder is dangerous on prose-heavy artifacts — usually it means a stronger pull-quote treatment or a more committed type scale, not chrome amplification. Don't add icon tiles or feature cards to a whitepaper. -- **Dashboard** (`dashboard`, `data-explorer`): bolder is rarely the right verb here. Bold in product registers means *clearer* hierarchy, not theatrics. Pull the focal KPI into a larger tile, push the supporting metrics smaller. Don't dramatize the chrome. - -## Verify before declaring done - -1. **Name the focal moment.** Where does the eye land first now? If you can't point to one element, bolder didn't land — either refuse the verb and route, or re-run. -2. **Re-run the two-altitude AI slop test** from SKILL.md. Did you reach for a banned or reflex pattern? Start over. -3. **Context still recognizable.** The artifact should read as a bolder version of the resolved context, not as a generic bold template. -4. **Scope check.** If bolder is touching most of the artifact, the verb misfired — surface this in the hand-off, don't push through. - -## Hand off to polish - -Per SKILL.md's hand-off output shape: summarise in plain markdown (not fenced) and lead with what changed. - -Bolder-specific content to surface: - -- **Lane** used: theatrical / restrained-bold / editorial-dramatic / committed-mono / data-native. -- **Focal moment.** Which element the eye lands on first now, and what changed about it. -- **Amplification axes** applied — max two of: type / spatial / color. Three is theatrical overcommitment. -- **Visual effect** layered in, if any: mesh / halftone / duotone / grain / illustration / backdrop-filter / full-bleed. "None" is a valid value when the lane carried the work without an effect. -- **Diff size** as a percent of the artifact. -- **Next step.** Polish raises the floor on the dimensions bolder didn't change. - -See SKILL.md `Hand-off output shape` for the variant rules. Bolder's refusal case fires on artifacts that already carry ≥2 Absolute bans, on artifacts where no focal moment can be named, or on short-form prose where amplification would misapply — route to `/visualize-lite simplify` or `/visualize-lite quieter` first in those cases. diff --git a/skills/visualize-lite/reference/cognitive-load.md b/skills/visualize-lite/reference/cognitive-load.md deleted file mode 100644 index 39a6fbfb..00000000 --- a/skills/visualize-lite/reference/cognitive-load.md +++ /dev/null @@ -1,127 +0,0 @@ -# Cognitive load - -Density vs clarity tradeoffs for content-heavy artifacts — dashboards, research-briefs, whitepapers, runbooks, survey-results, data-explorers. Brand profile overrides — if `DESIGN.md` declares a density register that pushes past these defaults (a NOC ops brand might run higher density), use that. The rules below are the fallback when the brand profile is silent. Lane: cognitive-load owns chunking, choice volume, disclosure, scan paths, info order, and decision count; [typography.md](typography.md) owns measure; [spatial.md](spatial.md) owns hierarchy + squint; [copy.md](copy.md) owns prose shape. - -The principle: density is quality *when the layering is right*. Density without layering is noise. Same data, different cognitive cost — the work is in the organization, not the reduction. - -## Chunking — working-memory defaults - -Working memory for *unstructured* items is closer to 4±1 than the often-cited "Miller's 7±2." Miller (1956) was about familiar *chunks*, not raw bits — phone numbers work because the digits group into chunks (area code, exchange, line). Cowan (2001) pegged the unstructured-item ceiling at 4±1. Default the artifact to **4 visible items per group**; allow up to 7 only when the items chunk naturally (sequential dates, ordinal scale, same metric type). Beyond 7, the reader can't hold the set — chunk or disclose. - -- **Chart series**: 4 lines on a single line chart is the comfortable ceiling; up to 7 when the lines are naturally grouped (e.g. percentile bands p50/p90/p99). Beyond, sort by value and group the tail into "other" or render as small multiples. -- **List items per group**: 4–7 bullets under a heading; break to a new sub-heading if more. The reader's eye loses the group at 8+. -- **Dashboard tiles per row**: 4 visible at desktop; 2 at mobile. Tile grids beyond 4×3 read as a wall of numbers; the reader doesn't process them, just scrolls past. -- **Navigation items**: 4–7 top-level entries. Hide secondary nav behind disclosure; don't pile every section into the chrome. -- **Form fields per visible group**: 4–7 inputs per fieldset / section. Beyond, break into multi-step or collapse non-essential fields behind progressive disclosure. -- **Color encoding on charts**: direct-label at ≤5 series (per [data-viz.md](data-viz.md)); 6+ accent hues per chart is a data-viz failure mode regardless of working-memory limits. The cognitive-load reason it fails AND the data-viz reason it fails both point to: re-encode (sort + group + "other"), or render as small multiples. - -## Hick's law — choice overload - -Decision time scales logarithmically with the number of choices: `T = a + b·log₂(n+1)`. Doubling the choices does not double the decision time — adding choice 8 is cheaper than adding choice 3 because the reader is already paying the cost of holding the existing set. The mistake is treating any increment as free. - -**Informed-choice carve-out**: when the list is sorted, labelled, or familiar (US-state dropdown, weekday picker), Hick's law softens because the choice becomes recognition, not deliberation. Long sorted lists hurt less than short unsorted ones. - -Apply ruthlessly: - -- **CTAs on surfaces asking the reader to act**: one primary action; demote secondaries. An explanatory report does not need a CTA merely to satisfy this rule. Depicted UI follows [interface-illustration.md](interface-illustration.md). -- **Theme toggle**: smart-default via `prefers-color-scheme`; skip the per-artifact toggle unless the brand profile asks for one (see [simplify.md](simplify.md) navigation density). -- **Navigation surfaces**: ONE per artifact (inline TOC, OR sticky sidebar, OR breadcrumb — not all three). -- **Filter chips on a dashboard**: ≤6 visible; the rest behind "more filters" disclosure. -- **Dialog buttons**: max 3 (cancel + primary + maybe secondary). A 5-button dialog is a 5-decision moment. - -## Intrinsic vs extraneous cognitive load - -Sweller's cognitive load theory, flattened for artifacts: total cognitive load = intrinsic (topic's irreducible complexity) + extraneous (chrome, decoration, layout choices the artifact imposes). Rule: minimize extraneous, preserve intrinsic. - -A 12-stage rollout has 12 stages. Stripping it to 4 stages to "reduce cognitive load" doesn't reduce load — it lies to the reader and shifts the missing complexity onto whoever inherits the rollout. Reduce the extraneous cost (one-look-per-stage layout, parallel structure, no decorative section markers) while keeping the intrinsic structure faithful. - -## Decision fatigue - -Distinct from Hick's law. Hick's measures how long each decision takes; decision fatigue measures how decision quality degrades across a sequence of decisions. The artifact reduces decision fatigue by removing decisions the reader doesn't need to make. - -- **Default everything.** Every toggle, dropdown, and configurable surface ships with the right answer pre-picked. The reader changes only what doesn't fit. -- **For artifacts asking the reader to act, one primary action *identity*** — the same CTA may repeat (hero band + closing band) when both restate the same commitment. -- **For action surfaces, one primary *decision* per section or viewport** — no section asks the reader to pick between two equal-weight actions. -- **Settings appear in the relevant context**, with administration deferred until needed. Settings competing with the main task burn decision-budget on configuration choices the reader did not come to make; placing every setting at the end is not a universal solution. -- **Executive-mode vs analyst-mode**: the same artifact serves both registers if the executive-mode signals (headline claim and supplied top-line numbers; a primary CTA only when action is the reader task) are at the top and the analyst-mode depth (methodology, raw data, edge cases) is below. Don't make the executive reader scroll to the answer. - -## Jakob's law — pattern familiarity - -Readers spend most of their time on other artifacts; they expect your artifact to work like the ones they know. Don't reinvent the table, chart, or nav — the cost of "wait, how does this work" is paid once per reader and never recovers. - -Brand can declare a distinctive register (terminal-native, editorial side-margins, manifesto typography) — Jakob's is an input, not a veto. The right shape: distinctiveness lives at the brand register; conventions live at the component shape. A terminal-native brand can still use a standard sortable table — the wrapper is brand, the contents are convention. - -## Progressive disclosure - -Layer the immediate answer, supporting explanation, and full account according to the reader's task, with clear entry points when detail is disclosed. Do not hide information that changes the conclusion, scope, permissions, evidence strength, or available action. Apply SKILL.md's preservation scope; moving detail out of the primary path does not authorize deleting it. - -- **Lead answer + supporting body** lets readers choose depth where a summary fits the approved pacing; it is not a mandatory opening for narrative or tutorial sequences. -- **Expandable comparison detail** can hold supplementary conditions or raw evidence. Choose visible columns from the comparison question, not a fixed column count; interpretation-changing conditions remain visible. -- **Footnotes / sidenotes** can carry provenance without interrupting prose, while evidence limitations stay beside the claim they qualify. -- **Inline code; full code on demand** keeps short snippets recognizable and longer blocks behind a clear heading or accessible link. -- **Show, then explain** when seeing the result helps understand the method; retain the source's required argument order when the result needs that context first. - -**Modern affordances** (Baseline gating matters): - -- `
` / `` is Baseline widely available — ship unguarded. -- `interpolate-size: allow-keywords` for smooth accordion height animation is **Limited availability** — wrap in `@supports (interpolate-size: allow-keywords)`; accept a snap fallback in unsupported readers. -- CSS anchor positioning for inline tooltips: `anchor()` Baseline January 2026, `position-anchor` still Limited — gate behind `@supports (position-anchor: --x) and (top: anchor(bottom))` (the `position-anchor` property is the Limited piece; `anchor-name` alone isn't a strong enough check). - -## Scannability — F-pattern and Z-pattern - -Readers scan before they read. The pattern they follow depends on the artifact shape: - -| Artifact shape | Scan pattern | What it implies | -|---|---|---| -| Long-form prose (reports, whitepapers, articles) | **F-pattern** | Headline + claim at top; structural h2s on the left margin; paragraph openings carry high scan weight (see [copy.md](copy.md) for the prose-shape rule). | -| Landing-style (release announcements, one-pagers) | **Z-pattern** | Top-left logo / nav → top-right CTA, diagonal to bottom-left hero claim → bottom-right final CTA. | -| Dashboards (tile grids, data-explorers) | **Grid scanning** | Order tiles by reading priority left-to-right, top-to-bottom; the reader's eye zigzags across the grid. | -| Mixed-shape (a one-pager with a dashboard inset, a report with a hero KPI) | **Layered** | Treat each region by its own pattern; don't impose one scan-path globally. | - -**First-word weight**: paragraph-opening words bear disproportionate scan weight under F-pattern reads. See [copy.md](copy.md) for the prose-shape rule (front-loaded claims, parallel structure); cognitive-load owns the scan-mechanic rationale. - -## Density-without-layering = noise - -Dense displays work when visual hierarchy carries the priority. They fail when everything reads at the same weight. - -- **Layer by visual weight**: primary signal in `--foreground`; secondary series in `--muted-foreground`; reference lines / annotations one step lighter again. Spatial owns the visual mechanic (see [spatial.md](spatial.md) squint test); cognitive-load owns the priority decision — which signal is primary, which is reference. -- **Anchor with one focal element per screen**: a hero KPI, a flagged anomaly, a chart with a single annotated peak. Without an anchor, the eye has nowhere to land first and the artifact reads as "wall of numbers." -- **Whitespace as separator, not as decoration**: gaps between tiles / sections should be smaller than the gaps that separate the artifact's major regions. Same-spacing-everywhere flattens grouping (per [spatial.md](spatial.md) failure modes). -- **Sparkline columns** for tabular density: replace a multi-line chart of 20 metrics with a table where each row is one metric and its column carries a sparkline. The eye scans vertically for "which metric is trending where" — denser AND faster than the multi-line chart. - -## Information arrival order - -The order in which information appears should match the order the reader needs it. Wrong order forces re-reading; re-reading is cognitive cost. - -- **Inverted-pyramid for analytical artifacts** (research, reports, postmortems): finding first → supporting data → caveats → methodology. The reader who only reads the first paragraph gets the answer. -- **Headline → context → outcome → next-step** for announcements and one-pagers. -- **Symptom → diagnosis → action** for runbooks and incident docs. -- **Current state → trend → comparison** for dashboards: the number first; whether it's good or bad second; what it's compared to third. -- **Summary → what changed → why → test plan** for diff / review-PR shapes: the reader who only reads the summary knows whether to approve. - -**Academic ordering** (introduction → background → methodology → results → discussion) is the right shape for a journal article — the claim earns evidence reading. It's the *wrong* shape for a postmortem, status report, or release note — the reader needs the answer, not the build-up. Don't lead with context; context is what the reader needs *if* they keep reading, not what gets them to keep reading. - -## Cognitive offloading - -Make the important distinction recognizable before asking the reader to read its explanation. This section owns recognition guidance; prose shape belongs in [copy.md](copy.md), and quantitative encodings in [data-viz.md](data-viz.md). - -- **Recognize the object.** Labelled icons, recognizable objects, or distinct forms can help identify recurring actors, resources, actions, and states. Prefer the brand's existing vocabulary; keep equivalent entities consistent and the same object recognizable across views. Choose coherent cues for the audience and content, not a fixed icon vocabulary or an icon for every heading. Labels carry meaning alongside color and form; decorative glyphs should not create redundant accessible announcements. -- **Understand the relationship.** Use attachment, containment, position, and connections to show what belongs together or changes. A comment attached to a document and two screens connected to one computer state store communicate different relationships; these are examples, not mandatory shapes. Variety without meaning adds decoding work. -- **Find the explanation locally.** Put direct labels and annotations beside the data or object they explain, define unfamiliar terms at first use, and reuse those names. Keep relevant values in the sentence making the claim. State the comparison the reader needs rather than making them compute it; for example, include the percentage change alongside absolute values when it matters. On long artifacts, retain useful context without multiplying sticky chrome. - -## Failure modes the agent reaches for - -- **Dashboard tile-grid with no hierarchy.** 12 same-sized tiles in a 4×3 grid; reader doesn't know where to look first. Pull the focal KPI into a larger tile. -- **Every section opens with context.** "Before we dive in, it's worth establishing…" — burns the reader's attention on prep before the payload. Lead with the payload. -- **Six CTAs on the same screen.** Reader can't pick. Demote five to secondary or remove. -- **Legend with 9 colours.** Reader can't hold the mapping. Re-encode (sort + group + "other"), or render as small multiples. -- **Long table with no grouping.** 50 rows, alphabetical, no visual chunks. Either chunk by category, or sort by value with a hairline between every 5 rows, or paginate. -- **Footnote rabbithole.** Every body sentence carries a superscript; reader stops reading the body and starts following footnotes. Trim to citations that genuinely move the argument. -- **Sticky chrome everywhere.** Sticky header + sticky sidebar + sticky footer + sticky table-of-contents = the artifact's viewport is half-chrome. One sticky element max. -- **Inline code that scrolls horizontally.** Reader has to side-scroll mid-paragraph. Either shorten the snippet, wrap, or push to a code block. -- **The "everything is bold" hierarchy.** When every section header is 700-weight 28px, nothing is primary. Use weight + size + space together to carry hierarchy; not weight alone. -- **Claim outside the F-pattern scan path.** Long-form prose with the key claim buried mid-paragraph. F-shaped scanning is what readers default to when formatting is weak — they sample left-edge first-words and miss whatever isn't there. Surface the claim through scan-visible structure (lead-in sentence, bold span, sub-heading); see [copy.md](copy.md) for the prose-shape side. -- **CTA outside the Z-pattern landing zone.** A landing-style artifact with the primary CTA at the top-left. The reader's eye finishes its Z at the bottom-right; that's where the CTA earns its place. -- **Hidden disclosure.** Apply the Progressive disclosure boundary above; supplementary depth must not conceal interpretation-changing information. -- **Configurability over defaults.** Every setting surfaced because "the reader knows best." Default everything to the right answer; expose settings only when the right answer genuinely varies per reader. -- **Pattern reinvention.** A novel table that isn't sortable; a chart with a custom legend that requires onboarding; a nav with a novel interaction. Jakob's law applies — the cost of "wait, how does this work" is paid once per reader and never recovers. diff --git a/skills/visualize-lite/reference/color.md b/skills/visualize-lite/reference/color.md deleted file mode 100644 index 3413c4cd..00000000 --- a/skills/visualize-lite/reference/color.md +++ /dev/null @@ -1,105 +0,0 @@ -# Color - -Use color for reading hierarchy, meaning, and atmosphere. Apply the resolved visual context from SKILL.md, including approved artifact-theme exceptions. Color refinement does not authorize replacing project identity or writing project tokens. - -## Decide what color should do - -Start with the brief and representative content. Identify settled choices, then name the open question: mood, clearer emphasis, readable text on a surface, or a supporting accent. A report may need only ink and paper; a poster may use large colored fields; an interface may also need action and state colors. - -Explain alternatives through the dimensions that actually differ: - -- **Character:** pale, muted, vivid, or deep. Lightness and chroma can change character without changing hue. -- **Relationships:** related hues, warm/cool tension, complements, or several accents. Hue-wheel geometry is a starting idea, not proof that colors work together or clash. -- **Distribution:** area, placement, repetition, and prominence. A small vivid mark and a full colored canvas can use the same palette but direct attention differently. - -Neutral gray, tinted neutrals, white, black, red-led identities, and multiple accents are valid. Sparse accents and 60/30/10 are optional composition heuristics, not limits. A color field can establish atmosphere without encoding a data category or UI state. - -## Separate colors from roles - -Palette values describe available colors. Semantic roles describe their use: canvas, text, emphasis, supporting accent, atmosphere, action, state, or data. One color can serve compatible roles; one role may need different shades across surfaces and themes. Do not invent a hue for every role. - -Use the existing semantic token surface. Add only roles the artifact needs; a report need not display success/error controls because its reference package includes those tokens. Keep required token fields intact when deriving a complete system. - -Meanings follow context. Red can be a brand primary, a chart series, or a destructive action. Where destructive actions exist, distinguish them from ordinary actions through labels, grouping, and more than color alone. Preserve established category and state mappings. Do not reserve a hue across every artifact. - -Chart scale and series decisions live in [data-viz.md](data-viz.md). Logos and factual imagery retain their approved colors unless alteration is explicitly in scope. - -## Make feedback testable - -“Quieter secondary” may mean lower chroma, different lightness, less area, less prominent placement, or another hue. Choose the interpretation supported by context; ask when ambiguity would change an approved choice. “Opposite” may mean a hue complement, warm versus cool, or light versus dark. - -For a focused comparison, preserve settled choices and an unchanged control. With Coral primary fixed, compare a softer secondary or the same secondary covering less space; change hue only when that dimension is open. Explain the changed variable on representative content. Swatches and role labels supplement the rendered comparison, not replace it. No fixed count, harmony sequence, or palette score is required. - -For an approved image, website, or artifact input, use the existing input workflow. Sampled prominence is evidence, not semantic authority: the largest region may be a background rather than an accent. Propose roles and supporting shades without silently replacing approved identity. - -## Derive usable values - -Preserve an existing system's color notation. For new web palettes, prefer OKLCH when adjusting lightness and chroma independently: - -- **L** controls perceptual lightness, not contrast ratio. -- **C** controls chroma. Gamut depends on lightness and hue; no universal chroma ceiling is reliable. -- **H** controls hue. Equal angular spacing does not guarantee equal perceptual distinction or a successful palette. - -Reuse the palette helper and existing color math for new OKLCH ramps, gamut budgets, and contrast. Near white or black, less chroma is generally available. Inspect the result rather than assuming equal numerical steps look equally spaced. Generated accent angles, neutral tints, state hues, and seed strategies are suggestions, not restrictions on approved palettes. - -`palette.mjs --check` validates its supported OKLCH token surface, not taste or arbitrary CSS. For other color formats or computed colors, verify resolved pairs with existing browser contrast checks. Do not rewrite approved colors merely to satisfy a helper's input format. - -## Verify actual pairings - -Check the rendered foreground against its real background at the actual text size and weight: - -| Content | WCAG AA minimum | -|---|---| -| Ordinary text, including small labels | 4.5:1 | -| Large text: at least 24px normal or about 18.67px bold | 3:1 | -| Visual information needed to identify controls, states, or meaningful graphics | 3:1 against adjacent colors | - -Do not round failures up. Check relevant hover, focus, selection, and theme states. Logos, decorative elements, and inactive controls have specific exceptions; ordinary prose does not. Use stronger contrast for demanding viewing conditions. The seed helper retains its conservative 7:1 body-token floor; generated ramps still need rendered verification. - -“Gray on color,” “blue on red,” and “yellow on white” do not fail merely because of their names. Some pairs need lightness changes or can vibrate perceptually. Check actual contrast and readability, then adjust the responsible foreground, surface, weight, or placement. For imagery, gradients, or translucent overlays, inspect the least favorable region behind the text; one sampled pixel or token pair is insufficient. - -Color cannot be the only way to convey required information. Add meaningful labels, shapes, patterns, or other cues. Grayscale review can expose missing cues but does not prove contrast or color-vision accessibility. Simulate relevant vision deficiencies when judging category or state discrimination. - -## Compose both themes - -Design light and dark as related treatments, not mechanical inversions. Adjust foregrounds, surfaces, and accents to retain hierarchy and meaning. An accent may need a lighter, darker, or less chromatic counterpart, or may remain unchanged when it works in both contexts. Test rather than forcing a percentage change. - -Black, neutral gray, and tinted dark surfaces are valid. Separate neighboring surfaces where content or interaction needs it; elevation need not invent a fixed number of panels. Recheck text, focus, overlays, and data encodings in each mode. - -### Explicit theme overrides - -Preserve the existing three-path token contract: - -```css -:root { /* light tokens */ } -[data-theme="dark"] { /* explicit dark override */ } -@media (prefers-color-scheme: dark) { - :root:not([data-theme="light"]):not([data-theme="dark"]) { - /* system dark fallback */ - } -} -``` - -System dark never overrides explicit light. The display.dev bar sets `data-theme` on ``. Follow Explore's direction-scoping contract in comparisons so one candidate's tokens cannot recolor another. - -## Apply through tokens - -Use semantic tokens in authored styles. Preserve an existing primitive/semantic structure rather than creating a second token model. Relative color syntax and `color-mix()` can derive values, but their output still needs contrast and gamut checks. `light-dark()` requires an appropriate inherited `color-scheme`; theme controls must update that scheme when the function is used. Keep a verified fallback for newer CSS functions whose target support is not established. - -Transparency is useful for overlays and atmosphere. It makes the result depend on underlying content; define purposeful overlay tokens and test the actual composite instead of stacking arbitrary alpha values. - -## Review failures, not hue names - -- A new palette silently replaces approved colors or meanings. -- Accent placement or coverage obscures hierarchy. -- A suggested tint, color count, or harmony angle becomes a mandatory gate. -- Swatches hide text, surface, or responsive failures. -- A passing token pair is reused on a different background without checking. -- State or data distinctions disappear without hue differences. -- An aesthetic finding names a hue but cannot explain a mismatch with the brief or artifact. - -Unrelated SKILL.md pattern rules still apply, including gradient text. Removing hue-only refusal does not make every palette effective. - -## Sources - -Selected principles were adapted from [Impeccable colorize](https://github.com/pbakaus/impeccable/blob/main/plugin/skills/impeccable/reference/colorize.md) by Paul Bakaus (Apache-2.0) and [Color Expert](https://github.com/meodai/skill.color-expert/blob/main/SKILL.md) by meodai (CC BY 4.0; separate terms apply to third-party references). The procedure and wording here are adapted for Visualize; their tool requirements and full workflows are not imported. Accessibility requirements follow [WCAG contrast](https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html) and [use of color](https://www.w3.org/WAI/WCAG22/Understanding/use-of-color.html). diff --git a/skills/visualize-lite/reference/copy.md b/skills/visualize-lite/reference/copy.md deleted file mode 100644 index 72a00929..00000000 --- a/skills/visualize-lite/reference/copy.md +++ /dev/null @@ -1,118 +0,0 @@ -# Copy - -Voice, tone, and prose discipline for visualize artifacts. Brand profile overrides every rule below — if `PRODUCT.md` declares voice, tone, or audience, use it. The rules below are the fallback when the brand profile is silent. Artifact prose is not app UI microcopy: some patterns transfer (active voice, present tense, specific claims), others don't (form-instruction shape, error-dialog phrasing, translation-expansion budgets) — copy.md sticks to the artifact register. - -## Voice baseline - -Subject-verb-object, active voice, present tense. Artifact prose is doing work — making a claim, explaining a system, presenting a finding. Passive-voice hedging dilutes the work. - -- *"The migration moves 50M rows in batches of 10K."* — direct, claims a fact. -- *"50M rows are migrated by the process in batches that have been set to 10K."* — passive, distances the prose from the work. - -Sentences land. Don't open with "It is worth noting that…" or "It should be considered that…" — drop the wrapper and state the thing. - -**Default register when `PRODUCT.md` is silent**: Google developer-documentation style. Present tense, short declarative, second person for actions ("you can"), no marketing voice, no hype words. Suits most technical artifacts as a starting point. - -## Tone register - -| Brand `voice` | Register on prose | -|---|---| -| `technical` (Stripe, Bun) | precise, unhedged when the API is deterministic | -| `analytical` (Stratechery) | structural, framework-first, long sentences when building, short on conclusions | -| `opinionated` (a16z, 37signals) | thesis-first, future-tense, founder-positive | -| `craft` (Hoefler & Co) | meticulous, historically-grounded, vocabulary-rich | -| `institutional` (Aurelius) | measured, cited, footnoted | -| `pragmatic` (BIG) | explanatory-then-poetic; constraint-then-resolution | - -Pick from the brand's declared `voice`, not from a generic "professional" default. - -**Voice is stable across the brand; tone shifts by moment.** An analytical brand writing a postmortem reads more formal than the same brand writing a release announcement — same voice, different tone. Match the moment without losing the voice. - -## Technical writing - -- **Cite the number, not the qualitative claim.** "3x faster install (1.2s vs 3.6s)" beats "much faster install." -- **Use the precise term once, then re-use it.** Don't alternate "session token" / "auth token" / "credential" / "cookie" in one paragraph as a stylistic variation. -- **Code identifiers in mono.** `{snake_case}` for identifiers, parameter names, file paths. Inline mono carries the typographic signal "this is a literal." -- **Inline links over footnote chrome** for short artifacts; footnotes only when the artifact carries an editorial register (Whitepaper and Editorial design systems). -- **Define the term once, near first use.** Introduce the precise term inline the first time it appears in body prose, then re-use unchanged. A late-defined glossary at the end of the artifact asks the reader to round-trip. -- **One hedge per claim, maximum.** "It might be possible that we could perhaps consider…" stacks four hedges on one verb. Pick the strongest hedge that's still honest, drop the rest. - -## Headings and titles - -Heading text carries the information hierarchy; don't skip levels (H2 → H4 with no H3 confuses scan order). Recognition cues follow [Cognitive offloading](cognitive-load.md#cognitive-offloading). Avoid decorative trailing colons and parenthetical descriptors. - -- **Sentence case for body headings; preserve proper nouns and product names.** "OAuth 2.0 flow", "Stripe webhook payload" — capitalize the proper noun, leave the rest lowercase. -- **Each heading carries the gist of its section.** "Migration moved 50M rows in 4h12m" beats "Migration results"; "OAuth flow drops a session cookie at `/auth/callback`" beats "OAuth flow." -- **No question-form headings** unless the brand's voice is genuinely conversational ("How does the migration handle replicas?" reads as a Q&A doc; "Migration replica behaviour" reads as a reference doc). - -## Scannable structure - -Readers scan before they read. Prose shape determines whether the scan finds the claim. - -- **Short paragraphs** (3-5 sentences max). Beyond five, the eye loses the through-line. -- **Front-loaded claims.** Lead the paragraph with the noun + verb that carry the point; supporting detail follows. -- **Bullets vs numbered lists.** Bullets when order doesn't matter; numbered when steps depend on each other. Keep parallel structure across items — every item starts with the same part of speech. -- **Don't bullet what should be prose.** Three connected sentences are clearer than three disconnected fragments. Bullets that read as fragments of one paragraph belong as a paragraph. -- **Consolidate repeated explanations within the authorized scope.** Keep repetition when it serves a different reader need. A closing synthesis connects findings rather than reproducing every preceding explanation; preserve required source content and interpretation-changing qualifications. - -See [cognitive-load.md](cognitive-load.md) for the F-pattern / Z-pattern scan-path mechanics; this section owns the prose-shape side of scannability. - -## Numbers, dates, units, locale - -One format per artifact. Switching mid-document forces the reader to re-parse. - -- **Dates**: ISO 8601 (`2026-05-24`) for technical artifacts. Long-form (`24 May 2026` or `May 24, 2026`) only when the artifact's register is editorial and the locale is fixed. -- **Units adjacent to number** with non-breaking space (`12 ms`, `4.2 GB`). Don't wrap the unit onto the next line. -- **Spell the unit on first use in prose, abbreviate in tables.** "12 milliseconds" then "12ms"; tables go straight to abbreviated. -- **Don't manufacture precision.** "87.3% of users prefer X" from a sample of 20 reads as overclaim. Round to the precision the sample supports ("about 9 in 10 of the 20-person panel"). -- **Tabular numerals on data displays.** See [typography.md](typography.md) for `font-variant-numeric: tabular-nums`; copy.md just notes that misaligned digits in a table read as broken. - -## Captions, labels, chart copy - -The text that travels with a visual carries half the visual's information. - -- **Chart titles are claims, not topics.** "Latency p99 doubled after the migration" beats "Latency over time." -- **Axis labels carry units** ("Response time (ms)" beats "Response time"). -- **Table column headers parallel in structure.** Either all noun phrases or all verb phrases, not a mix. -- **Captions earn their place by saying what the visual can't** — source, methodology, sample size, caveats. "Source: prod replica `db-east-1`, 2026-05-22; p99 only, errors excluded" earns its line; "A chart showing latency over time" doesn't. Narrative-on-data ("drop at 14:32 coincides with the deploy") goes on the chart annotation, not in the caption (see [data-viz.md](data-viz.md)). Remove captions that merely narrate the drawing or explain its styling; describe an otherwise unclear relationship when needed for understanding or a text alternative. - -See [data-viz.md](data-viz.md) for chart-annotation *placement*; this section owns the *words*. - -## Inclusive language - -- **Gender-neutral default.** Singular *they* for unknown persons; pluralize when it reads more naturally ("users open the dashboard" beats "the user opens their dashboard"). -- **Ability-neutral phrasing.** "Open the dashboard" beats "see the dashboard"; "find the setting" beats "look at the setting." -- **Drop idioms with baggage.** "Primary/replica" (not master/slave); "allowlist/blocklist" (not whitelist/blacklist); "main branch" (not master branch). -- **Don't manufacture politeness.** "Sign in" beats "Please sign in if you'd like to"; "Save" beats "Click save when you're ready." Politeness padding reads as condescension in technical prose. - -## Error, empty, success state copy - -Apply only when the artifact carries interactive surfaces — most static reports / decks / dashboards skip this section entirely. - -- **Errors: what + how.** "Couldn't reach the server (check your connection and retry)." Don't apologize, don't blame the user. -- **Empty states: what + why + next.** "No artifacts yet. Run `dsp publish` to ship your first." -- **Loading states: specific verb + object.** "Fetching deploys…" beats "Loading…"; "Rendering chart…" beats a bare spinner. -- **Success states: result + durable state.** "Saved. Version 3 is live." beats "Successfully saved." State what changed and what's now true; skip the "Successfully…" prefix that adds no information. -- **No humour in error copy.** A 500-error joke is funny once and abrasive on the second retry. Errors are not the moment to perform brand personality. - -## CTA and link copy - -- **Verb-first link text for action links.** "Download report" beats "Click here"; "Read the spec" beats "See more." -- **On surfaces asking the reader to act, one primary CTA per section / viewport.** Multiple primary-styled buttons compete; explanatory content need not invent an action. See [cognitive-load.md](cognitive-load.md) for decision architecture; depicted UI follows [interface-illustration.md](interface-illustration.md). -- **Link text reads as a self-contained action or resource out of context.** A reader skimming or a screen reader pulling out all the links should see "Download report", "Read the spec", "Open the dashboard", or — for resource links — "Migration plan v3", "Stripe webhook docs". Never "here", "this", "more info." - -## Failure modes the agent reaches for - -- **"Consider…" / "You might want…"** — hedged suggestion when the artifact has a position. Take the position; let the reader disagree. -- **"It's worth noting that…"** — wrapper around a claim. Drop the wrapper. -- **"In today's fast-paced world…"** — AI-slop opener. Cut and start with the specific. -- **"This adds visual interest."** — vague justification (in commentary, not in the artifact). Name what specifically. -- **Em-dash overuse.** Two em-dashes per paragraph is a tell. Use commas, parentheses, or sentence breaks for variety. -- **Corporate hedging.** "It is recommended that consideration be given to…" — pull the verb to the front and drop the recommend-shaped passive. -- **"In conclusion" / "To summarize"** — the final paragraph announces itself. Just write the final paragraph. -- **"Things to consider:" scaffolding.** "Some considerations:" / "Things to keep in mind:" framings before a list — drop the framing wrapper, just list the things. -- **Title Case On Every Heading.** H2/H3/H4 in Title Case throughout reads as 1990s corporate. Sentence case for body headings; preserve proper nouns and product names. -- **Hedged superlatives.** "Arguably the best", "perhaps the most important", "one of the leading" — either commit to the claim or remove it. Hedging strips the force without buying safety. -- **Buzz-phrase chains.** "Leverage cutting-edge solutions to deliver scalable, mission-critical outcomes." Generic enterprise vocabulary stacked without a specific claim. Replace with the specific. -- **Click-here links.** Link text that requires surrounding context to mean anything ("click here", "this", "see more"). Use verb-first action text or self-contained resource labels that read on their own. -- **Fragment-chain rhythm.** "Faster. Cheaper. Better." Sequential period-fragments in the same paragraph or hero. Reads as AI-generated even when the words are right. Use fragments in isolation, not chained. diff --git a/skills/visualize-lite/reference/create.md b/skills/visualize-lite/reference/create.md deleted file mode 100644 index 833fe086..00000000 --- a/skills/visualize-lite/reference/create.md +++ /dev/null @@ -1,91 +0,0 @@ -# Creation procedure (default `/visualize-lite `) - -Before following the instructions below, apply the shared rules in SKILL.md. - -The default invocation creates a new artifact. An explicit request to restyle an existing artifact uses this procedure with SKILL.md's Artifact themes contract: the existing content, structure, and behavior remain the source unless the request opens them to change. The agent picks a template across all 35 in `templates/`, composes against the resolved context and tokens from SKILL.md, and writes self-contained HTML. - -Autonomous harnesses (Codex-class) have been observed jumping to file writes before the user confirms direction. Gates below prevent that. - -## Preflight - -Before composing, confirm: - -1. **Context loaded.** Resolve artifact context, brand/context source, token source, and fallback behavior per SKILL.md before choosing a template or writing code. If fallback context is used because `DESIGN.md` / `PRODUCT.md` are absent, extract binding brand cues from project instructions: named fonts, palette roles, logo/mark rules, density/register, and anti-references. If those instructions cite token files, stylesheets, design guidelines, or logo components, read those files before composing. Do not treat those cues as optional mood. **Brand-source precedence when sources disagree:** persisted `DESIGN.md` and `tokens.css` win for visual tokens (colors, fonts, surfaces); `PRODUCT.md` wins for voice and audience; fallback project instructions fill gaps but do not override persisted brand tokens unless the user explicitly says so in the current request. Apply SKILL.md's Artifact themes contract before locking the visual context; approved artifact exceptions participate in that resolution. -2. **Brand-fidelity lock.** From the artifact context resolved in step 1, record the applicable invariants across the categories below. These are enforceable, not aspirational. Do not invent invariants where the brand profile is silent — absence is a valid answer for any individual category. - - **Named fonts.** If the brand profile or fallback source names specific fonts, those fonts must lead the body / heading / code CSS stacks in the artifact. `system-ui`, `Inter`, `-apple-system`, or any other generic sans cannot lead the stack unless the brand explicitly names them. - - **Named palette / color system.** If the brand profile names palette roles or uses a specific color system (OKLCH, `color-mix`, `lab`, `lch`), `--background` / `--foreground` / `--primary` / borders / surfaces must derive from that palette and use that color system, not from fallback cream-paper, generic grays, or seed-generated hues. - - **Named anti-references.** If the brand profile names specific aesthetics to avoid, the first viewport cannot instantiate them under another label. - - These invariants (whichever apply) are re-audited at pre-render time in build step 7. If any invariant is violated in authored CSS, revise before rendering — do not surface the artifact to the user. -3. **Product clear.** Topic understood: what's the artifact for, who reads it, when. -4. **Artifact mode picked.** Choose the composition mode from the reader job before selecting a template: - - `document` — evidence-heavy prose, strategy, research, reviews, briefs, sourced analysis, decisions, and tradeoffs. Default here when the reader needs to understand an argument. - - `canvas` — spatial synthesis where relationships, flows, maps, topology, or workshop framing are the main read. - - `dashboard` — repeated/status metrics where monitoring, comparison, exceptions, or operational scanning are the main read. - - `deck` — slide or presentation sequence where each screen is consumed separately. - - Mode describes consumption, not aesthetic register. A `document` can still use product-native, application-like, spatial, editorial, or technical-drawing grammar when that grammar expresses the source and brand. Do not let the conservative mode default choose the artifact's look for you. -5. **Template picked.** Select from `templates/*/template.md` using template name, description, Use when, and Do not use when. Templates are starting points, not fixed artifact shells: customize the chosen template to fit the reader job and source, then fall back to pattern recipes for recurring structures the template does not cover. Template names are weak evidence; required structure and reader job are strong evidence. Pick the most specific template only when its required structure fits the source without inventing missing fields. If a named template would force a time axis, live-status semantics, escalation path, operational checks, slide sequence, scoring model, or other content the source does not provide, choose the broader document template and borrow only bounded primitives. Bounded primitives are small structures inside the chosen mode — e.g. labeled time-window rows, status marks, compact scenario tables, one contained topology figure — not the named template's full chrome or layout system. State the choice with one-line rationale. -6. **Composition reflex check.** Before sketching shape, name up to three artifact-class defaults you must NOT assume — at least one when an obvious reflex exists for the class. The defaults are moves that appear across every AI-generated version of this artifact class regardless of source or brand — the median answer to the class's prompt. - - The reflex defaults for any artifact class tend to cluster around the dominant masthead pattern, the default section rhythm, the default verdict/answer placement, and the default secondary-navigation device (TOC, sidebar, sticky chrome). Identify the ones that apply to the class at hand (up to three). - - For each default named, cite the brand, source, or reader-job evidence that justifies an alternative shape — "the source doesn't make comparison claims, so no head-to-head primitive; replace with the source's strongest framing claim as the lede" is valid. "I want it to feel different" is not — that's novelty-for-its-own-sake. - - Output stays internal to preflight; the shape gate carries forward only the chosen alternatives, not the rejection process. If you cannot name even one obvious reflex default, the artifact class isn't class-saturated enough for this check — say so and skip. -7. **Shape sketched.** Derive sections from the source and reader task. For each substantial explanation, identify the reader question, the answer visible when scanning, and the evidence or qualifications needed to interpret it. Representation choices include prose for an argument, a table or aligned views for comparison, recognizable objects for entities, spatial composition for relationships, and interaction when a transition adds understanding. Choose for the question rather than treating these examples as required mappings; simple sections need no extra primitive. Load [cognitive-load.md's Cognitive offloading](cognitive-load.md#cognitive-offloading) when recurring entities or states need recognition cues, and its Progressive disclosure guidance when moving supporting detail out of the primary reading path. Ordinary heading/body hierarchy alone does not trigger it. Measured-data displays use [data-viz.md](data-viz.md); comparison/evidence tables use the table pattern. Relationship figures use [diagram.md](diagram.md) plus the tentative non-spatial type reference routed from `SKILL.md` before proposing the shape. Interface depictions use [interface-illustration.md](interface-illustration.md). Under SKILL.md's preservation scope, identify added summaries or interpretive labels in the shape proposal or change summary; distinguish them visually only when necessary to prevent misattribution to the source. When the proposed artifact has functional controls or changes state in place, load `patterns/interactive-states.md` now and record which controls function locally and which are illustrative or inert in the Interaction model. -8. **Mutation check.** If a target path was given, confirm whether overwriting or writing fresh. -9. **Illustration and evidence check.** Use supplied images or prior-art screenshots when they make an object, behavior, or distinction recognizable; name their role in the existing shape gate. Place evidence beside the claim it supports, with attribution and relevant caveats. Crop without removing context that changes interpretation; disclose consequential crops in the proposal or caption. Embed images in self-contained HTML without unnecessary full-resolution bytes. At their actual desktop and phone display size, the detail supporting the claim must remain recognizable. Crop, add a contextual inset, enlarge, or choose another illustration when needed; a caption cannot compensate for an unreadable focal detail. Interface depictions follow [interface-illustration.md](interface-illustration.md). If generated bitmap imagery would carry a source-backed role, decide whether it is essential or optional and name that role in the shape gate's visual system or primitives. Load [image-generation.md](image-generation.md) before choosing a route or generating bytes. If no safe route is available, optional imagery is omitted with a note; essential imagery blocks with setup guidance. Do not substitute a decorative gradient or crude SVG. - -Mode defaults are intentionally conservative. Phrases like "make it visual", "make it beautiful", "polish this", or "design this" do not by themselves justify `canvas`, `dashboard`, or `deck`. They still require a reader job that needs spatial synthesis, operational scanning, or presentation pacing. For `document` mode, visual craft comes from a visible argument: typography, rhythm, evidence structure, spatial grouping, tables, diagrams, contrast, and pacing. A document should still feel designed. Restraint is a tool, not the goal; do not let document mode collapse into a lightly styled memo. - -`document` mode defaults to an unboxed reading surface. Use boxes, cards, large radius, and display-scale type only when they clarify a real content role: repeated entities, independent state, comparison, alerting, or a deliberate cover moment. Do not use them to make ordinary sections, metadata, source lists, summaries, rules, lifecycle steps, or takeaways feel "designed." Brand tokens supply character; artifact mode decides scale. - -Document context belongs where it helps interpretation. Keep context needed to understand the opening near the title and argument; place supplementary provenance beside the relevant evidence or in supporting detail. Preserve required information without turning every available frontmatter field into opening metadata. Use quiet labelled context within the reading measure, not a competing right rail, split cover, or facts panel unless metadata is the main object, as in a reference directory. A TOC rail can help a long document but should not compete with its opening. - -Document prose stays prose. Do not promote full-sentence or paragraph-length claims into bold display blocks; that turns the reading hierarchy into a battle card. If a claim needs emphasis, use a short heading, a normal-weight lead sentence, or a labeled callout whose body remains body-weight. Bold belongs to phrase-level emphasis, row keys, states, totals, and focal values, not whole explanatory paragraphs. - -## Shape gate - -Before code edits, obtain approval for the proposed shape and visual scope. A user-supplied pre-confirmed brief or explicit restyling request covering the existing structure and proposed changes already provides that approval. Otherwise present a shape-inventory message and wait; an agent-authored brief does not approve itself. - -Render the message as ordinary markdown (bullets, bold) — never as a fenced code block. Tone is conversational; lead with the proposal, not with a banner. Don't print "STOP" or any other all-caps self-instruction as user-facing copy. - -Required content in the message: - -- **Mode:** document, canvas, dashboard, or deck + one sentence explaining the reader job. -- **Template:** picked template name + a one-line rationale for the pick. -- **Sections:** the section list, in the order they'll be composed, derived directly from the source's structure and framing claims — not from artifact-class convention. -- **Source-specific reading choice:** the one reading-order or composition choice the source itself motivates — e.g. "lead with the comparison table because the source is explicitly a comparison brief" or "open with the verdict because the source is explicitly advisory." If the source doesn't motivate a distinguishing reading choice, "default linear argument because the source is linear" is a valid answer — restraint paired with a source fact about the source's shape. Do not invent a distinguishing choice to satisfy this field. This is not "what makes this artifact distinctive"; it's "what reading choice the source requires." -- **Organizing idea:** how the composition itself makes the subject easier to understand. Name the relationship, contrast, sequence, decision, or object from the source that shapes the opening, section rhythm, or explanatory primitive. If the source has no useful organizing idea, say so and use a restrained reading experience instead of manufacturing one. This field is about the artifact's visual argument, not a decorative motif. -- **Visual system:** define the artifact's type role, color roles, surface roles, rhythm/variation rule, and how the source-specific reading choice and organizing idea are visually carried. Color roles must describe jobs, not swatches: thesis/verdict, metadata/navigation, evidence/comparison, risk/state, source/provenance, focal emphasis. Expressiveness may come from composition, type, density, spatial rhythm, imagery, interaction, or color; a restrained palette is valid when another dimension carries the point of view. -- **Primitives (only when a section requires one):** for each section that *requires* a primitive beyond prose (table for comparison, chart for measured data, code for a snippet, diagram for a relationship prose cannot carry), name the reader question, the primitive, and the section it serves. Sections that don't require a primitive stay as prose. There is no upfront primitive budget — primitives follow the sections that need them. -- **Interaction model (only when present):** name the functional controls and states, what changes locally, the keyboard and focus path, how updates are announced, and which actions remain illustrative or inert under the interactive-state recipe's capability boundary. -- **Relationship figures (only when present):** state the outer artifact shell independently from each figure. For every figure, name the source-backed entities and relationships, semantic pattern, routed type, direction and primary reading path, group/boundary meaning, complexity budget, desktop and mobile topology (including any split or redraw), and whether geometry is authored directly or calculated locally before inline-SVG normalization. Explain how meaningfully different entity kinds remain distinguishable and equivalent entities remain consistent. -- **Mobile shape:** how the first two phone screens read. Name what compresses, collapses, or disappears so desktop chrome doesn't become mobile clutter. If the header carries metadata, propose labeled fields rather than rounded chips, and keep them in the same reading flow as the title/thesis. -- **Brand:** one-line readout from the brand-fidelity lock (preflight step 2) — applicable font / palette-or-color-system / anti-reference invariants, or explicitly note none specified for a category. Don't enumerate token paths, CSS variable names, or hue values in the gate; if the user asks, expand on request. -- **Composition reflex consequence (only if preflight step 6 surfaced reflexes):** one sentence per chosen alternative — e.g. "avoiding the research-report masthead; opening leads with the strongest source claim." If preflight step 6 was skipped because the artifact class isn't class-saturated, omit this field. Do not surface the rejection process itself, only the chosen consequences. -- A single-sentence approval prompt at the end ("Sound right, or change something first?", "Want me to proceed?", etc. — vary it). - -If the proposed shape is `document` mode, viewport-scale heroes, metadata rails, facts columns, cards, or metric strips must earn their place by carrying the source-specific reading choice or a real reader task. They are not banned; they fail only when they are generic chrome, fake quantification, or competing metadata. Bars, score widgets, KPI tiles, radar charts, lane maps, and market maps require supplied evidence plus a reader job that needs that primitive. "Visual interest" is not a rationale. - -`shape=pass` only counts when the user separately approves the brief or supplies a pre-confirmed one. Self-authored briefs (the agent paraphrasing its own preflight and proceeding) do NOT count as approval. "The implementation will be semantic anyway" doesn't skip the gate. - -## Build (only after shape=pass) - -1. Read the selected template's `template.md`. -2. If the template frontmatter declares `shell: `, read `shells//README.md`. Read shell runtime files only when the artifact needs to inline or modify the mechanics. -3. Decide which pattern recipes are relevant from the user ask and template Creation guidance. Read only those `patterns/.md` files. Recipes are optional creation guidance, not automatic dependencies. -4. Compose a self-contained HTML artifact from the template contract, selected pattern recipes, brand tokens, and shell mechanics if present. Generated artifacts must not include comments or metadata naming the selected template, recipes, or shell; required runtime markup such as `` is allowed. -5. Inline the resolved design tokens into the artifact's ` pair — a tokens.css whose *comment* mentions - // "`; - highlight.addEventListener("animationend", () => { - highlight.remove(); - }, { once: true }); - document.body.append(highlight); - }, x2, y); - } - } - } - /** - * Drags an element over the given element or point. - * - * @returns DEPRECATED. When drag interception is enabled, the drag payload is - * returned. - */ - async drag(target) { - await this.scrollIntoViewIfNeeded(); - const page = this.frame.page(); - if (page.isDragInterceptionEnabled()) { - const source2 = await this.clickablePoint(); - if (target instanceof ElementHandle2) { - target = await target.clickablePoint(); - } - return await page.mouse.drag(source2, target); - } - try { - if (!page._isDragging) { - page._isDragging = true; - await this.hover(); - await page.mouse.down(); - } - if (target instanceof ElementHandle2) { - await target.hover(); - } else { - await page.mouse.move(target.x, target.y); - } - } catch (error) { - page._isDragging = false; - throw error; - } - } - /** - * @deprecated Do not use. `dragenter` will automatically be performed during dragging. - */ - async dragEnter(data = { items: [], dragOperationsMask: 1 }) { - const page = this.frame.page(); - await this.scrollIntoViewIfNeeded(); - const target = await this.clickablePoint(); - await page.mouse.dragEnter(target, data); - } - /** - * @deprecated Do not use. `dragover` will automatically be performed during dragging. - */ - async dragOver(data = { items: [], dragOperationsMask: 1 }) { - const page = this.frame.page(); - await this.scrollIntoViewIfNeeded(); - const target = await this.clickablePoint(); - await page.mouse.dragOver(target, data); - } - /** - * @internal - */ - async drop(dataOrElement = { - items: [], - dragOperationsMask: 1 - }) { - const page = this.frame.page(); - if ("items" in dataOrElement) { - await this.scrollIntoViewIfNeeded(); - const destination = await this.clickablePoint(); - await page.mouse.drop(destination, dataOrElement); - } else { - await dataOrElement.drag(this); - page._isDragging = false; - await page.mouse.up(); - } - } - /** - * @deprecated Use `ElementHandle.drop` instead. - */ - async dragAndDrop(target, options) { - const page = this.frame.page(); - assert(page.isDragInterceptionEnabled(), "Drag Interception is not enabled!"); - await this.scrollIntoViewIfNeeded(); - const startPoint = await this.clickablePoint(); - const targetPoint = await target.clickablePoint(); - await page.mouse.dragAndDrop(startPoint, targetPoint, options); - } - /** - * Triggers a `change` and `input` event once all the provided options have been - * selected. If there's no `` has the - * `multiple` attribute, all values are considered, otherwise only the first - * one is taken into account. - */ - async select(...values) { - for (const value of values) { - assert(isString(value), 'Values must be strings. Found value "' + value + '" of type "' + typeof value + '"'); - } - return await this.evaluate((element, vals) => { - const values2 = new Set(vals); - if (!(element instanceof HTMLSelectElement)) { - throw new Error("Element is not a