Skip to content

feat(skills): verify-responsive — render the UI at real viewport widths - #38

Merged
pallaoro merged 2 commits into
mainfrom
verify-responsive-skill
Sep 4, 2026
Merged

pallaoro merged 2 commits into
mainfrom
verify-responsive-skill

Conversation

@pallaoro

@pallaoro pallaoro commented Sep 3, 2026 •

Copy link
Copy Markdown
Member

Adds a third runnable skill: verify-responsive — the default way to look at a UI at a given width.

Why

A media query only proves itself when something evaluates it at that width. The skill is a throwaway harness of fixed-width <iframe>s pointed at the running app. An iframe is a nested browsing context, so its viewport is the iframe box.

Why not just resize the window? Resizing buys exactly one thing — a narrower viewport — which is the same thing the iframe gives you. The iframe adds three more:

  • both breakpoints in one screenshot, side by side, rather than one width at a time;
  • a frame the parent can script: click into it, read state back, assert innerWidth / matchMedia(...) from inside — a resized window can only be photographed;
  • no dependency on the resize having worked — automation resize calls can quietly fail to change the layout viewport, so the screenshot returns desktop-width, the desktop branch renders, and you sign off on a mobile layout you never rendered.

The method that genuinely beats this harness is device emulation, not window resizing.

The load-bearing rule

The harness goes in the app’s own static dir, not on a second server. Same-origin is what makes the frame scriptable.

Verified in Chrome while writing it

Claim Result
390 frame gets a 390 viewport innerWidth=390, (max-width: 640px) matches; the 1280 frame does not — each rendered its own branch of the same page
Same-origin harness is scriptable clicked inside the frame, read back DOM state and localStorage
Cross-origin harness is not contentDocument is null (not a thrown SecurityError)
It is not device emulation inside the 390 frame pointer:fine and hover:hover stay true, maxTouchPoints=0, dpr=1

That last row is documented as an explicit limitation: a layout branching on hover/pointer, UA sniffing, or DPR will render its desktop arm at 390px and mislead you. Width-based layout is covered honestly; anything else needs real device emulation.

Registration

Reachable from every index: skills/README.md, the root README table, and the installer. bin/install.js hardcoded the shipped skill dirs at four sites — lifted to one SKILLS array, so this addition costs one entry instead of four (net −1 line).

Checks

  • node scripts/build-rules.js --check → no drift (CLAUDE.md untouched)
  • node --check bin/install.js → OK
  • Installer run end to end against a throwaway HOME for --only claude --only openclaw: the skill lands in ~/.claude/skills/ and ~/.openclaw/workspace/skills/, and --uninstall removes all three cleanly

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.
…allback

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.
@pallaoro
pallaoro merged commit 4cb4b78 into main Sep 4, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant