@bacnh85/pi-ux 0.4.7 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,123 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.6.0 (2026-09-17)
4
+
5
+ ### Added
6
+
7
+ - **`ux_audit` accepts `path`** — audit a CSS file verbatim (absolute or
8
+ cwd-relative) instead of retyping it into the `css` string. Incident-driven:
9
+ retyped/condensed CSS drifted (inlined DESIGN.md shadow values, mislabeled
10
+ colour pairs) and produced false gate failures and false confidence.
11
+ Exactly one of `path`/`css`; the tool guidance now leads with
12
+ "NEVER retype CSS when the file is on disk".
13
+
14
+ ### Changed
15
+
16
+ - **ux-capture skill**: new Interaction section — `web_interact` (pi-web
17
+ ≥0.16.0) as the default path for behavior verification, with the manual CDP
18
+ recipe (trusted input, double-unwrap, clipboard activation caveats) as
19
+ fallback; honest sub-500px capture + `reduced_motion` documented as native
20
+ pi-web behavior; the iframe-wrapper layout probe demoted to a manual-capture
21
+ fallback.
22
+ - **ux-design skill** Step 4: render-and-inspect loop gains "Interact &
23
+ verify" — click the primary CTA, submit the form, read back state with
24
+ `web_interact` before the audit gate.
25
+
26
+ ## 0.5.0 (2026-09-16)
27
+
28
+ ### Added
29
+
30
+ - **Step 1.5 — Derive a direction** in the ux-design skill: the positive
31
+ counterpart to the guardrail. From the brief's subject material, commit —
32
+ before any markup — to mood adjectives, a one-line visual concept, a type
33
+ voice, a color mood (hex), and exactly one signature element. Test for a
34
+ real direction: two different designers following it must produce visibly
35
+ different pages. Replaces the passive generic-default check.
36
+ - **Direction playbook** (injected, ux-design): the positive layer the skill
37
+ previously lacked. Typography-voice pairing table (editorial → Newsreader +
38
+ Source Sans 3, ledger → Spline Sans pair, technical → IBM Plex pair, warm
39
+ consumer → DM Sans, …) with off-table alternatives; color-mood construction
40
+ (temperature first, every neutral tinted, committed accent posture, band
41
+ rhythm); composition anatomy (hierarchy by scale/weight/color, hero formula,
42
+ rows-over-card-grids, shaped whitespace, depth via composition); the
43
+ signature element how-to; default-vs-directed contrast snippets.
44
+ - **Step 4 render-inspect is REQUIRED in strict mode** for multimodal models,
45
+ with a concrete LOOK checklist (squint test, dead zones, monotony, timidity
46
+ — "cover the logo, could this page belong to anyone?", type sizes, mood
47
+ visibility) instead of the previous "fix what regex gates can't see".
48
+ - **Style-direction starters** in ux-presets (Option B+): S1 Editorial print
49
+ (Newsreader + Source Sans 3, paper/ink/oxblood), S2 Ledger (Spline Sans
50
+ pair, desk/rule/tabular-nums, stamp badges), S3 Warm consumer (DM Sans,
51
+ linen/basil, category spines, freshness badges). All color pairs verified
52
+ against the shipped APCA implementation (Lc ≥75 body, ≥60 small-bold
53
+ badges). B1/B2 remain as neutral fallbacks.
54
+ - **`ux-routing` skill** — the model-routing table moved out of the injected
55
+ body into a consult-when-delegating reference skill (offsets the added
56
+ injection weight; routing prose was irrelevant to most non-delegating
57
+ turns).
58
+ - **`bench/` design-quality harness**: three fixed briefs (landing page,
59
+ dashboard, mobile screen), `run.sh` (headless pi run with pinned model +
60
+ screenshot capture at desktop/mobile widths), and a fixed six-axis rubric
61
+ (hierarchy, composition, typography, color/mood, copy, feel). Results are
62
+ gitignored; see bench/README section in the main README.
63
+ - **ux-capture: layout probe + viewport-truth section.** Headless Chrome
64
+ clamps window width to 500px, so a `--window-size=390` capture renders at
65
+ 500 and crops the PNG — right-edge "cuts" that aren't in your CSS. The
66
+ skill now documents the clamp and prescribes a 390×844 iframe-wrapper probe
67
+ (an iframe is a true 390px CSS viewport) that measures
68
+ `scrollWidth`/viewport honestly on the delivered file. Capturing mobile via
69
+ the wrapper is also required: never widen a viewport to make a problem
70
+ invisible.
71
+ - **ux-capture: capture with entrance animations disabled**
72
+ (`--force-prefers-reduced-motion`). Staggered page-load reveals with
73
+ `opacity:0` backwards-fill screenshot as blank sections otherwise — the
74
+ forced query doubles as a reduced-motion audit (every section must remain
75
+ fully readable with animations off).
76
+ - **ux-design LOOK checklist** is now pinned to the brief's target viewport,
77
+ with an explicit dead-zone check for app screens (content must not leave a
78
+ large empty region below the last element at the target height).
79
+ - **Polish pass (critic-driven):** a glm-5.3-flash calibration review against
80
+ a reference-grade page named five half-point gaps; each became a rule in
81
+ the Direction playbook — display scale floor (h1 ≥ clamp(2.75rem, 7vw,
82
+ 6.75rem), 3–4× body), signature elements at composition scale (≥25vw,
83
+ edge-bleeding, reduced contrast), a three-surface color budget (header
84
+ inherits the base), repeating rows as fixed column grids with a shared
85
+ terminal axis, and a 2–3-mark "punctuation kit" (accent H1 terminal,
86
+ framed pull quotes, stamps, texture glyphs — placeholder monograms banned).
87
+
88
+ ### Changed
89
+
90
+ - Step 0 rung 4 reframed: a preset is a **floor, not an identity** — after
91
+ dropping one in, the direction step must still bend display face, neutrals,
92
+ and accent until the page could not be mistaken for the stock preset.
93
+ - Step 5 gate table now lists exactly what `ux_audit` implements (contrast /
94
+ tokens / states+motion / slop tells); the unimplemented rows (token
95
+ coverage, shadow recipes, component hygiene) are documented as model-side
96
+ checks instead of implying mechanical enforcement.
97
+ - ux_audit tool description fixed: "block handoff until this fails to pass"
98
+ → "handoff is blocked until this passes".
99
+
100
+ ### Why
101
+
102
+ Field benchmarking (`bench/`, glm-5.3-flash, three briefs, fixed rubric —
103
+ hierarchy / composition / typography / color-mood / copy / feel, 1–5 each)
104
+ showed the 0.4.x skill produced correct-but-forgettable pages: ~70% of the
105
+ injected guidance was prohibitions, so the model played safe (uniform
106
+ sections, no signature, cream-clay/broadsheet cliché drift on landing pages).
107
+ Discipline without direction is half the method; 0.5.0 ships the other half.
108
+
109
+ **Measured:** baseline 0.4.7 = 60.5/90 → 0.5.0 = 79.5/90 (+19); after the
110
+ critic-driven polish pass, **landing = 29.5 and 29 on two consecutive runs
111
+ (goal ≥29)**, run total 84/90, and three new case types (portfolio, pricing,
112
+ settings-form) scored 28–28.5 on first try — all 15 scored pages pass
113
+ `ux_audit`, and a non-UI task with pi-ux active shows zero derailment.
114
+ Winning directions each run were distinct and subject-grounded (alpine
115
+ expedition log with route-card signature; white/ink/vermilion with an
116
+ elevation-profile card; forest-green badge-stamp identity; ink-ledger
117
+ dashboard with OVERDUE stamps; grocer's stock-card mobile screen; printed
118
+ monograph portfolio). Per-run evidence in `bench/results/*/SCORES.md`
119
+ (gitignored; scores reproduced here).
120
+
3
121
  ## 0.4.7 (2026-09-12)
4
122
 
5
123
  ### Fixed
package/README.md CHANGED
@@ -1,10 +1,10 @@
1
1
  # pi-ux
2
2
 
