From 4da655908267a3e13c746960806d5cc156d8d9ce Mon Sep 17 00:00:00 2001 From: pallaoro Date: Thu, 3 Sep 2026 19:30:18 +0200 Subject: [PATCH 1/2] =?UTF-8?q?feat(skills):=20verify-responsive=20?= =?UTF-8?q?=E2=80=94=20render=20the=20UI=20at=20real=20viewport=20widths?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A media query only proves itself when something evaluates it at that width. When a browser-automation window resize doesn't change the viewport the page actually sees, the screenshot comes back desktop-width, the desktop branch renders, and the mobile layout is never exercised — a quiet false pass. The skill is one throwaway harness: fixed-width iframes pointed at the running app, written into the app's own static dir so the parent stays same-origin and can script into the frame, drive it, and assert the width from inside rather than eyeballing a screenshot. Verified in Chrome while writing it: a 390-wide frame reports innerWidth 390 and matches (max-width: 640px) while a 1280 frame does not, and each renders its own branch of the same page; contentDocument is null from a cross-origin harness, which is why placement in the static dir is load-bearing; and inside the frame pointer:fine and hover:hover stay true with maxTouchPoints 0, so the skill states plainly that it narrows the viewport but does not emulate a device. install.js hardcoded the shipped skill dirs at four sites, so lift them to one SKILLS array — adding the third skill now costs one entry instead of four. --- .claude-plugin/marketplace.json | 4 +- .claude-plugin/plugin.json | 2 +- README.md | 10 ++++- bin/install.js | 13 +++--- package.json | 2 +- skills/README.md | 14 +++++-- skills/verify-responsive/SKILL.md | 66 +++++++++++++++++++++++++++++++ 7 files changed, 95 insertions(+), 16 deletions(-) create mode 100644 skills/verify-responsive/SKILL.md diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 8867409..81468cc 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -1,7 +1,7 @@ { "$schema": "https://anthropic.com/claude-code/marketplace.schema.json", "name": "greybeard", - "description": "Greybeard \u2014 Karpathy-inspired coding-agent guidelines, the skillify / check-resolvable meta-skills, and the /pressure-test, /sidenote and /visualize commands.", + "description": "Greybeard \u2014 Karpathy-inspired coding-agent guidelines, the skillify / check-resolvable meta-skills, the verify-responsive practice skill, and the /pressure-test, /sidenote and /visualize commands.", "owner": { "name": "Clawnify", "url": "https://github.com/clawnify" @@ -9,7 +9,7 @@ "plugins": [ { "name": "greybeard", - "description": "skillify + check-resolvable skills and the /pressure-test, /sidenote and /visualize commands. Pair with the npx installer for the always-on guidelines.", + "description": "skillify + check-resolvable + verify-responsive skills and the /pressure-test, /sidenote and /visualize commands. Pair with the npx installer for the always-on guidelines.", "source": "./", "category": "productivity" } diff --git a/.claude-plugin/plugin.json b/.claude-plugin/plugin.json index 46edb40..ddf4a6e 100644 --- a/.claude-plugin/plugin.json +++ b/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "greybeard", - "description": "Karpathy-inspired coding-agent skills: skillify (capture repeated work), check-resolvable (keep skills DRY + MECE), and the /pressure-test, /sidenote and /visualize commands.", + "description": "Karpathy-inspired coding-agent skills: skillify (capture repeated work), check-resolvable (keep skills DRY + MECE), verify-responsive (render a UI at real viewport widths), and the /pressure-test, /sidenote and /visualize commands.", "author": { "name": "Clawnify", "url": "https://github.com/clawnify" diff --git a/README.md b/README.md index 6c53aaa..429e92a 100644 --- a/README.md +++ b/README.md @@ -20,7 +20,7 @@ > Built and maintained by [Clawnify](https://clawnify.com) — a managed platform that provisions AI agents with WhatsApp / Telegram / Email and browser capabilities for non-technical users. -A single `CLAUDE.md` file to improve AI coding-agent behavior, derived from [Andrej Karpathy's observations](https://x.com/karpathy/status/2015883857489522876) on LLM coding pitfalls, plus three sections we added for the AI-assisted-coding era. Ships with two runnable [meta-skills](#skills-skillify-dry--mece-resolvers), its three commands — [`/pressure-test`](#the-pressure-test-command), the decision test, [`/sidenote`](#the-sidenote-command), park-a-thought, and [`/visualize`](#the-visualize-command), draw-the-real-shape — and a [one-command installer](#install) that fans it all out to every AI coding agent you use. +A single `CLAUDE.md` file to improve AI coding-agent behavior, derived from [Andrej Karpathy's observations](https://x.com/karpathy/status/2015883857489522876) on LLM coding pitfalls, plus three sections we added for the AI-assisted-coding era. Ships with three runnable [skills](#skills-skillify-dry--mece-resolvers), its three commands — [`/pressure-test`](#the-pressure-test-command), the decision test, [`/sidenote`](#the-sidenote-command), park-a-thought, and [`/visualize`](#the-visualize-command), draw-the-real-shape — and a [one-command installer](#install) that fans it all out to every AI coding agent you use. ## The Problems @@ -184,6 +184,12 @@ The principle above ships as two runnable meta-skills in [`skills/`](./skills) | [`skillify`](./skills/skillify/SKILL.md) | Capture what you just did as a reusable, parameterized skill, then register it in the resolver. | | [`check-resolvable`](./skills/check-resolvable/SKILL.md) | Audit the whole library so it stays DRY and MECE — no duplicates, no overlaps, no gaps. | +And the **practice skills** those two produce — procedures captured with `skillify`, kept in the same index: + +| Skill | What it does | +|-------|--------------| +| [`verify-responsive`](./skills/verify-responsive/SKILL.md) | Render the real page at real viewport widths in fixed-width iframes, then drive and assert it from the parent. | + They're standard [Claude Code Agent Skills](https://code.claude.com/docs/en/skills) (portable to Cursor, Codex, OpenClaw). Install and usage: [`skills/README.md`](./skills/README.md). ## The three commands @@ -240,7 +246,7 @@ Then `/visualize `, or a bare `/visualize` to draw whatever's curr npx @clawnify/greybeard ``` -It detects the AI coding agents you actually use and installs the right files for each: the seven guidelines into the rule file each one reads (`CLAUDE.md`, `AGENTS.md`, `GEMINI.md`, `.cursor/rules/`, `.windsurf/rules/`, `.clinerules/`, `.github/copilot-instructions.md`), plus the `skillify` / `check-resolvable` skills for Claude Code and OpenClaw, and the `/pressure-test`, `/sidenote` and `/visualize` commands for Claude Code. Shared files are edited between markers, so re-running is a safe no-op and your own content is preserved. +It detects the AI coding agents you actually use and installs the right files for each: the seven guidelines into the rule file each one reads (`CLAUDE.md`, `AGENTS.md`, `GEMINI.md`, `.cursor/rules/`, `.windsurf/rules/`, `.clinerules/`, `.github/copilot-instructions.md`), plus the `skillify` / `check-resolvable` / `verify-responsive` skills for Claude Code and OpenClaw, and the `/pressure-test`, `/sidenote` and `/visualize` commands for Claude Code. Shared files are edited between markers, so re-running is a safe no-op and your own content is preserved. ```bash npx @clawnify/greybeard --list # show detected agents diff --git a/bin/install.js b/bin/install.js index bd43bb0..dbf876c 100644 --- a/bin/install.js +++ b/bin/install.js @@ -138,13 +138,14 @@ function rel(p) { return p.startsWith(HOME) ? '~' + p.slice(HOME.length) : path. // The canonical principles payload for fenced installs = the generated AGENTS.md. const PRINCIPLES = fs.readFileSync(path.join(PKG, 'AGENTS.md'), 'utf8'); +// Skill directories shipped to every provider that reads skills. Add a new one here only. +const SKILLS = ['skillify', 'check-resolvable', 'verify-responsive']; // ── Per-provider install / uninstall ──────────────────────────────────────── function installClaude(dir, un) { if (un) { removeFenceFrom(path.join(dir, 'CLAUDE.md')); - removePath(path.join(dir, 'skills', 'skillify')); - removePath(path.join(dir, 'skills', 'check-resolvable')); + for (const s of SKILLS) removePath(path.join(dir, 'skills', s)); removePath(path.join(dir, 'commands', 'scalable.md')); // legacy name, pre-rename removePath(path.join(dir, 'commands', 'pressure-test.md')); removePath(path.join(dir, 'commands', 'sidenote.md')); @@ -152,8 +153,7 @@ function installClaude(dir, un) { return; } fenceInto(path.join(dir, 'CLAUDE.md'), PRINCIPLES); - copyDir(path.join(PKG, 'skills', 'skillify'), path.join(dir, 'skills', 'skillify')); - copyDir(path.join(PKG, 'skills', 'check-resolvable'), path.join(dir, 'skills', 'check-resolvable')); + for (const s of SKILLS) copyDir(path.join(PKG, 'skills', s), path.join(dir, 'skills', s)); removePath(path.join(dir, 'commands', 'scalable.md')); // clean up the pre-rename command from prior installs writeFile(path.join(dir, 'commands', 'pressure-test.md'), fs.readFileSync(path.join(PKG, 'commands', 'pressure-test.md'), 'utf8')); writeFile(path.join(dir, 'commands', 'sidenote.md'), fs.readFileSync(path.join(PKG, 'commands', 'sidenote.md'), 'utf8')); @@ -164,9 +164,8 @@ function applyProvider(p, un) { if (p.kind === 'claude') return installClaude(expand('~/.claude'), un); if (p.kind === 'openclaw') { const base = expand('~/.openclaw/workspace/skills'); - if (un) { removePath(path.join(base, 'skillify')); removePath(path.join(base, 'check-resolvable')); return; } - copyDir(path.join(PKG, 'skills', 'skillify'), path.join(base, 'skillify')); - copyDir(path.join(PKG, 'skills', 'check-resolvable'), path.join(base, 'check-resolvable')); + if (un) { for (const s of SKILLS) removePath(path.join(base, s)); return; } + for (const s of SKILLS) copyDir(path.join(PKG, 'skills', s), path.join(base, s)); return; } if (p.kind === 'file') { diff --git a/package.json b/package.json index 6bcdfed..69cfc4a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@clawnify/greybeard", "version": "0.1.0", - "description": "Greybeard \u2014 Karpathy-inspired guardrails that make your AI coding agent behave like a principled senior engineer. Guidelines + skillify / check-resolvable skills + the /pressure-test, /sidenote and /visualize commands, installable across every AI coding agent.", + "description": "Greybeard \u2014 Karpathy-inspired guardrails that make your AI coding agent behave like a principled senior engineer. Guidelines + the skillify / check-resolvable / verify-responsive skills + the /pressure-test, /sidenote and /visualize commands, installable across every AI coding agent.", "license": "MIT", "homepage": "https://github.com/clawnify/greybeard", "repository": { diff --git a/skills/README.md b/skills/README.md index 4d3c4f2..e478c39 100644 --- a/skills/README.md +++ b/skills/README.md @@ -1,12 +1,20 @@ # Skills: Skillify, DRY & MECE Resolvers -Two meta-skills that make a coding agent *compound* — turning repeated work into reusable skills, and keeping the skill library clean as it grows. Derived from the YC conversation with Pete Koomen & Andrej Karpathy on how AI-native organizations build a "shared organizational brain." +Two **meta-skills** that make a coding agent *compound* — turning repeated work into reusable skills, and keeping the skill library clean as it grows — plus the **practice skills** they produce. The meta-skills are derived from the YC conversation with Pete Koomen & Andrej Karpathy on how AI-native organizations build a "shared organizational brain"; the practice skills are what came out of running `skillify` on our own work. | Skill | What it does | |-------|--------------| | [`skillify`](./skillify/SKILL.md) | Capture what you just did as a reusable, parameterized skill, then register it in the resolver. | | [`check-resolvable`](./check-resolvable/SKILL.md) | Audit the whole library so it stays **DRY** (no duplicates) and **MECE** (no overlaps, no gaps). | +## Practice skills + +Procedures captured with `skillify`. Each one exists because reasoning about the answer kept being wrong and rendering it was cheap. + +| Skill | What it does | +|-------|--------------| +| [`verify-responsive`](./verify-responsive/SKILL.md) | Render the real page at real viewport widths in fixed-width iframes, then drive and assert it from the parent. | + ## The decision test Behind every choice these skills make — *should I skillify this? generalize how far? merge or keep separate?* — is one question: @@ -34,14 +42,14 @@ These are standard [Claude Code Agent Skills](https://code.claude.com/docs/en/sk ```bash mkdir -p ~/.claude/skills -cp -R skillify check-resolvable ~/.claude/skills/ +cp -R skillify check-resolvable verify-responsive ~/.claude/skills/ ``` **One project only:** ```bash mkdir -p .claude/skills -cp -R skillify check-resolvable .claude/skills/ +cp -R skillify check-resolvable verify-responsive .claude/skills/ ``` Then add a one-line entry for each to your project's resolver (`AGENTS.md` / `CLAUDE.md`) so the agent — and your teammates — can find them. After that, just say *"skillify this"* when you finish something worth keeping. diff --git a/skills/verify-responsive/SKILL.md b/skills/verify-responsive/SKILL.md new file mode 100644 index 0000000..c9e6cf3 --- /dev/null +++ b/skills/verify-responsive/SKILL.md @@ -0,0 +1,66 @@ +--- +name: verify-responsive +description: Verify a responsive UI by rendering the real page at real viewport widths in fixed-width iframes, then driving and asserting it from the parent. Use when checking a mobile or tablet layout, a breakpoint, or small-screen-only behavior — especially when a window-resize tool won't give you a mobile viewport. +--- + +# Verify Responsive + +"The CSS looks right" is not "it renders right at 390px." A media query only proves itself when something evaluates it at that width. This is §4 applied to UI — *define success criteria, loop until verified* — where the criterion is a rendered page at a real width, not a stylesheet that reads correctly. + +The tool is one throwaway HTML file: fixed-width ` +
1280 × 800
+
+ ``` + + Open `/_viewports.html` and screenshot it. + +4. **Drive it and assert from the parent.** Same-origin, so the frame is fully scriptable — you can reach past what a screenshot shows: + + ```js + const w = m.contentWindow, doc = m.contentDocument; + w.innerWidth; // 390 + w.matchMedia('(max-width: 640px)').matches; // true → the mobile arm really is live + doc.querySelector('[data-testid="menu"]').click(); + doc.querySelector('.drawer').className; // read the result back + w.localStorage.getItem('seen'); // and any state it wrote + ``` + + Assert the width from *inside* the frame before trusting a screenshot. "Looks narrow to me" is how you verify nothing. + +5. **Delete the harness before committing.** Anything in the static dir ships. `rm _viewports.html`, then confirm with `git status` — don't leave it for review to catch. + +## What this does not give you + +The iframe narrows the **viewport**. It does not emulate a device. Measured inside a 390px frame on a laptop: `pointer: fine` and `hover: hover` are both still true, `navigator.maxTouchPoints` is `0`, and `devicePixelRatio` is the host's. + +So a layout that branches on width is covered honestly. One that branches on `@media (hover: hover)`, sniffs the user agent, or swaps assets on DPR will render its **desktop** arm at 390px and mislead you with a screenshot that looks right. There is also no browser chrome, so `100dvh`-vs-`100vh` differences never show up. When the behavior under test depends on any of those, use real device emulation or a real device — this harness will lie to you. + +## Related + +- [`/visualize`](../../commands/visualize.md) draws the *structure* of the code. This renders the *running UI*. Use this one when the question is "what does it actually look like at this width." From 4cb4b783e0d3a9e7c91d7fa8db0e443527c9bc8e Mon Sep 17 00:00:00 2001 From: pallaoro Date: Thu, 3 Sep 2026 19:44:39 +0200 Subject: [PATCH 2/2] docs(verify-responsive): make the harness the default, not a resize fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The description and intro framed the skill as what you reach for when a window-resize tool fails, which undersells it and makes a worse method the first choice. Plain window resize is strictly dominated: it buys a narrower viewport, which is exactly what the iframe gives you, while the iframe adds both breakpoints in one screenshot, a frame the parent can script and assert from inside, and no dependency on the resize having taken effect. Resize being unreliable is now the third supporting reason rather than the trigger. The method that genuinely beats the harness is device emulation — touch, DPR, UA — which the limitations section already covers and now links to. --- skills/verify-responsive/SKILL.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/skills/verify-responsive/SKILL.md b/skills/verify-responsive/SKILL.md index c9e6cf3..986e5f5 100644 --- a/skills/verify-responsive/SKILL.md +++ b/skills/verify-responsive/SKILL.md @@ -1,6 +1,6 @@ --- name: verify-responsive -description: Verify a responsive UI by rendering the real page at real viewport widths in fixed-width iframes, then driving and asserting it from the parent. Use when checking a mobile or tablet layout, a breakpoint, or small-screen-only behavior — especially when a window-resize tool won't give you a mobile viewport. +description: The default way to check a responsive UI — render the real page at real viewport widths in fixed-width iframes, then drive and assert it from the parent. Use whenever you need to see or verify a layout at a given width: a mobile or tablet breakpoint, or small-screen-only behavior. Reach for it first, ahead of resizing a window. --- # Verify Responsive @@ -9,7 +9,15 @@ description: Verify a responsive UI by rendering the real page at real viewport The tool is one throwaway HTML file: fixed-width `