| name | ui-proposal |
| layer | stage |
| description | Propose a UI change for human sign-off before building it. The default path deploys a rough live staging preview (with NUXT_PUBLIC_CROUTON_REVIEW=true) so the reviewer pins comments on the real running page. Use --static for the offline HTML/CSS mockup fallback (no deploy available, or speed over fidelity). Invoke for any task that adds/changes a .vue component, a layout, a page, or a theme. |
| allowed-tools | Read, Write, Edit, Bash, Glob, Grep |
UI Proposal โ sign off on a live preview before you build it
The UI sign-off loop (epic #307) requires human approval before you build/finalise a
visual surface. The default path deploys a rough real build to a staging preview with
the review overlay on, so the reviewer clicks the running page rather than reading a static
PNG. A --static fallback (offline HTML/CSS mockup + PNG) is available when a live deploy
isn't practical.
Either way the outcome is identical: a draft PR is posted, the reviewer gives feedback,
you iterate, and an approve/lgtm comment unblocks the build.
When to use
- A task's diff adds or changes a visual surface: a
.vue component, a layout, a page,
a theme (crouton-themes, a ui: block in app.config.ts), or app CSS/theme tokens.
- The user asks to "mock up", "propose", "show what it'd look like", or wants design sign-off.
- Skip for pure
<script>/composables/types, server/**, config, tests, and docs โ no
visible result, no gate.
Which path to choose
| Default (live preview) | Fallback (--static) |
|---|
| When | App can be deployed to staging | Deploy is unavailable, broken, or would take too long |
| Fidelity | Real Nuxt UI styling, real data, real responsive behaviour | Offline HTML/CSS approximation |
| Reviewer UX | Click the running page, pin a comment | Read a PNG, comment inline on the .md diff |
| Approval signal | Reply approve / lgtm โ same loop | Reply approve / lgtm โ same loop |
Default path โ live preview with review overlay
The loop (capture tooling provided by @fyit/crouton-devtools):
- Build a rough real version of the surface. You don't need a polished feature โ a
scaffold that renders the changed component is enough. Commit and push the branch.
- Deploy to staging with the review flag using the
/poc-deploy skill (for POC
apps) or pnpm cf:staging (for apps/). The flag must be on:
NUXT_PUBLIC_CROUTON_REVIEW=true pnpm cf:staging
Do not set this flag on production deploys (cf:deploy) โ it is staging-only.
- Post the preview URL on the draft PR. Example comment:
๐ Live preview ready: https://<name>.pmcp.dev
Reviewer: open the link, click any element, and type your change in the overlay.
Each pin posts a ๐ฏ Preview feedback comment here naming the source file.
Reply `lgtm` or `approve` when satisfied.
- Apply
status:blocked, @mention @pmcp, and stop. Do not build further until
approved.
- On each
๐ฏ Preview feedback comment: read the named source file, make the change,
commit, redeploy. Reply to the comment when done.
- On
approve / lgtm reply: remove status:blocked, drop a short note on the PR,
and resume building/generating (step 6 of task-worker).
Env contract (already wired by WS2 of epic #590)
The staging deploy script in pocs/<name>/package.json carries the flag; the Worker secret
(NUXT_CROUTON_REVIEW_GITHUB_TOKEN) + vars (NUXT_CROUTON_REVIEW_REPOSITORY,
NUXT_CROUTON_REVIEW_PR) are set by scripts/inject-review-env.mjs during cf:staging.
You don't need to set these by hand โ use the /poc-deploy skill or the app's cf:staging
script and they're handled automatically.
Fallback path โ static mockup (--static)
Use when a live deploy is not available (e.g. packages-only change with no runnable app, or
the deploy pipeline is broken and speed matters more than fidelity). Pass --static when
invoking this skill, or when the task-worker agent determines staging isn't reachable.
What it produces
| Artifact | Path | Committed? |
|---|
| Mockup source | writeups/ui-proposals/<slug>.html | yes (editable source of truth) |
| "What changes" list | writeups/ui-proposals/<slug>.md | yes (inline-commentable diff surface) |
| Rendered image | writeups/ui-proposals/<slug>.png | yes โ committed so it can be embedded inline in the sticky comment (#613) |
<slug> = kebab of the surface, e.g. mobile-collection-viewer.
Why the PNG is committed (not in screenshots/). A GitHub comment can only show an
image inline if it has a fetchable URL. The PNG is committed to writeups/ui-proposals/
(gitignore-excepted, like ticket-diagram's renders) and referenced by its
raw.githubusercontent.com/<repo>/<branch>/โฆpng URL, so it renders as an image on web
and mobile. Linking the .html instead made the proposal "open as code" on mobile โ
the #569 papercut this fixes.
Step 1 โ Understand the surface
Read the component(s) you're about to change. The mockup must reflect the actual current
UI for "before" and your intended design for "after". Use real labels/data where you
have them.
Step 2 โ Build the mockup from the template
Copy template.html (next to this skill) to writeups/ui-proposals/<slug>.html and fill
the slots:
- Frame: phone frame for mobile surfaces, desktop frame for wide ones (both in the template).
- Before: mirror today's UI honestly (including rough edges the change fixes).
- After: your proposed design.
- "What changes" list: 3โ5 plain-language bullets.
Rules (keep it portable):
- No JavaScript, no external/CDN assets. Inline SVG icons only (template ships a set).
Non-negotiable โ the artifact must render offline.
- Match the app's look: dark Nuxt-UI palette, emerald primary, template variables.
- One file, self-contained.
example.html (next to this skill) is a complete worked reference.
Step 3 โ Render to PNG
node .claude/skills/ui-proposal/render.mjs writeups/ui-proposals/<slug>.html writeups/ui-proposals/<slug>.png
Uses the repo's Playwright (@playwright/test) headless Chromium โ no network, 2ร for crisp
image. Render into writeups/ui-proposals/ (not screenshots/) so the PNG is committed and
can be embedded inline (step 4).
Step 4 โ Hand off (review happens on the DIFF)
Commit a text artifact so feedback can be inline. Alongside the .html, write the
writeups/ui-proposals/<slug>.md โ the "what changes" list, one item per line. Committed,
it lands in "Files changed" so the reviewer can inline-comment a specific change.
HARD RULE โ the PNG MUST render inside the comment. The reviewer sees the design by
looking at the comment, not by opening a file. A path reference (writeups/โฆ/<slug>.png) or
a link to the .html is a failed hand-off โ it "opens as code" on mobile (#569/#613) and
makes the reviewer go hunting. Always embed a Markdown image by its raw URL, and only
after you've confirmed that URL serves the image.
- Commit the
.html + .md + .png (via /commit, scope docs).
- Push the branch so GitHub can serve the PNG. The raw URL 404s until the commit is on
the remote โ pushing is not optional, it's what makes the image appear. Push the current
branch (set upstream if new:
git push -u origin <branch>). In an interactive session with
no PR, this is still required โ the issue comment needs the same hosted file.
- Verify the raw URL resolves before you post โ a 404 means a silent path-link fallback,
the exact failure this rule exists to prevent:
url="https://raw.githubusercontent.com/FriendlyInternet/nuxt-crouton/<branch>/writeups/ui-proposals/<slug>.png"
curl -s -o /dev/null -w '%{http_code}' "$url"
- Post the sticky comment with the PNG embedded inline โ on the PR if one exists,
otherwise on the tracking issue (interactive / no-PR runs). Same body either way:
<!-- ui-proposal:<slug> -->
### ๐จ UI proposal โ <slug>

Review the **"what changes"** list (`writeups/ui-proposals/<slug>.md`) and comment any change.
Reply `lgtm` / `approve` when satisfied.
Use the actual head branch in the URL (e.g. receiptDesign, claude/issue-<NN>-<slug>);
the image re-renders whenever the committed file changes, so editing in place (step 5) works.
Confirm it rendered, not just that the URL is 200 (step 3). Read the posted body back:
if the image shows as code not an image (the #569/#613 symptom), the GitHub-MCP writer
mangled the Markdown URL (backtick-wrapped it / dropped the src) โ a break curl-ing the
URL can't detect. Re-post with an HTML <img src="<raw-url>" alt="โฆ" width="380"> tag (a URL
in an attribute can't be auto-wrapped) and re-read to confirm. (#1615)
- Steer feedback to the
.md โ inline comments in the diff (PR) or on the committed file.
- Apply
status:blocked, @mention @pmcp, and stop.
Step 5 โ Revision loop (both paths share this)
On each change request: revise the proposal (mockup files for --static, source file for
live-preview), re-render / redeploy, edit the sticky comment in place (never post a new
one), and reply to/resolve each inline thread you addressed. Commit and push.
On approve / lgtm reply: remove status:blocked, note "approved โ building" on the
sticky comment, and resume.
Conventions
- Before and after side-by-side for a change; after-only for net-new UI (no "before" exists).
- Keep the proposal focused on the surface under discussion โ don't redraw the whole app.
- Re-render / redeploy after every revision so the proposal never drifts.
- One sticky comment per proposal. Never post a new comment per revision โ edit in place.