3
- Anti-slop UI/UX design discipline for the [Pi coding agent](https://pi.dev). Anchors a lintable **DESIGN.md**, runs **deterministic** slop-audit gates (APCA contrast + tokens + states + slop tells), and works **with text-only models** (DeepSeek-v4, GLM-5.2, Kimi K3) — `agy`/Gemini/Claude is optional polish, never the review gate.
3
+ Anti-slop UI/UX design discipline for the [Pi coding agent](https://pi.dev). Anchors a lintable **DESIGN.md**, **derives a design direction** (mood, type voice, color mood, signature element) from the subject, runs **deterministic** slop-audit gates (APCA contrast + tokens + states + slop tells), and works **with text-only models** (DeepSeek-v4, GLM-5.2, Kimi K3) — `agy`/Gemini/Claude is optional polish, never the review gate.
4
4
 
5
5
  ## Why
6
6
 
7
- AI-generated UI converges on the same defaults — purple/indigo glow, shadow-heavy cards, missing focus/disabled/error states because under vague direction, models reach for high-frequency statistical patterns. Slop is an **ownership problem**: "the design has no owner at the system level." The fix is shift-left: own the system in a DESIGN.md, then gate deterministically.
7
+ AI-generated UI fails in two directions. Without discipline it converges on slop — purple/indigo glow, shadow-heavy cards, missing focus/disabled/error states. Without direction it converges on the *correct but forgettable* default — Inter, a blue accent, white cards, timid sizes which passes every lint and still has no feel, because under vague direction models reach for high-frequency statistical patterns either way. The fix is both halves: **own the system** in a DESIGN.md, **derive a direction** from the subject, then gate deterministically.
8
8
 
9
9
  **Text-only models now lead frontend** (Kimi K3, an open MIT model, is #1 on the Arena.ai Frontend Code Arena, ahead of Claude Fable 5). Inside a fully-specified system they produce non-slop UI — which means the review gate can be mechanical, not a vision-LLM call.
10
10
 
@@ -45,23 +45,27 @@ The injected skill enforces **Constraint-First Design Generation**:
45
45
  ```bash
46
46
  npx @google/design.md lint DESIGN.md
47
47
  ```
48
- If absent, reuse a preset (`ux-presets` skill: shadcn/Material/Radix) or scaffold one once. pi-ux orchestrates `@google/design.md` via shell-out — **not** a runtime dependency.
48
+ If absent, reuse a preset or style-direction starter (`ux-presets` skill: shadcn/Material/Radix; S1 Editorial print / S2 Ledger / S3 Warm consumer) or scaffold one once. Presets are floors, not identities. pi-ux orchestrates `@google/design.md` via shell-out — **not** a runtime dependency.
49
49
  1. **5-field brief per screen** — user job, inventory, token constraints, required states, one reference.
50
+ 1.5. **Derive a direction** — from the subject's material, commit to mood adjectives, a one-line visual concept, a type voice (pairing table), a color mood (hex), and one signature element — before any markup. The test: two different designers following the direction must produce visibly different pages.
50
51
  2. **Generate fast, converge early** — text-only models inside the locked system; 2-loop convergence trigger.
51
52
  3. **Normalise** the draft back into tokens/elevation/spacing.
52
- 4. **Render & Inspect** (multimodal models) — reference-first capture, then screenshot your own build and LOOK. Default: local headless-Chrome capture read back inline (offline, no daemon); alternative: `web_screenshot` (pi-web 0.6.2+, PNG returned inline) at a daemon-reachable address (LAN IP / host.docker.internal — SSRF-protected daemons block private ranges; cloudflared tunnel as last resort). Judge at viewer resolution (1×–3×); never chase sub-visible precision. Skip when text-only — the deterministic gates are the whole loop.
53
+ 4. **Render & Inspect** (multimodal models; required in strict mode) — reference-first capture, then screenshot your own build and LOOK against a concrete checklist (squint test, dead zones, monotony, timidity, type, mood). Default: local headless-Chrome capture read back inline (offline, no daemon); alternative: `web_screenshot` (pi-web 0.6.2+, PNG returned inline) at a daemon-reachable address. Judge at viewer resolution (1×–3×); never chase sub-visible precision. Skip when text-only — the deterministic gates are the whole loop.
53
54
  5. **Slop-audit gate** — run `ux_audit` (measurable): APCA contrast, token coverage, state coverage, slop tells, tracked-eyebrow/near-black taste tells, reduced-motion coverage.
54
55
 
55
- Taste rules ship in the skill: named cliché clusters (the cream/terracotta "Claude look", acid-on-black, broadsheet kit, SaaS-card kit, template chrome), typography-as-personality, one-orchestrated-motion, design-writing rules, and the generic-default check ("would I produce this plan for any similar brief?").
56
+ The **Direction playbook** ships in the skill as the positive layer: a typography-voice pairing table (editorial → Newsreader + Source Sans 3, ledger → Spline Sans pair, …), color-mood construction (temperature, tinted neutrals, committed accent posture, band rhythm), composition anatomy (hero formula, rows-over-card-grids, shaped whitespace), the signature element, and default-vs-directed contrast snippets. Taste rules add the named cliché clusters (the cream/terracotta "Claude look", acid-on-black, broadsheet kit, SaaS-card kit, template chrome), typography-as-personality, one-orchestrated-motion, and design-writing rules.
56
57
 
57
58
  ## The `ux_audit` tool
58
59
 
59
60
  Deterministic slop-audit — no model needed, all gates are computable:
60
61
 
61
62
  ```
62
- ux_audit css="..." pairs=[{fg:"#111",bg:"#fff",label:"body",weight:400,size:16,min:4.5}]
63
+ ux_audit path="web/src/app.css" pairs=[{fg:"#111",bg:"#fff",label:"body",weight:400,size:16,min:4.5}]
64
+ ux_audit css="..." pairs=[...]
63
65
  ```
64
66
 
67
+ Pass `path` to a stylesheet file — it is audited **verbatim**. Never retype or condense CSS into the `css` string when the file is on disk: retyped copies drift (inlined tokens, mislabeled pairs) and cause false gate failures or false confidence. Exactly one of `path`/`css`.
68
+
65
69
  | Gate | What it checks |
66
70
  |------|----------------|
67
71
  | **Contrast (APCA)** | Perceptual APCA Lc per fg/bg pair (Lc ≥75 body, ≥45 large-bold, ≥30 non-text). hex or `oklch()`. Optional `weight`/`size` set the threshold. WCAG 2.x ratio shown as a compliance sidecar. |
@@ -75,25 +79,18 @@ Returns pass/fail per gate + a formatted report. In `strict` mode this is the ga
75
79
 
76
80
  ## Model routing (deterministic-first)
77
81
 
78
- The skill tells the agent which model to use for each step. The gate is mechanical, not a vision-LLM call:
79
-
80
- | Step | Best tool/model |
81
- |------|-----------------|
82
- | Define system (DESIGN.md) | Reuse a preset OR `agy_execute mode=plan pro-high` (Gemini) **once** |
83
- | Lint system | `npx @google/design.md lint DESIGN.md` (shell-out) |
84
- | Per-screen brief | **GLM-5.2** (1M ctx) |
85
- | Generate variants | **DeepSeek-v4**, **GLM-5.2**, or **Kimi K3** (text-only, inside constraints) |
86
- | Normalise into system | **DeepSeek-v4** or **GLM-5.2** |
87
- | Slop audit | `ux_audit` tool (deterministic) + DESIGN.md lint |
88
- | Optional polish (never a gate) | `agy_execute mode=accept-edits sonnet` (Claude) |
89
-
90
- **The inversion rule:** the cheaper/weaker the model, the MORE you must externalise constraints. **The deterministic-first principle:** don't spend vision-model quota on what `ux_audit` computes for free.
82
+ The full who-does-what table lives in the **`ux-routing`** skill (not injected consulted when delegating). The gate is mechanical, not a vision-LLM call. **The inversion rule:** the cheaper/weaker the model, the MORE you must externalise constraints. **The deterministic-first principle:** don't spend vision-model quota on what `ux_audit` computes for free.
91
83
 
92
84
  ## Skills
93
85
 
94
- - **`ux-design`** — the Constraint-First method + deterministic-first model routing (auto-injected by the hook when active).
95
- - **`ux-presets`** — reference design-system presets for Step 0: a lintable DESIGN.md starter, the shadcn/Material/Radix reuse table, and a CSS-only `:root` fallback. Reference only — no bundled CSS.
86
+ - **`ux-design`** — the Constraint-First method + Direction playbook (auto-injected by the hook when active).
87
+ - **`ux-presets`** — reference presets for Step 0: neutral Web/Mobile DESIGN.md starters, three style-direction starters (S1 Editorial print, S2 Ledger, S3 Warm consumer — APCA-verified pairs), the shadcn/Material/Radix reuse table, and a CSS-only `:root` fallback. Reference only — no bundled CSS.
96
88
  - **`ux-capture`** — the Step 4 render-and-inspect capture playbook: local headless-Chrome capture read inline vs daemon-rendered `web_screenshot`, LAN IP/host.docker.internal addressing, SSRF-blocked daemons, cloudflared tunnel as last resort.
89
+ - **`ux-routing`** — the model-routing table for delegating design steps (Define/Generate/Inspect/Audit) across agy/Gemini, Claude, DeepSeek, GLM, Kimi. Not injected.
90
+
91
+ ## Benchmark
92
+
93
+ `bench/` holds a design-quality harness: three fixed briefs (landing, dashboard, mobile), `run.sh` (headless `pi` run with the same model + screenshot capture), and a fixed scoring rubric. Used to measure output quality across pi-ux versions; results are gitignored.
97
94
 
98
95
  ## Configuration
99
96
 
@@ -1,4 +1,6 @@
1
1
  import { createRequire } from "node:module";
2
+ import fs from "node:fs";
3
+ import path from "node:path";
2
4
 
3
5
  const require = createRequire(import.meta.url);
4
6
  const {
@@ -67,9 +69,13 @@ function auditParametersSchema() {
67
69
  type: "object",
68
70
  additionalProperties: false,
69
71
  properties: {
72
+ path: {
73
+ type: "string",
74
+ description: "Path to a CSS stylesheet file to audit verbatim (absolute or cwd-relative). PREFERRED over retyping `css` — retyped copies drift (inlined tokens, mislabeled pairs) and cause false audit failures. Exactly one of path/css.",
75
+ },
70
76
  css: {
71
77
  type: "string",
72
- description: "CSS stylesheet content to audit (inline stylesheets, styled-components output, or a concatenated .css file).",
78
+ description: "CSS stylesheet content to audit (inline stylesheets, styled-components output, or a concatenated .css file). Prefer `path` for on-disk files. Exactly one of path/css.",
73
79
  },
74
80
  pairs: {
75
81
  type: "array",
@@ -92,6 +98,23 @@ function auditParametersSchema() {
92
98
  };
93
99
  }
94
100
 
101
+ /**
102
+ * Resolve the stylesheet to audit: a file `path` read verbatim (preferred —
103
+ * retyped `css` drifts and causes false gate failures) or inline `css`.
104
+ * Exactly one of the two.
105
+ */
106
+ export function resolveAuditCss(params, cwd) {
107
+ const css = typeof params.css === "string" ? params.css : "";
108
+ const hasCss = css.trim().length > 0;
109
+ const p = typeof params.path === "string" ? params.path.trim() : "";
110
+ if (hasCss && p) throw new Error("Pass exactly one of `path` or `css` — not both.");
111
+ if (!hasCss && !p) throw new Error("Pass a stylesheet to audit: `path` (preferred, read verbatim) or `css`.");
112
+ // Relative paths resolve against the tool-call cwd (falls back to the
113
+ // process cwd) — the session cwd can differ from this process's cwd.
114
+ if (p) return fs.readFileSync(path.resolve(cwd || process.cwd(), p), "utf8");
115
+ return css;
116
+ }
117
+
95
118
  export function formatAuditResult(result) {
96
119
  const lines = [];
97
120
  lines.push(result.pass ? "✅ UX AUDIT PASSED" : "❌ UX AUDIT FAILED");
@@ -170,9 +193,10 @@ export default function uxExtension(pi) {
170
193
  name: "ux_audit",
171
194
  label: "UX Slop Audit",
172
195
  description:
173
- "Run deterministic slop-audit gates on CSS: APCA contrast (perceptual; WCAG sidecar), off-system token values (hardcoded hex / ad-hoc shadows), missing interaction states (:focus-visible / :disabled + prefers-reduced-motion), and named AI slop tells (glassmorphism, gradient orbs, neon glow, default-card, tracked-out eyebrows, tinted near-black). No model needed — all gates are computable. In strict mode, block handoff until this fails to pass. AUDIT THE COMPLETE STYLESHEET, not fragments. If no contrast pairs are supplied, they are auto-extracted from rules that declare both colour and background.",
196
+ "Run deterministic slop-audit gates on CSS: APCA contrast (perceptual; WCAG sidecar), off-system token values (hardcoded hex / ad-hoc shadows), missing interaction states (:focus-visible / :disabled + prefers-reduced-motion), and named AI slop tells (glassmorphism, gradient orbs, neon glow, default-card, tracked-out eyebrows, tinted near-black). No model needed — all gates are computable. In strict mode, handoff is blocked until this passes. AUDIT THE COMPLETE STYLESHEET, not fragments. If no contrast pairs are supplied, they are auto-extracted from rules that declare both colour and background.",
174
197
  promptSnippet: "Run deterministic UX slop-audit (APCA contrast + tokens + states + slop tells)",
175
198
  promptGuidelines: [
199
+ "Pass `path` to the stylesheet file — it is audited verbatim. NEVER retype or condense CSS into the `css` string when the file is on disk: retyped copies drift (inlined DESIGN.md shadow values, mislabeled pairs) and produce false failures or false confidence.",
176
200
  "Contrast, token-coverage, and slop-tells are computable, not judgement — use this tool instead of eyeballing or calling a vision model.",
177
201
  "Pass fg/bg colour pairs (hex or oklch()) + optional weight/size to set the APCA threshold; the WCAG ratio is shown as a compliance sidecar. Omit pairs and they are auto-extracted from colour+background rules — but hand-picking catches text-on-inherited-backgrounds that auto-extraction misses.",
178
202
  "Audit the COMPLETE stylesheet — fragment input falsely fails the States gate (no interactive selectors present) and misses off-system values elsewhere.",
@@ -180,8 +204,8 @@ export default function uxExtension(pi) {
180
204
  "State coverage flags interactive elements (button/a/input/...) missing :focus-visible or :disabled rules.",
181
205
  ],
182
206
  parameters: auditParametersSchema(),
183
- async execute(_toolCallId, params, _signal, _onUpdate, _ctx) {
184
- const css = typeof params.css === "string" ? params.css : "";
207
+ async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
208
+ const css = resolveAuditCss(params, ctx?.cwd);
185
209
  const pairs = Array.isArray(params.pairs) ? params.pairs : [];
186
210
  const result = audit({ css, pairs });
187
211
  return {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bacnh85/pi-ux",
3
- "version": "0.4.7",
4
- "description": "Anti-slop UI/UX design discipline for your Pi agent — anchors a lintable DESIGN.md, runs deterministic slop-audit gates (APCA contrast + tokens + states + slop tells), works with text-only models, ships reference design-system presets.",
3
+ "version": "0.6.0",
4
+ "description": "Anti-slop UI/UX design discipline for your Pi agent — anchors a lintable DESIGN.md, derives a design direction (mood, type voice, color mood, signature), runs deterministic slop-audit gates (APCA contrast + tokens + states + slop tells), works with text-only models, ships reference design-system presets.",
5
5
  "keywords": [
6
6
  "pi-package",
7
7
  "pi",
@@ -12,20 +12,140 @@ description: >
12
12
 
13
13
  Judge captures at viewer resolution (1×–3×); never chase sub-visible precision.
14
14
 
15
- ## Default web_screenshot (pi-web ≥0.7.0, auto local detection)
15
+ **Disable entrance animations when capturing**: pass `reduced_motion=true` to
16
+ `web_screenshot` / `web_interact` (pi-web ≥0.16.0), or add
17
+ `--force-prefers-reduced-motion` to manual headless Chrome (or emulate the media
18
+ query). Pages rightly use staggered page-load reveals with `opacity:0`
19
+ backwards-fill — captured mid-animation they screenshot as blank sections,
20
+ and you will "fix" content that isn't broken. The same forced query doubles
21
+ as a reduced-motion audit: with animations off, every section must still be
22
+ fully visible and readable.
23
+
24
+ **Capture at the brief's target viewport.** Web pages: 1280–1440 wide. App
25
+ screens and mobile-first briefs: the width the brief names (usually 390) at
26
+ its target height (~844). pi-web handles this honestly now: `web_screenshot`
27
+ and `web_interact` with `width`/`viewport` below 500px automatically use CDP
28
+ device-metrics emulation (true 390px CSS viewport) and return a
29
+ `scrollWidth`/`innerWidth` probe — `scrollWidth > width` means overflowing CSS.
30
+ If you fall back to manual headless Chrome, know its trap: many builds
31
+ **clamp window width to 500px**, so a "390 capture" secretly renders at 500
32
+ and crops (see the layout probe below for the wrapper that does it honestly).
33
+ If content overflows or dead-ends at the target size, the page is broken —
34
+ **fix the page. Never widen the viewport to make a problem invisible.**
35
+
36
+ ## Interaction — web_interact (pi-web ≥0.16.0)
37
+
38
+ A screenshot proves the page LOOKS right; only interaction proves it WORKS.
39
+ After visual inspection, verify behavior with `web_interact` — one call = one
40
+ browser lifecycle: open `url`, run `steps` in order, get per-step results, a
41
+ final inline PNG, and a scrollWidth probe.
42
+
43
+ ```text
44
+ web_interact url="http://localhost:5173" viewport={width:390,height:844} \
45
+ reduced_motion=true grant=["clipboard-read","clipboard-write"] steps=[
46
+ {click: "#copy-btn"},
47
+ {evaluate: "document.getElementById('status').textContent", label: "status"},
48
+ {type: {selector: "#email", text: "a@b.co"}},
49
+ {press: "Enter"},
50
+ {wait_for: "[data-success]"}
51
+ ```
52
+
53
+ - **Trusted clicks**: steps click via CDP `Input.dispatchMouseEvent` at the
54
+ element's center — synthetic `el.click()` grants no user activation, so
55
+ `document.execCommand('copy')` and login/clipboard flows would silently fail
56
+ under it. Under a trusted click, copy returns true.
57
+ - **evaluate is double-unwrapped**: `Runtime.evaluate` nests the value at
58
+ `{result:{result:{value}}}` — the tool returns the real value; if you ever
59
+ hand-roll CDP, single-unwrapping yields `undefined` and makes the app LOOK
60
+ broken when it isn't.
61
+ - Steps stop at the first failure with the reason — a broken selector surfaces
62
+ loudly instead of no-op'ing later steps.
63
+ - Clipboard readback on insecure origins: there is no clipboard API to read
64
+ back with; `execCommand` returning true under a trusted click is the
65
+ strongest available signal (grant permissions for secure origins).
66
+
67
+ Manual CDP (fallback only, when web_interact is unavailable): launch Chrome
68
+ with `--remote-debugging-port=0`, read `<profile>/DevToolsActivePort` for the
69
+ ws URL, create targets over the websocket (`Target.createTarget` — not the
70
+ `/json/new` HTTP endpoint, whose method flipped to PUT), attach with
71
+ `flatten: true`.
72
+
73
+ ## Layout probe (fallback for manual captures)
74
+
75
+ Needed only when CDP tooling above is unavailable. Two Chrome facts make naive mobile checks lie:
76
+
77
+ 1. **Headless Chrome clamps window width to 500px.** A `--window-size=390`
78
+ capture renders the page at 500px and crops the PNG to 390 — cuts at the
79
+ right edge are the CROP, not your CSS. Detect it:
80
+ `--dump-dom` a page containing `window.innerWidth` — 500 at a 390 request
81
+ means clamped.
82
+ 2. `window.innerWidth` therefore never reports the true mobile viewport.
83
+
84
+ The honest way to see and measure a 390px screen: a **wrapper page with a
85
+ 390×844 iframe** (an iframe IS a true 390px CSS viewport, immune to the
86
+ clamp), rendered at a 500px window with `--allow-file-access-from-files`:
87
+
88
+ ```bash
89
+ cp index.html /tmp/page.html
90
+ cat > /tmp/wrapper.html <<'EOF'
91
+ <!DOCTYPE html><html><head><meta charset="utf-8"><style>
92
+ body{margin:0;background:#888}#frame{width:390px;height:844px;border:0;outline:2px solid #000}
93
+ </style></head><body>
94
+ <iframe id="frame" src="./page.html"></iframe>
95
+ <pre id="out">measuring…</pre>
96
+ <script>
97
+ const f=document.getElementById('frame');
98
+ f.addEventListener('load',()=>{
99
+ const d=f.contentDocument;
100
+ document.getElementById('out').textContent=
101
+ 'page scrollWidth:'+d.documentElement.scrollWidth+' / viewport:390';
102
+ });
103
+ </script>
104
+ </body></html>
105
+ EOF
106
+ "$CHROME" --headless --disable-gpu --allow-file-access-from-files \
107
+ --virtual-time-budget=6000 --window-size=500,900 \
108
+ --screenshot=/tmp/mobile-390.png "file:///tmp/wrapper.html"
109
+ "$CHROME" --headless --disable-gpu --allow-file-access-from-files \
110
+ --virtual-time-budget=6000 --window-size=500,900 \
111
+ --dump-dom "file:///tmp/wrapper.html" | grep -o 'page scrollWidth:[^<]*'
112
+ ```
113
+
114
+ **Pass = scrollWidth 390 / viewport:390**, and the screenshot shows the true
115
+ mobile render (grey letterbox on the right is the wrapper, not your page).
116
+ If scrollWidth exceeds 390, fix the CSS (min-width on rows/grid, an
117
+ unbreakable string, a fixed-width column) and probe again — on the delivered
118
+ file, after the last edit.
119
+
120
+ **Alternative: puppeteer-core device emulation** (when node ≥18 and npm are
121
+ available — no wrapper file, and it gives true `fullPage` + a JS overflow
122
+ probe): `npm i puppeteer-core` once, then launch with
123
+ `executablePath` pointing at installed Chrome and
124
+ `page.setViewport({ width: 390, height: 844, deviceScaleFactor: 2 })`.
125
+ `setViewport` is real device-metrics emulation — immune to the window clamp.
126
+ Probe with `document.documentElement.scrollWidth` via `page.evaluate` before
127
+ screenshotting; `page.screenshot({ fullPage: true })` for the tall capture.
128
+
129
+ ## Default — web_screenshot (pi-web ≥0.16.0, auto local detection)
16
130
 
17
131
  `web_screenshot` auto-routes localhost/LAN/file URLs to the locally installed
18
132
  headless Chrome and returns the PNG inline — no daemon, no manual commands:
19
133
 
20
- - `web_screenshot url="http://localhost:PORT"` — done; the model sees the render.
134
+ - `web_screenshot url="http://localhost:PORT" width=390 height=844` — the
135
+ model sees the render; below 500px the capture is CDP device-emulated
136
+ (honest viewport, no clamp) and includes the scrollWidth probe.
137
+ - `reduced_motion=true` disables entrance animations for the shot.
21
138
  - `full_page=true` captures a tall 8000px window; `wait_for` settles JS via
22
139
  `--virtual-time-budget`; `engine="local"` forces local on a public URL.
23
140
  - `web_pdf` works the same way (`--print-to-pdf`) for full-content archival.
24
141
  - If Chrome is missing: `web_status` shows `localChrome.path`; set `CHROME_PATH`.
142
+ - Empty replies / connection resets from a dev-server URL usually mean a
143
+ STALE HUNG server on the port (accepts TCP, returns nothing) — `lsof -ti
144
+ :PORT` and kill it before diagnosing the tools.
25
145
 
26
146
  ## Fallback — manual headless Chrome (pi-web <0.7.0 or if the tool errors)
27
147
 
28
- Headless Chrome writes the PNG; the `read` tool shows it inline (multimodal models see it).
148
+ Headless Chrome writes the PNG; the `read` tool shows it inline (multimodal models see it). Keep `--window-size` at the target viewport (e.g. `390,844` for a phone screen) — never widen it to hide overflow.
29
149
 
30
150
  - Linux: `google-chrome --headless --screenshot=/tmp/shot.png --window-size=1280,800 http://localhost:PORT` (or `chromium`)
31
151
  - macOS: `"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" --headless --screenshot=/tmp/shot.png --window-size=1280,800 http://localhost:PORT`
@@ -4,11 +4,13 @@ description: >
4
4
  Anti-slop UI/UX design discipline for AI-generated interfaces. Enforces
5
5
  industrial-design principles (Dieter Rams: honest, thorough to the last
6
6
  detail, as little design as possible) so output is a defensible system, not
7
- statistical-default slop (purple glow, shadow-as-texture, missing states).
8
- Covers the Constraint-First method: own the system via a lintable DESIGN.md,
9
- write a 5-field brief, generate inside constraints, normalise, render-and-inspect
10
- with vision (multimodal models see their own output), pass a measurable slop-audit
11
- gate. Works deterministically with text-only models
7
+ statistical-default slop (purple glow, shadow-as-texture, missing states)
8
+ and equally not the correct-but-forgettable default (Inter, blue accent,
9
+ timid sizes). Covers the Constraint-First method: own the system via a
10
+ lintable DESIGN.md, write a 5-field brief, DERIVE A DIRECTION from the
11
+ subject (mood, type voice, color mood, signature element), generate inside
12
+ constraints, normalise, render-and-inspect with vision, pass a measurable
13
+ slop-audit gate. Works deterministically with text-only models
12
14
  (DeepSeek-v4, GLM-5.2, Kimi K3); agy/Gemini/Claude is optional polish, never
13
15
  the review gate. Use when designing or building any UI — web, mobile, or
14
16
  desktop. Active via /ux lite|strict|off.
@@ -16,11 +18,20 @@ argument-hint: ""
16
18
  license: MIT
17
19
  ---
18
20
 
19
- # UX Design Discipline — Anti-Slop, Industrial-Design Method
21
+ # UX Design Discipline — Direction + Anti-Slop, Industrial-Design Method
20
22
 
21
- You implement UI INSIDE an existing design system. You do NOT invent visual
22
- language. Slop fills the gaps you leave — so you stop leaving gaps. The system
23
- is anchored by a repo-root **DESIGN.md** that every generation reads.
23
+ Design work has two moves, and skipping either produces slop:
24
+
25
+ 1. **Discipline** never drift into the statistical default: tokens only, named
26
+ elevation, full states, none of the banned tells. (Steps 0–5 + the audit gate.)
27
+ 2. **Direction** — always commit to a visual point of view drawn from the
28
+ subject, before any markup. (Step 1.5 + the Direction playbook.)
29
+
30
+ Discipline without direction gives the correct-but-forgettable page — Inter,
31
+ a blue accent, white cards, timid sizes — that no design lead would ship.
32
+ Direction without discipline gives purple glow and missing states. The
33
+ repo-root **DESIGN.md** anchors the system; the **direction brief** anchors
34
+ the taste. You produce both.
24
35
 
25
36
  ## The anti-slop guardrail (hard rules)
26
37
 
@@ -53,11 +64,11 @@ deterministic gate and the agent both read.
53
64
  1. **Repo-root `DESIGN.md` exists** → read it; use its tokens verbatim. This is the happy path.
54
65
  2. **An existing system is already wired in** (shadcn/MUI/Radix theme, Tailwind config) → reuse it — YAGNI applies to design systems too.
55
66
  3. **A reference site exists** (brand to match, look to echo) → `web_extract` it and distill its design DNA (4–6 core hex, type roles, radius/spacing rhythm) into a DESIGN.md starter, lint it, then work inside it.
56
- 4. **Nothing exists** → do NOT block. Pick a **medium-tuned preset** from the `ux-presets` skill (B1 Web vs B2 Mobile — infer from the task; **ASK only if web-vs-mobile is genuinely unclear**, because it changes touch targets, hover, and safe areas). Use the preset **in-context as the implicit system** for this generation, then offer to persist it to repo-root `DESIGN.md` so future sessions reuse it (don't auto-write — that's an unrequested file mutation).
67
+ 4. **Nothing exists** → do NOT block. Pick a preset from the `ux-presets` skill (B1 Web / B2 Mobile neutral, or a style-direction starter — infer from the task; **ASK only if web-vs-mobile is genuinely unclear**, because it changes touch targets, hover, and safe areas). Use it **in-context as the implicit system** for this generation, then offer to persist it to repo-root `DESIGN.md` (don't auto-write — that's an unrequested file mutation). **A preset is a floor, not an identity:** Step 1.5 must still bend its display face, neutrals, and accent until the finished page could not be mistaken for the stock preset. If your page could be the untouched preset, you did not design.
57
68
  5. **No preset fits** → generate a DESIGN.md once with `agy_execute mode=plan pro-high` (Gemini); thereafter text-only models are sufficient.
58
69
 
59
70
  The non-negotiables a preset/system must define before any screen is written:
60
- - **Tokens:** colour (one accent + neutrals), type scale, spacing (8px grid), radius, elevation.
71
+ - **Tokens:** colour (one accent + tinted neutrals), type scale, spacing (8px grid), radius, elevation.
61
72
  - **Elevation scale:** 3–5 named levels only. Map every shadow to one.
62
73
  - **State contract:** every interactive component declares all states (web: + `:hover`/`:focus-visible`; mobile: + `:active`/pressed, NO hover); motion ships with a `prefers-reduced-motion` fallback.
63
74
 
@@ -75,10 +86,29 @@ All five are required. If you can't produce inventory + states with confidence,
75
86
  4. **Required interaction states** per interactive component.
76
87
  5. **One reference** screen/component that already feels like the product (brand tone).
77
88
 
78
- **Generic-default check (before building):** if there is no DESIGN.md, sketch a mini plan first — 4–6 named hex, type roles, a one-line layout concept or ASCII wireframe, one principle. Then ask: *would I produce this same plan for any similar brief?* Every part that answers yes is a default, not a choice — revise it and say what changed.
79
-
80
89
  **Scale to the task:** internal single-file tools → one-line brief + one draft is fine; user-facing product UI → full brief + 2–3 variants. The gates are the same either way.
81
90
 
91
+ ### Step 1.5 — Derive a direction (before any markup)
92
+
93
+ This is the taste step, and it is not optional for user-facing UI. From the
94
+ brief, extract the subject's **material** and commit to a direction brief.
95
+ Test for a real direction: *two different designers following it must produce
96
+ visibly different pages.* If they would produce the same page, you wrote a
97
+ default, not a direction — revise.
98
+
99
+ 1. **Subject material** — industry, material, era, energy. "B2B invoicing" is a category; "the accountant's desk — paper, ink, ruled columns, stamps" is material. Design the material.
100
+ 2. **Mood adjectives** — three, committed ("calm, ruled, exact"). "Modern, clean, minimal" are the absence of mood — banned as answers.
101
+ 3. **Visual concept** — one line naming the world this page lives in ("an expedition logbook, not a travel-agency template"; "a ledger book, not an admin panel").
102
+ 4. **Type voice** — display + body pairing from the Direction playbook table (or a deliberate off-table choice). Never default to Inter/system-ui when the subject has a voice.
103
+ 5. **Color mood** — temperature, neutral tint, the one accent, written as hex (playbook below).
104
+ 6. **Signature element** — the one memorable thing (playbook below). Exactly one.
105
+
106
+ Say the direction in one line before writing markup —
107
+ `Direction: <concept> · type <display>+<body> · palette <hex> · signature: <element>` —
108
+ then generate inside it. When you catch yourself mid-build reaching for a
109
+ stock choice, ask the generic-default question: *would I produce this same
110
+ plan for any similar brief?* Every yes is a default masquerading as a choice.
111
+
82
112
  ### Step 2 — Generate fast, converge early
83
113
  Generate several variants, commit early to the direction with the clearest hierarchy under real content.
84
114
  - **Variants are files, not vibes:** write 2–3 drafts as separate HTML files (`.ux/drafts/<slug>-{a,b,c}.html`), screenshot each, view the images side by side, and commit to one direction before touching real code. When redesigning existing UI, first capture a BEFORE screenshot of the current page as reference context.
@@ -88,61 +118,129 @@ Generate several variants, commit early to the direction with the clearest hiera
88
118
  Replace ad-hoc colours with tokens, remap shadows to the elevation scale, snap spacing to the rhythm, turn one-offs into component variants.
89
119
  - Gate: sample 10 components, verify token mapping. If fewer than 8 map cleanly, stop and repair the baseline.
90
120
 
91
- ### Step 4 — Render & Inspect (vision verify — multimodal models)
121
+ ### Step 4 — Render & Inspect (vision verify)
92
122
 
93
123
  The deterministic gates check CSS text; they never see the page. When the
94
- generating model is multimodal (GLM-5.3, Claude, Gemini), close the
95
- loop with eyesthis is the single biggest quality lever for flash-tier
96
- models:
124
+ generating model is multimodal (GLM-5.3, Claude, Gemini) this step is
125
+ **REQUIRED in strict mode** you see your own output, which is the single
126
+ biggest quality lever for flash-tier models. Skipping it is a gate failure
127
+ even when `ux_audit` passes. (Text-only models, or genuinely no capture path
128
+ → skip; the deterministic gates are the whole loop.)
97
129
 
98
130
  1. **Reference-first.** If the task gives a URL or screenshot as the design target, capture it with `web_screenshot` (pi-web 0.6.2+ returns the PNG inline) BEFORE generating. Every visual judgment is made against that reference.
99
- 2. **Inspect your own build.** Serve the UI (dev server or `python3 -m http.server`), then capture and LOOK — default: local headless Chrome + `read` (renders inline; commands in the `ux-capture` skill; offline, no daemon); alternative: `web_screenshot` (pi-web 0.6.2+) at a daemon-reachable address (full playbook in `ux-capture`). Fix what regex gates can't see: broken layout, weak hierarchy, spacing rhythm, brand fit. Batch fixes, re-screenshot, stop when converged (same 2-loop trigger as Step 2).
100
- 3. **Visibility baseline.** Judge at what a viewer sees at 1×–3×. Nothing sub-visible can fail, and nothing sub-visible may be producedno ±1px claims, no per-pixel diffs, no instrument-read values on either side.
101
- 4. **Gates stay final.** `ux_audit` (Step 5) remains the blocking authority; vision settles only what looking can settle.
102
-
103
- Text-only models, or no capture path at all (no headless Chrome, no reachable `web_screenshot`) → skip this step; the deterministic gates are the whole loop.
131
+ 2. **Inspect your own build.** Serve the UI (dev server or `python3 -m http.server`), then capture and LOOK — default: local headless Chrome + `read` (renders inline; commands in the `ux-capture` skill); alternative: `web_screenshot` at a daemon-reachable address (full playbook in `ux-capture`).
132
+ 3. **The LOOK checklist** at the **brief's target viewport** (mobile briefs: capture at exactly 390 wide; never widen the capture to make a problem invisible that is cheating the loop). Fix every failure in one batch, re-capture, repeat (2-loop convergence trigger, then move on):
133
+ - **Squint test:** three distinguishable levels of hierarchy? Does the eye land first where it should?
134
+ - **Dead zones:** any region with nothing for the eye? Whitespace piling up on one side? On app screens: does content leave a large empty region below the last element at the target height?
135
+ - **Monotony:** consecutive sections with identical weight/background? Every content group in the same box?
136
+ - **Timidity:** cover the logo — could this page belong to anyone? Then amplify the display scale or the signature; the direction is not coming through.
137
+ - **Type & overflow at target width:** display sizes actually large? measure comfortable? **any horizontal scroll, cut-off text, or squeezed badges at the target width?** orphans, cramped labels?
138
+ - **Mood:** is the palette's temperature visible at a glance, or is it generic white+blue?
139
+ 4. **Interact & verify** — a screenshot proves looks, not behavior. Drive the page with `web_interact` (pi-web ≥0.16.0): click the primary CTA, submit the form, toggle a control, and read back state — steps stop at the first failure with the reason. Trusted CDP clicks grant user activation, so clipboard/login flows behave for real.
140
+ 5. **Visibility baseline.** Judge at what a viewer sees at 1×–3×. Nothing sub-visible can fail, and nothing sub-visible may be produced — no ±1px claims, no per-pixel diffs, no instrument-read values on either side.
141
+ 6. **Gates stay final.** `ux_audit` (Step 5) remains the blocking authority; vision settles only what looking can settle.
104
142
 
105
143
  ### Step 5 — Slop-audit gate (blocks handoff on fail)
106
144
 
107
145
  Run `ux_audit` on the generated CSS. The contrast gate reports **APCA Lc**
108
146
  (perceptual, primary — Lc ≥75 body / ≥45 large-bold / ≥30 non-text) with a
109
147
  WCAG 2.x ratio sidecar for compliance reporting. APCA catches dark-theme +
110
- thin-type slop that the legacy WCAG ratio misses. A 4th gate flags named AI
111
- tells (glassmorphism, gradient orbs, neon glow, default-card).
148
+ thin-type slop that the legacy WCAG ratio misses.
112
149
 
113
- | Gate | Pass | Fail action |
150
+ | Gate (implemented in `ux_audit`) | Pass | Fail action |
114
151
  |---|---|---|
115
- | Token coverage | ≥8/10 components map to tokens | Pause, repair baseline |
116
- | Shadow recipes | ≤3 named recipes on core surfaces | Collapse to named elevations |
117
152
  | Contrast (APCA) | Lc ≥75 body, ≥45 large-bold, ≥30 non-text (WCAG sidecar shown) | Block handoff until fixed |
118
- | State coverage | all interactive elements have focus + disabled; any transition/animation ships a prefers-reduced-motion fallback | Keep in draft |
119
- | Component hygiene | no duplicates, no frame-pile | Refactor before handoff |
120
- | Slop tells | no glassmorphism / orbs / glow / default-card / 1px-gray-border / tracked-out eyebrow / tinted near-black bg | Refactor: space → bg shift → elevation before a border |
153
+ | Tokens | no hardcoded hex outside `:root` definitions; no ad-hoc box-shadows built from raw values | Move values into tokens / named elevations |
154
+ | States + motion | interactive elements have `:focus-visible` + `:disabled`; any transition/animation ships a `prefers-reduced-motion` fallback | Keep in draft |
155
+ | Slop tells | no glassmorphism / gradient orbs / neon glow / default-card / 1px-gray-border / tracked-out eyebrow / tinted near-black bg | Refactor: space → bg shift → elevation before a border |
156
+
157
+ **Model-side checks** (not mechanically gated — you verify): token mapping ≥8/10 sampled components; ≤3 named shadow recipes; no duplicate components. A 4th slop gate flags named AI tells automatically.
158
+
159
+ ## Direction playbook (the positive layer)
121
160
 
122
- ## Model routing (who does what)
161
+ The guardrail stops bad; the playbook produces good. Pull from it in Step 1.5.
123
162
 
124
- Split the work along each model's strength. **The inversion rule:** the cheaper/weaker the model, the MORE you must externalise constraints. Taste lives in the brief, not the weights.
163
+ ### Typography voice
125
164
 
126
- **The deterministic-first principle:** the gate is mechanical (DESIGN.md lint +
127
- `ux_audit`), not a vision-LLM call. Text-only models now lead frontend
128
- (Kimi K3 is #1 on the Arena.ai Frontend Code Arena, ahead of Claude Fable 5) —
129
- inside a locked system they produce non-slop. agy/Gemini/Claude is optional,
130
- never the review gate.
165
+ Choose deliberately; 1–2 families with clearly distinct roles. Inter/system-ui
166
+ is the statistical default this method exists to escape — reach past it unless
167
+ the subject is genuinely neutral infrastructure. Pairings that work (Google
168
+ Fonts; first = display, second = body):
131
169
 
132
- | Step | Best tool/model | Why |
170
+ | Subject voice | Pairing | Why it works |
133
171
  |---|---|---|
134
- | **Define system** (DESIGN.md: tokens, elevation, type) | Reuse a preset (ux-presets) OR `agy_execute mode=plan pro-high` (Gemini) **once** | Preset is cheapest. Gemini = strongest visual reasoning for the one-time scaffold. |
135
- | **Lint system** | `npx @google/design.md lint DESIGN.md` (shell-out) | Deterministic token-ref + contrast + structure validation. |
136
- | **Per-screen brief** | Main Pi model: **GLM-5.2** | 1M ctx holds the whole DESIGN.md while scoping one screen |
137
- | **Generate variants** | Main Pi model: **DeepSeek-v4**, **GLM-5.2**, or **Kimi K3** | Text-only models lead frontend inside a locked system; cheaper than vision calls. |
138
- | **Normalise into system** | Main Pi model: **DeepSeek-v4** or **GLM-5.2** | Long context, token remapping, mechanical precision |
139
- | **Render & inspect** | The generating model itself, when multimodal (GLM-5.3, Claude, Gemini) via `web_screenshot` | Eyes on your own output beat rules in a prompt — flash-tier models produce notably better UI when they see the rendered result (inline image blocks) |
140
- | **Slop audit** | `ux_audit` tool (deterministic) + DESIGN.md lint | Contrast (APCA) + token coverage + slop tells are computable, not judgement |
141
- | **Optional polish** (never a gate) | `agy_execute mode=accept-edits sonnet` (Claude) or `opus` | Only if brand-fit is uncertain after the deterministic gate passes. NOT required. |
172
+ | Editorial / literary / journal | **Newsreader** + **Source Sans 3** | serif display at 500–600, tight leading; body stays quiet |
173
+ | Expedition / outdoors / heritage | **Bricolage Grotesque 800** + **Newsreader** | heavy grotesque display over a serif body reads "printed field guide" |
174
+ | Financial / ledger / legal | **Spline Sans** + **Spline Sans Mono** | mono for every numeral, `tabular-nums`; ruled borders do the branding |
175
+ | Technical / infra / dev tool | **IBM Plex Sans** + **IBM Plex Mono** | personality from weight contrast + hairline rules, not decoration |
176
+ | Warm consumer / food / home | **DM Sans** 700–800 display + body | geometric warmth; personality from color + radius + scale |
177
+ | Dense data / dashboard | Spline Sans pair or IBM Plex pair | personality from rhythm and status-color discipline |
178
+
179
+ Cautions: the cream+Fraunces+terracotta and Space-Grotesk-on-dark looks are
180
+ named cliché clusters below. Off-table picks that still carry voice:
181
+ Archivo, Schibsted Grotesk, Libre Caslon Text, Spectral (displays);
182
+ Public Sans, Instrument Sans, Work Sans, Outfit (bodies).
183
+
184
+ Numbers that make type feel designed: marketing h1 ≥ `clamp(2.75rem, 7vw, 6.75rem)`
185
+ (≈96–108px at 1440 — the hero must dominate at 3–4× body size or the scale
186
+ step is wrong); section h2 ≥ 2rem; stat/metric numerals ≥ 2rem with
187
+ `tabular-nums`; display weight 700–800 against body 400; body 1rem–1.125rem/1.6
188
+ at 45–75ch; display letter-spacing −0.01 to −0.025em. Timid sizes are the #1
189
+ "no feel" symptom — when in doubt, bigger display, fewer words.
190
+
191
+ ### Color mood construction
192
+
193
+ - **Temperature first:** warm or cool page? Then tint EVERY neutral with the mood hue at very low chroma — warm paper `#F6F4EE`, green-black ink `#22302A`, blue-gray desk `#F3F1EA`. Never pure `#fff`/`#000`.
194
+ - **One accent, posture committed:** deep + saturated (vermilion, forest, indigo, oxblood) beats bright + default. Pure blue `#0066FF` on white IS the default look. Test the accent's APCA pair before committing to it.
195
+ - **Support colors only when they encode meaning** (success/danger/warning), each with a soft tinted background for badges — never as decoration.
196
+ - **Bands give rhythm:** 2–3 background treatments across a page (paper → tinted → dark ink → paper). A dark band mid-page is a strong, cheap rhythm marker. Alternating white sections are not rhythm, they're fog.
197
+ - **Surface budget: three surfaces, one family.** Base, one warm/tinted mid, one inverse (dark) — all tinted by the same mood hue — plus the accent reserved for CTAs and marks. If the header/nav introduces a color the rest of the page never uses, delete it: the header inherits the base surface. Five unrelated surfaces read as template collage.
198
+
199
+ ### Composition anatomy
200
+
201
+ - **Hierarchy = scale + weight + color contrast**, not shadow boxes. If hierarchy needs a shadow, the scale is broken.
202
+ - **Hero formula:** kicker (small, real information) → one big claim (display face, ≥3rem, ≤9 words) → one sub (≤2 lines) → one primary action. No gradient-blob backgrounds; the whitespace and type ARE the design.
203
+ - **Rows beat card grids** for repeated content (trips, features, invoices, episodes) — and a row is a **fixed column grid, not flowing text**: 4–5 columns at identical x-positions across all rows (identity left, one datum per middle column, terminal value — price/CTA — right-aligned on a shared axis), hairline separators, equal row heights. Metadata never wraps to a second line; if it doesn't fit the column, the copy is too long. If you must card, vary the span — one wide, two narrow.
204
+ - **Shaped whitespace:** asymmetric gutters, a deliberately wide margin around one element, rag that breathes. Whitespace is a material you place, not what's left over.
205
+ - **Depth via composition** — overlap, scale steps, band shifts — not glow or shadow-piles.
206
+
207
+ ### The signature element
208
+
209
+ Exactly one memorable element, drawn from the subject, **scaled like
210
+ composition rather than framed like content**: ≥25% of the viewport wide,
211
+ allowed to bleed off the canvas edge, set at reduced contrast so it reads as
212
+ atmosphere — never a small bordered "illustration card" smaller than the
213
+ headline it sits beside. Forms that work: an oversized glyph or numeral from
214
+ the subject's own writing system, a rotated stamp/seal, index numerals treated
215
+ as design objects, a contour/texture system that fills the hero, one
216
+ full-bleed moment. Everything else stays quiet. If you cannot name your
217
+ signature element, you don't have one — and the page will be forgettable.
218
+
219
+ ### The punctuation kit (authored details, 2–3 per viewport)
220
+
221
+ Small deliberate marks are the difference between "clean" and "authored":
222
+
223
+ - one accent-colored terminal on the H1 (a colored period, a final word);
224
+ - pull quotes framed by a bracket, rule, or oversized mark — not a floating italic slab;
225
+ - one stamp/seal/badge marking the page's scarcity or guarantee claim;
226
+ - texture glyphs from the subject's language on repeated items (JP kanji beside route names, §, №, coordinates);
227
+ - terminal marks as data affordances (an arrow on prices/rows) — but NEVER appended to every link/button (that is the template-chrome cliché below).
228
+
229
+ Placeholder monograms (initials in a circle) are not punctuation — replace
230
+ them with role labels and one real credential line.
231
+
232
+ ### Default vs directed (feel the difference)
233
+
234
+ - *Default:* `Inter`, `#111` on `#fff`, `h1{font-size:2.5rem}`, blue button, three equal cards.
235
+ - *Directed (same brief, "ledger" concept):* Spline Sans + mono `tabular-nums`, desk `#F3F1EA`, ruled table borders, one rotated `OVERDUE` stamp badge, forest accent `#1E6B50`.
236
+
237
+ Same effort. One is a page; the other is a template.
142
238
 
143
- **Cross-family rule:** Gemini/Claude produce → deterministic gate reviews. Don't spend vision-model quota on what `ux_audit` computes for free. agy review is a fallback for aesthetic uncertainty, never the gate.
239
+ ## Model routing
144
240
 
145
- DeepSeek/GLM/Kimi K3 are safe for design **only inside a fully-specified system**. If no system exists yet, generate a DESIGN.md once (preset or agy), then text-only models are sufficient for every generation thereafter.
241
+ The who-does-what table lives in the `ux-routing` skill (not injected). Always
242
+ true: **the deterministic gate reviews; a multimodal model looks at its own
243
+ render; taste lives in the direction brief, not the model choice.**
146
244
 
147
245
  ## Banned anti-patterns
148
246
 
@@ -158,7 +256,7 @@ screen stays in draft until refactored.
158
256
  - Shadows as texture (drifting blur/opacity per component). Shadows = named elevation only.
159
257
  - Magic pixel values; off-scale font sizes; ad-hoc accent colours.
160
258
  - Shipping a component without `focus-visible` + `disabled` states.
161
- - Prompting "make it modern/clean" with no DESIGN.md — the single biggest slop trigger.
259
+ - Prompting "make it modern/clean" with no DESIGN.md and no direction — the single biggest slop trigger.
162
260
 
163
261
  ### Named cliché clusters (credit: anthropics/skills frontend-design)
164
262
  All legitimate for *some* briefs — but they are defaults, not choices, and appear regardless of subject. Where the brief pins a direction, follow it exactly; where an axis is free, don't spend it here:
@@ -172,8 +270,8 @@ All legitimate for *some* briefs — but they are defaults, not choices, and app
172
270
  ## Taste rules
173
271
 
174
272
  - **Ground it in the subject.** Distinctive choices come from the brief's industry, materials, and vernacular — a toy for kids and a trading dashboard should not share a visual language. If the subject is unclear, confirm it before designing.
175
- - **Typography carries personality.** Choose typefaces deliberately per project (1–2 families, clearly distinct roles); body lines under ~80 chars. Never accent a single word of a headline; no ALL-CAPS labels by default; structural devices (numbers, rules, eyebrows) only when they encode real information — `01 / 02 / 03` is for actual sequences.
273
+ - **Typography carries personality.** Choose typefaces deliberately per project (1–2 families, clearly distinct roles — the playbook table is the starting point, not the ceiling); body lines under ~80 chars. Never accent a single word of a headline; no ALL-CAPS labels by default; structural devices (numbers, rules, eyebrows) only when they encode real information — `01 / 02 / 03` is for actual sequences.
176
274
  - **Motion: one orchestrated moment.** A single page-load sequence or reveal lands better than effects scattered everywhere; fade-and-slide-up on every section is an AI tell. Motion that answers an action (opening, confirming) is welcome. Always ship a `prefers-reduced-motion` fallback.
177
- - **Design writing is design.** Use the user's words, not system words ("notifications", not "webhook config"). CTAs say what happens ("Save changes", not "Submit"); one name per action across the flow. Errors direct instead of apologising; empty states invite action.
178
- - **Spend boldness in one place.** One memorable element; everything around it quiet and disciplined. Quality floor without announcing it: responsive, visible keyboard focus, reduced motion, accessible contrast.
275
+ - **Design writing is design.** Use the user's words, not system words ("notifications", not "webhook config"). CTAs say what happens ("Save changes", not "Submit"); one name per action across the flow. Errors direct instead of apologising; empty states invite action. In a screenshot test, placeholder names ("Acme", "Lorem", "Feature One") read as template — write real content even in drafts.
276
+ - **Spend boldness in one place.** One memorable element (the signature); everything around it quiet and disciplined. Quality floor without announcing it: responsive, visible keyboard focus, reduced motion, accessible contrast.
179
277
  - **Data-viz rules (dashboards & charts).** Chart fills must pass non-text contrast (≥3:1) against their track. Adjacent categorical fills must be nameably different, not opacity steps; cap ramps at 7. Secondary series ≤0.85 opacity or a muted token; today/selected gets full accent. Numeric cells get `font-variant-numeric: tabular-nums`. Empty/zero chart states show an axis or "no data" slot, never a blank canvas. Tables: row hover on bg, right-aligned numerics, sentence-case headers.
@@ -3,11 +3,12 @@ name: ux-presets
3
3
  description: >
4
4
  Reference design-system presets for ux-design. Anti-slop shortcuts: instead of
5
5
  inventing tokens, elevation, and state contracts from scratch, reuse one of
6
- these battle-tested systems (shadcn/ui, Material 3, Radix) or drop in a
7
- medium-tuned DESIGN.md starter (Web or Mobile) as the implicit system when no
8
- DESIGN.md exists. Use when starting a new UI project with no existing design
9
- system, when Step 0 (Own the system) of the ux-design method has nothing to
10
- reuse, or when asked for a starter token set. These are REFERENCES only
6
+ these battle-tested systems (shadcn/ui, Material 3, Radix), drop in a
7
+ medium-tuned DESIGN.md starter (Web or Mobile), or adopt a style-direction
8
+ starter (Editorial print, Ledger, Warm consumer) that already carries a point
9
+ of view. Use when starting a new UI project with no existing design system,
10
+ when Step 0 (Own the system) of the ux-design method has nothing to reuse,
11
+ or when asked for a starter token set. These are REFERENCES only —
11
12
  pi-ux never bundles CSS.
12
13
  argument-hint: ""
13
14
  license: MIT
@@ -172,7 +173,6 @@ Map EVERY shadow to a named level — never invent blur/opacity per component:
172
173
  ```
173
174
 
174
175
  ### B2 — Mobile preset (touch-first, native / RN / mobile web)
175
-
176
176
  ```markdown
177
177
  ---
178
178
  name: Mobile Baseline
@@ -275,6 +275,130 @@ is nearer than the page — keep it that way.
275
275
  - iOS vs Android: follow the host platform — iOS HIG (SF symbols, larger corner radius, sheet) vs Material 3 (FAB, ripple, top app bar).
276
276
  ```
277
277
 
278
+ ## Option B+ — style-direction starters (a floor WITH a point of view)
279
+
280
+ B1/B2 are deliberately neutral — they pass the gates but carry no identity, and
281
+ a project that keeps them untouched ships the "correct but forgettable" page.
282
+ The three starters below pair the same gate-hardened token structure with a
283
+ **subject material and type voice**, so Step 1.5 starts from a direction
284
+ instead of a blank. Pick the one whose material matches the brief; then still
285
+ bend it (Step 1.5): swap the accent to the subject's world, adjust the mood
286
+ adjectives, and commit a signature element of your own. All pairs below were
287
+ verified against the APCA ladder (Lc ≥75 body · ≥45 large · ≥60 small-bold
288
+ badges).
289
+
290
+ ### S1 — Editorial print (journal, heritage brand, long-form, portfolio)
291
+
292
+ Material: the printed page — warm paper, ink, hairline rules. Near-square
293
+ corners; separation by rules and whitespace, elevation almost never. Display
294
+ **Newsreader** (500–600, tight leading) over **Source Sans 3** body.
295
+
296
+ ```yaml
297
+ name: Editorial Print
298
+ colors:
299
+ bg: "#F6F2E9" # warm paper
300
+ surface: "#EEE8DA" # shaded paper band
301
+ text: "#20241F" # ink
302
+ text-muted: "#565B50"
303
+ accent: "#7D2B25" # oxblood — links, rules, stamps
304
+ accent-hover: "#67241E"
305
+ border: "#DBD3C2" # hairline
306
+ danger: "#9B2C20"
307
+ typography:
308
+ body: { fontFamily: "Source Sans 3", fontSize: 1.0625rem, lineHeight: 1.65 }
309
+ h1: { fontFamily: Newsreader, fontSize: 3rem, fontWeight: 600, lineHeight: 1.1 }
310
+ h2: { fontFamily: Newsreader, fontSize: 2.1rem, fontWeight: 600 }
311
+ label: { fontFamily: "Source Sans 3", fontSize: 0.85rem, fontWeight: 600 }
312
+ rounded: { sm: 2px, md: 2px, lg: 3px } # print is square
313
+ components:
314
+ ruled-row: { borderTop: "1.5px solid {colors.text}", padding: "{spacing.md} 0" }
315
+ link: { color: "{colors.accent}", textDecorationThickness: "1px" }
316
+ button-quiet: { border: "1.5px solid {colors.text}", color: "{colors.text}", rounded: "{rounded.sm}" }
317
+ stamp: { color: "{colors.accent}", border: "2px solid {colors.accent}", transform: "rotate(-2deg)" }
318
+ ```
319
+
320
+ Do: rules over shadows (border hierarchy); generous margins around display
321
+ type; numerals and folios as design objects. Don't: shadows bigger than
322
+ `--elev-sm`; rounded-2xl softness; dark mode (this system IS paper).
323
+
324
+ ### S2 — Ledger (finance, invoicing, dense data, legal, ops dashboards)
325
+
326
+ Material: the accountant's desk — desk paper, cards, ruled columns, stamps.
327
+ Every numeral in the mono face with `font-variant-numeric: tabular-nums`.
328
+ Display **Spline Sans** + **Spline Sans Mono** for all data.
329
+
330
+ ```yaml
331
+ name: Ledger
332
+ colors:
333
+ bg: "#F3F1EA" # desk
334
+ surface: "#FCFBF7" # card
335
+ text: "#1C211E" # ink
336
+ text-muted: "#5B625D"
337
+ accent: "#1E6B50" # ledger green — buttons, positive
338
+ accent-text: "#17573F" # green as TEXT on desk/card (Lc 79-81)
339
+ accent-hover: "#155640"
340
+ danger: "#B0362A" # stamp text on #F4E3E0 (small-bold ≥600)
341
+ warning: "#8A6210" # stamp text on #F2EAD4 (small-bold ≥600)
342
+ border: "#E0DDD2"
343
+ rule: "#1C211E" # 1.5px structural rules — the branding
344
+ typography:
345
+ body: { fontFamily: "Spline Sans", fontSize: 0.9rem, lineHeight: 1.5 }
346
+ data: { fontFamily: "Spline Sans Mono", fontWeight: 500, fontVariantNumeric: tabular-nums }
347
+ h1: { fontFamily: "Spline Sans", fontSize: 1.5rem, fontWeight: 700, letterSpacing: "-0.015em" }
348
+ label: { fontFamily: "Spline Sans Mono", fontSize: 0.6875rem, fontWeight: 500, letterSpacing: "0.08em", textTransform: uppercase }
349
+ rounded: { sm: 3px, md: 6px, lg: 8px }
350
+ components:
351
+ panel: { backgroundColor: "{colors.surface}", border: "1.5px solid {colors.rule}", rounded: "{rounded.md}" }
352
+ kpi-value: { font: "{typography.data}", fontSize: 1.6875rem, fontWeight: 600 }
353
+ status-paid: { color: "{colors.accent-text}", backgroundColor: "#E2EEE7", fontWeight: 700, fontSize: "0.65rem" }
354
+ status-overdue: { color: "{colors.danger}", backgroundColor: "#F4E3E0", fontWeight: 700, fontSize: "0.65rem", transform: "rotate(-2deg)" }
355
+ table-head: { font: "{typography.label}", borderBottom: "1.5px solid {colors.rule}" }
356
+ ```
357
+
358
+ Do: structural 1.5px rules; status badges as stamps (soft bg + dark text);
359
+ KPI strip as one ruled panel, not four floating cards. Don't: pastel
360
+ dashboards with soft shadows everywhere; chart fills lighter than 3:1 against
361
+ the track; intercom-blue accents.
362
+
363
+ ### S3 — Warm consumer (food, home, family, habits, kitchen/bath apps)
364
+
365
+ Material: warm kitchen linen. Chunky friendly radii, one deep green spine,
366
+ freshness told in color (fresh/expiring/expired), oversized action. Display
367
+ **DM Sans** 700–800.
368
+
369
+ ```yaml
370
+ name: Warm Consumer
371
+ colors:
372
+ bg: "#FAF6EE" # linen
373
+ surface: "#FFFFFF" # card
374
+ text: "#27302A"
375
+ text-muted: "#5C6457" # passes on BOTH linen and white
376
+ accent: "#2F6B3C" # basil — FAB, primary actions
377
+ accent-pressed: "#245430"
378
+ accent-soft: "#E5F0E2"
379
+ danger: "#A93222" # expired badge text on #F8E3DD (bold)
380
+ warning: "#8A5D0B" # expiring badge text on #F7ECD4 (bold)
381
+ border: "#EAE4D6"
382
+ typography:
383
+ body: { fontFamily: "DM Sans", fontSize: 1rem, lineHeight: 1.45 }
384
+ h1: { fontFamily: "DM Sans", fontSize: 1.4375rem, fontWeight: 800, letterSpacing: "-0.02em" }
385
+ badge: { fontFamily: "DM Sans", fontSize: 0.65625rem, fontWeight: 700, letterSpacing: "0.03em" }
386
+ rounded: { sm: 12px, md: 16px, lg: 22px }
387
+ components:
388
+ list-row: { backgroundColor: "{colors.surface}", border: "1.5px solid {colors.border}", rounded: "{rounded.md}", padding: "12px 14px" }
389
+ category-spine: { position: absolute, left: 0, width: 5px, backgroundColor: "{category-color}" }
390
+ fab: { backgroundColor: "{colors.accent}", color: "#F2F7EE", rounded: 999px, padding: "16px 26px", fontWeight: 800 }
391
+ badge-soon: { color: "{colors.warning}", backgroundColor: "#F7ECD4" }
392
+ badge-expired: { color: "{colors.danger}", backgroundColor: "#F8E3DD" }
393
+ ```
394
+
395
+ Do: category color spines (5px, left edge); FAB as the page's one loud
396
+ element; header bands in the accent color. Don't: neon or candy gradients;
397
+ hover-only feedback (touch medium — use B2's tap rules); body below 16px.
398
+
399
+ Mobile adaptation: any S-starter keeps its palette/type and takes B2's
400
+ mechanics (44pt targets, `:active` states, safe areas, thumb zone).
401
+
278
402
  ## Option C — minimal `:root` token set (CSS-only, no DESIGN.md)
279
403
 
280
404
  For CSS-only projects that do not adopt DESIGN.md, drop this compact baseline
@@ -330,11 +454,13 @@ into the project's `:root`. It satisfies the token + elevation + state gates of
330
454
 
331
455
  1. Run **Step 0** of ux-design. If the project already has a DESIGN.md / tokens / elevation / states → use them.
332
456
  2. If not, pick **Option A** — reuse an installed system (shadcn/Material/Radix) when the stack matches, OR pull a named style (Claymorphism, Brutalism, …) from the design.md library. Both reuse-before-invent; both still must pass `ux_audit`.
333
- 3. Otherwise pick **Option B** — infer the medium (Web B1 vs Mobile B2) from the task, use the starter in-context as the implicit system, and keep generating. Only ASK if web-vs-mobile is unclear.
457
+ 3. Otherwise pick **Option B** — infer the medium (Web B1 vs Mobile B2) from the task, use the starter in-context as the implicit system, and keep generating. Only ASK if web-vs-mobile is unclear. When the subject's material clearly matches a style-direction starter (**S1 Editorial print / S2 Ledger / S3 Warm consumer**), prefer it over the neutral B-starter — it hands Step 1.5 a direction for free.
334
458
  4. Only for CSS-only projects that won't adopt DESIGN.md, use **Option C** (`:root` block).
335
459
  5. Generate screens with any text-only model (DeepSeek-v4 / GLM-5.2 / Kimi K3) INSIDE these constraints.
336
460
  6. Run `ux_audit` with the CSS + the most common text colour pairs before handoff.
337
461
 
462
+ **Floors, not identities:** whichever starter you pick, Step 1.5 of ux-design still bends it — accent toward the subject's world, mood adjectives committed, one signature element named. The finished page must not be recognisable as the stock starter.
463
+
338
464
  ## Audit-ready pairs
339
465
 
340
466
  ```jsonc
@@ -354,3 +480,4 @@ into the project's `:root`. It satisfies the token + elevation + state gates of
354
480
  - Not a CSS framework — reference only. Don't bundle it; copy what you need.
355
481
  - Not themeable at runtime — it's a baseline to extend, not a product.
356
482
  - Not opinionated about the accent — `accent` / `{colors.accent}` is the ONE knob you turn for brand.
483
+ - Not an identity — even the S-starters are floors. A finished page that looks exactly like its starter means Step 1.5 was skipped.
@@ -0,0 +1,45 @@
1
+ ---
2
+ name: ux-routing
3
+ description: >
4
+ Reference table for dividing UX work across models in the Pi ecosystem —
5
+ who defines the DESIGN.md scaffold, who generates variants, who renders and
6
+ inspects, what reviews. Consult when delegating design work to other models
7
+ (agy/Gemini, Claude, DeepSeek, Kimi) or when deciding whether the main model
8
+ should do a step itself. Not injected into every turn by design; the
9
+ always-true core rule lives in ux-design: the deterministic gate reviews, a
10
+ multimodal model looks at its own render, and taste lives in the direction
11
+ brief, not in the model choice.
12
+ ---
13
+
14
+ # Model routing for design work (who does what)
15
+
16
+ Split the work along each model's strength. **The inversion rule:** the
17
+ cheaper/weaker the model, the MORE you must externalise constraints. Taste
18
+ lives in the direction brief, not the weights.
19
+
20
+ **The deterministic-first principle:** the gate is mechanical (DESIGN.md lint +
21
+ `ux_audit`), not a vision-LLM call. Text-only models now lead frontend
22
+ (Kimi K3 is #1 on the Arena.ai Frontend Code Arena, ahead of Claude Fable 5) —
23
+ inside a locked system they produce non-slop. agy/Gemini/Claude is optional,
24
+ never the review gate.
25
+
26
+ | Step | Best tool/model | Why |
27
+ |---|---|---|
28
+ | **Define system** (DESIGN.md: tokens, elevation, type) | Reuse a preset (ux-presets) OR `agy_execute mode=plan pro-high` (Gemini) **once** | Preset is cheapest. Gemini = strongest visual reasoning for the one-time scaffold. |
29
+ | **Derive direction** (Step 1.5) | The generating model itself, after reading the subject material | Direction needs the subject brief, not a bigger model. |
30
+ | **Lint system** | `npx @google/design.md lint DESIGN.md` (shell-out) | Deterministic token-ref + contrast + structure validation. |
31
+ | **Per-screen brief** | Main Pi model: **GLM-5.2** | 1M ctx holds the whole DESIGN.md while scoping one screen |
32
+ | **Generate variants** | Main Pi model: **DeepSeek-v4**, **GLM-5.2**, or **Kimi K3** | Text-only models lead frontend inside a locked system; cheaper than vision calls. |
33
+ | **Normalise into system** | Main Pi model: **DeepSeek-v4** or **GLM-5.2** | Long context, token remapping, mechanical precision |
34
+ | **Render & inspect** | The generating model itself, when multimodal (GLM-5.3, Claude, Gemini) via `web_screenshot` | Eyes on your own output beat rules in a prompt — flash-tier models produce notably better UI when they see the rendered result (inline image blocks) |
35
+ | **Slop audit** | `ux_audit` tool (deterministic) + DESIGN.md lint | Contrast (APCA) + tokens + states + slop tells are computable, not judgement |
36
+ | **Optional polish** (never a gate) | `agy_execute mode=accept-edits sonnet` (Claude) or `opus` | Only if brand-fit is uncertain after the deterministic gate passes. NOT required. |
37
+
38
+ **Cross-family rule:** Gemini/Claude produce → deterministic gate reviews. Don't
39
+ spend vision-model quota on what `ux_audit` computes for free. agy review is a
40
+ fallback for aesthetic uncertainty, never the gate.
41
+
42
+ DeepSeek/GLM/Kimi K3 are safe for design **only inside a fully-specified
43
+ system** (DESIGN.md or preset + direction brief). If no system exists yet,
44
+ create it once (preset + Step 1.5 direction, or agy), then text-only models
45
+ are sufficient for every generation thereafter.