Files
claude_mac/lib/design-gate.md
T
bastien bd3e525bb3 feat(design-gate): ask for 21st login and wait instead of skipping
The design gate checked the 21st CLI with command -v only, so an
installed-but-signed-out CLI read as READY and every 21st step failed
downstream. tool_active now probes 21st whoami through a three-state
function: signed in (TWENTYFIRST_TOKEN or API_KEY_21ST set, or 'Logged in
as'), signed out (exact 'Not logged in'), unknown (rc != 0, timeout,
unexpected output). Signed out is a new verdict, SIGN-IN REQUIRED, exit
12: design-gate.md tells the orchestrator to ask the user to run
! 21st login, end the turn, re-run the gate on their reply, and to skip
21st only on an explicit 'proceed without 21st', never silently. Unknown
surfaces as exit 11 with the whoami diagnostic and a CLI-runtime remedy,
so a node/PATH failure can never loop on a sign-in prompt. INCOMPLETE
still wins. Hermetic suite lib/tests/design-tool-gate.test.sh (stub CLI,
fixture repo through DESIGN_GATE_REPO_OVERRIDE) covers every state.
2026-09-28 12:51:55 +02:00

217 lines
12 KiB
Markdown

# DESIGN GATE — Auto-detect design tasks, ensure the design toolchain is active
Inline snippet. Include in any agent STEP 0 that may touch UI/design.
## WHEN TO RUN
Run this gate when the task description OR target files match design signals.
## DETECTION
Check BOTH the task description AND the filesystem:
**Task description signals** (case-insensitive match on $ARGUMENTS):
- UI keywords: `component`, `button`, `card`, `modal`, `dialog`, `tooltip`, `dropdown`, `sidebar`, `navbar`, `header`, `footer`, `layout`, `grid`, `form`, `input`, `table`
- Style keywords: `css`, `style`, `theme`, `color`, `font`, `spacing`, `margin`, `padding`, `border`, `shadow`, `animation`, `transition`, `hover`, `motion`, `animate`, `responsive`, `dark mode`, `light mode`
- Design keywords: `design`, `ui`, `ux`, `visual`, `polish`, `pixel`, `figma`, `mockup`, `wireframe`, `prototype`
- Framework UI: `tailwind`, `styled-component`, `emotion`, `chakra`, `radix`, `shadcn`, `headless`
**Filesystem signals** (quick check, no deep scan):
- Target files have `.tsx`, `.jsx`, `.vue`, `.svelte`, `.astro`, `.css`, `.scss`, `.less`, or `.module.css` extension
- `tailwind.config` or `postcss.config` present in project root
- `tokens/`, `theme/`, or `design-system/` directory exists
- Storybook config (`.storybook/`) present
- Animation lib in `package.json` deps: any package `is_anim_lib_installed` recognizes (`lib/animation-lib-check.sh`, the single source)
## DECISION
Source of truth for activation is the **profile system** — never an atomic
per-tool toggle. The gate's whole job: confirm the design toolchain is active,
and if not, point at ONE command — `/profile design`.
### 1. Tier — does the gate even apply?
- **Trivial** (≤2 files, single cosmetic value, one CSS tweak — same scope as
`/hotfix`) → no design tools required. Skip the gate, proceed.
- **Build UI / design system / review-audit** → toolchain required, continue.
- In doubt (trivial tweak vs real UI change) → do NOT silently skip: ask the
user, or default to the Build tier.
Tier does NOT change WHAT gets checked. Every non-trivial design tier draws from
the one `design` profile — so the gate checks that profile's **design-core
tools** (the `# GATE-BLOCK:` allowlist in `design.profile`: ui-ux-pro-max,
frontend-design, emil-design-eng, design-motion-principles, design-html,
design-review, design-consultation, the `21st` CLI and `21st-ui-build` — the
canary for the whole 21st skill pack). The profile also bundles
browser/plan/shotgun tooling and graphify for convenience; those never trip the
gate. Motion (`design-motion-principles`) and static-HTML (`design-html`) are
already in the core set — checked regardless; their CLAUDE.md "+motion /
+static" notes say which tool you'll lean on, not a separate activation step.
`site-motion` (personal skill, site-level scroll/page choreography) rides the
same Build chain but isn't on the GATE-BLOCK list: it ships with the repo,
nothing to install or verify.
### 2. State — run the deterministic check
bash "$HOME/.claude/lib/design-tool-gate.sh"
It reads the design-core tools (`# GATE-BLOCK:` in `design.profile`) plus their
types (`profile.sh show design --plain`) and checks each on its own channel —
skill symlink, `claude plugin list`, `claude mcp list`, `command -v`. It never
reads `disabledMcpServers` (unreliable for bi-modal servers like context7).
The core set lives in `design.profile`, not in the script or here — single source.
Exit codes: `0` = ready · `11` = ready-but-unverified (proceed, but surface it) · `10` = incomplete (gate trips) · `12` = sign-in required (21st installed, signed out) · `2` = error.
### 3. Branch on the result
- **0 / `READY`** → proceed silently. Toolchain is active.
- **10 / `INCOMPLETE`** → STOP. The script reports up to three groups; relay
them and the remedy to the user:
🎨 DESIGN DETECTED — the design toolchain isn't fully active.
activate with /profile design: <skills / ui-ux-pro-max>
required + manual step: <e.g. 21st — needs the CLI>
→ run /profile design to activate it, then continue.
- **activate with /profile design** → skills + the plugin; `/profile design`
turns them on directly.
- **required + manual step** → required tools the profile can't flip silently.
**the `21st` CLI lands here: it TRIPS the gate** (it's required for Build),
it is NOT a silent "optional". `/profile design` symlinks the 21st skills,
but the CLI they shell out to is a global npm install: tell the user to run
`npm i -g @21st-dev/cli` then `21st login` (no API key, no MCP).
- Do NOT hand-activate individual tools. The profile is the unit of activation.
- **12 / `SIGN-IN REQUIRED`** → STOP. The 21st CLI is installed but `21st
whoami` reports signed out. Relay the script's block, then ask the user to
run `! 21st login` (the `!` prefix runs it in this session, browser flow,
saves a local token) — or run `21st login` in any terminal on this
machine, then reply (the token is a local file; any terminal works, `!`
in-session is just the convenient form). END THE TURN and wait. On the
user's reply, re-run `design-tool-gate.sh` before any 21st step: `READY` →
continue; still `12` → ask again once, then offer the opt-out. Explicit
refusal — the user answers "proceed without 21st" (or words to that
effect) → say visibly `21st skipped for this run at your request` and
continue with the rest of the toolchain, 21st steps left out; after that,
a later `12` in the same run is reported in one line, never re-asked.
Never skip silently ("not logged in, so we don't use it" is the failure
this branch closes). Never run `21st login` yourself — it opens a browser
and needs the human. `TWENTYFIRST_TOKEN` is a shell-profile setting
followed by a session restart, never an in-session `export` (tool calls
don't share a shell, and a secret doesn't belong in the transcript).
- **11 / `READY BUT UNVERIFIED`** → `claude` was unreachable, so the design
plugin (ui-ux-pro-max) could NOT be checked. Do NOT report a plain "ready":
proceed only after telling the user that N tool(s) went unverified and having
them confirm with `claude plugin list`. Fail-visible, not fail-silent. A
`21st (whoami: rc=… …)` entry in this block means the CLI itself could not
answer (a runtime/PATH problem, e.g. node under nvm) — the remedy is the
diagnostic the script prints, never a sign-in prompt; relay its own line.
### 4. Animation library — suggest-only (fires only on a real motion signal)
Orthogonal to the toolchain check above: §2-3 are about Claude's design TOOLS;
this is about the PROJECT's runtime dep. Evaluate it only once the toolchain is
resolved and you're actually proceeding with the build (READY, after the user
ran `/profile design`, or after the sign-in re-run returns READY). Never on
the INCOMPLETE stop path — that path has one action only (`/profile
design`); don't stack an optional note on it.
**Fires only when ALL THREE hold** — drop any one → no suggestion, stay silent:
1. **Motion signal** — the task matched a motion keyword from §DETECTION:
`animation`, `transition`, `hover`, `motion`, or `animate`. A static
button / card / layout with no motion signal needs no anim lib → skip.
2. **Stack eligible** — `detect_anim_eligibility` returns `eligible|…`.
3. **No anim lib yet** — `is_anim_lib_installed` finds none.
Only if condition 1 holds, run the helper for 2 and 3 — do NOT re-list packages
here; the helper's `is_anim_lib_installed` is the single source of which libs
count:
source "$HOME/.claude/lib/animation-lib-check.sh"
result=$(detect_anim_eligibility) # '<status>|<package>|<reason>'
status=$(echo "$result" | cut -d'|' -f1)
pkg=$(echo "$result" | cut -d'|' -f2)
reason=$(echo "$result" | cut -d'|' -f3)
if [ "$status" = "eligible" ] && ! is_anim_lib_installed >/dev/null; then
cmd=$(recommend_anim_install_cmd "$pkg") # pnpm/yarn/bun/npm per lockfile
# → surface the one-line suggestion below. Do NOT run $cmd.
fi
**Surface — always this single line (non-blocking, suggest-only):**
🎬 Stack motion-eligible (<reason>), no anim lib — `<cmd>`? (optional; say the word, I'll add it)
**Rules:**
- **Suggest-only, never auto-install.** Run `<cmd>` ONLY on explicit user
consent. BDR-005: mid-session + existing `package.json` = consent required —
same contract as `/onboard` STEP 2.5, opposite of `/init-project` STEP 5e
(auto-install on a just-validated fresh scaffold).
- **Non-blocking.** Never halts the build; NOT a second gate. The toolchain stop
(§3, exit 10) is the only STOP. Surface the line, keep going.
- **Stateless dedup, by construction.** The suggestion is ALWAYS the single line
above — no first-time-block / later-short split. That split would need session
state the gate doesn't have, and a file marker would persist forever (per
project, not per session). Determinism here comes from having nothing to
remember, not from a behavioral "the agent recalls it" guard. Re-fire is one
ignorable line, on a narrow population: condition 3 (`is_anim_lib_installed`,
10 libs incl gsap / react-spring / lottie) kills it the instant any anim lib
lands, so only "eligible + pure-CSS + actively declined" ever sees it twice.
- **Two "motion"s (agent-facing).** The lib `motion` (npm dep, this step) ≠ the
skill `design-motion-principles` (`# GATE-BLOCK:` core set, §2-3). The
toolchain check handles the skill; this step handles the lib. Don't conflate
them when talking to the user.
### 5. Impeccable design context — suggest-only (one check, one line)
Same class as §4: a PROJECT-side prerequisite, not a tool. `impeccable`
installs globally, but every one of its verbs reads a per-project `PRODUCT.md`
that only `/impeccable init` writes. Without it the skill runs on invented
context, which is worse than not running it — and nothing else in the process
says so, because init has to happen in the agent chat, not in an installer.
**Fires when BOTH hold** — else stay silent:
1. impeccable symlink present under `skills/` (non-blocking external — not on the `# GATE-BLOCK:` list, so §3 never checks it).
2. The project has no `PRODUCT.md` at its root.
Evaluate it on the same path as §4: after the toolchain resolves, never on the
INCOMPLETE stop path. One line, non-blocking:
🧭 impeccable has no project context here (no PRODUCT.md) — run `/impeccable init` first? (optional)
**Rules:**
- Non-blocking, and never run `init` unprompted: it interviews the user about
the product, so it needs their attention, not their absence.
- One line per session at most. A refusal is an answer; do not re-ask inside
the same task.
- Skip entirely for a review/audit of a single component and for any non-UI
work. This is for Build and design-system tiers.
### Other toolchains
The script defaults to the `design` profile. A task needing another profile's
toolchain passes it: `design-tool-gate.sh <profile>`. Scope comes from that
profile's `# GATE-BLOCK:` line (absent → every skill/plugin/mcp entry). The
remedy is always `/profile <that>` — a profile, never a lone tool.
## IMPORTANT
- Remedy is ALWAYS a profile (`/profile design`), never an atomic tool toggle —
the profile system is the single source of truth for what's active.
- the `21st` CLI is REQUIRED (it trips the gate) and `/profile design` cannot
install it — the gate names the two commands; surface them to the user.
Signed out → exit 12: ask `! 21st login`, wait, re-run; explicit opt-out
only, never a silent skip.
- The design-core set (what trips the gate) is declared in `design.profile` on
the `# GATE-BLOCK:` line(s) — edit there to add/remove a blocking design tool,
not in the script.
- The state check shells out to `claude` (plugin/mcp list): a few seconds.
Trivial / non-design tasks skip it entirely (no signal, or trivial tier).
- `design-tool-gate.sh`'s per-type state checks MIRROR
`profile.sh:skill_status()` — change one, sync the other, except the 21st
auth state: gate-only, no skill_status counterpart.
- Do NOT run this gate on pure backend/API/CLI tasks (no signals = no gate).