/ui-preview
Capture headless-Chrome screenshots of the tingly-box frontend (running locally in mock mode) so frontend changes can be visually verified in environments without a real browser. Use when the user asks to "preview", "screenshot", "see the page", "show me the UI", "verify
Claude Code
$ npx -y skills add tingly-dev/tingly-box --skill ui-preview --agent claude-code
Codex
$ npx skills add tingly-dev/tingly-box --skill ui-preview -a codex -y
Cursor
$ npx skills add tingly-dev/tingly-box --skill ui-preview -a cursor -y
Gemini CLI
$ npx skills add tingly-dev/tingly-box --skill ui-preview -a gemini-cli -y
GitHub Copilot
$ npx skills add tingly-dev/tingly-box --skill ui-preview -a github-copilot -y
OpenCode
$ npx skills add tingly-dev/tingly-box --skill ui-preview -a opencode -y
Windsurf
$ npx skills add tingly-dev/tingly-box --skill ui-preview -a windsurf -y
SKILL.md
ui-preview.SKILL.mdname: ui-preview
description: Capture headless-Chrome screenshots of the tingly-box frontend (running locally in mock mode) so frontend changes can be visually verified in environments without a real browser. Use when the user asks to "preview", "screenshot", "see the page", "show me the UI", "verify visually", or when frontend layout / component / styling changes need a sanity-check before review. Works in restricted/cloud sandboxes where Playwright's normal Chromium install is blocked.
Headless UI preview (Playwright + Chrome for Testing)
Designed for the remote-execution container where:
- `cdn.playwright.dev` / `dl.google.com` are blocked — use Chrome for Testing via `storage.googleapis.com`.
- Ubuntu's `chromium-browser` is a snap stub and won't run.
- MCP servers can't be registered mid-session — use Playwright's Node API directly.
Setup (run once per fresh container)
> ⚠️ **This project uses pnpm** (`frontend/pnpm-lock.yaml` is the source of truth). > Do **NOT** run `npm i` here — npm ignores the pnpm lockfile and re-resolves > transitive deps, which pulls **broken** versions (e.g. `es-toolkit@1.47.0` vs the > pinned `1.46.1`) that crash the whole SPA at load. See *Troubleshooting* below.
`playwright` is already a committed `devDependency` in `frontend/package.json`, so a plain install provides it — no ad-hoc add/revert needed. `@emotion/react` / `@emotion/styled` are likewise already declared.
# 1. Install deps from the lockfile (also fixes a node_modules previously
# polluted by npm — wipe it first with `rm -rf node_modules` if you suspect that).
cd frontend
pnpm install --frozen-lockfile
# 2. Download Chrome for Testing (Playwright's own Chromium download is blocked)
mkdir -p /tmp/chrome && cd /tmp/chrome
curl -fsSL -o chrome.zip \
"https://storage.googleapis.com/chrome-for-testing-public/148.0.7778.96/linux64/chrome-linux64.zip"
unzip -q chrome.zip # → /tmp/chrome/chrome-linux64/chrome
**Do NOT** modify `frontend/package.json` / `frontend/pnpm-lock.yaml` for tooling anymore — everything needed is already committed. If a previous session left them dirty (or npm polluted the tree), reset with:
cd <repo> && git checkout -- frontend/package.json frontend/pnpm-lock.yaml
pnpm -C frontend install --frozen-lockfile
Dev server
cd frontend
USE_MOCK=true node_modules/.bin/vite --mode mock --port 3000 &
until curl -fs http://localhost:3000 >/dev/null; do sleep 1; done
`USE_MOCK=true` must be a shell env var (read by `vite.config.ts` before `.env.mock`).
Auth seeding
Every script must seed `localStorage.user_auth_token` via `addInitScript` or the app redirects to the login screen:
await page.addInitScript(() => {
localStorage.setItem('user_auth_token', 'mock-token-for-screenshots');
});First-run onboarding guides
Mock mode defaults these **off** (`src/mocks/mockConfig.ts`), so a fresh browser context no longer pops the "Direct Routing Guide" dialog and steals focus/clicks — no Escape-key dismissal needed before interacting with the page. If a script specifically needs to screenshot or assert the first-run guide itself, force it on per-navigation with a query param (no code change, no shared state between runs):
await page.goto('http://localhost:3000/agent/openai?mockOnboarding=on', { waitUntil: 'networkidle' });`?mockOnboarding=off` is the (redundant) explicit default, useful when a test wants to assert the off state regardless of what a prior run left in localStorage.
Data profile: populated vs. newcomer
The onboarding flag above only controls dialogs. What data the mock backend "has" is a separate switch (`src/mocks/mockConfig.ts`):
await page.goto('http://localhost:3000/credentials?mockData=newcomer', { waitUntil: 'networkidle' });- `?mockData=populated` (default): the full demo data set.
- `?mockData=newcomer`: a fresh install. It has no credentials, quota, usage,
request logs or Claude Code profiles, and the built-in rules have no services. Use it to check that each page lands sensibly before the user has added anything. It also turns the first-run guides on, unless `?mockOnboarding=off` is given.
The profile is kept in `sessionStorage` for the tab, so in-app navigation stays in the chosen world. Pass `?mockData=populated` to switch back, or use a fresh browser context.
Scripts
All scripts live here and are run from `frontend/`. They use `createRequire(cwd)` to resolve `playwright` from `frontend/node_modules` regardless of the script's location:
import { createRequire } from 'module';
const { chromium } = createRequire('file://' + process.cwd() + '/')('playwright');| Script | Mode | Purpose | |--------|------|---------| | [`screenshot.mjs`](screenshot.mjs) | mock | **Ad-hoc template** — copy here and customise; do NOT commit | | [`docs-screenshots.mjs`](docs-screenshots.mjs) | mock | All `docs/images/` product screenshots (`<group>-<index>-<name>.png`, sidebar order) + theme previews; GIF frames listed in `docs/images/gif-frames.txt` | | [`regression-credentials.mjs`](regression-credentials.mjs) | mock | Assertion-based regression for `/credentials` Add API Key flow | | [`scenario-routing-graph.mjs`](scenario-routing-graph.mjs) | real backend | Codex routing graph screenshots (requires running Go server) |
Each script is self-documenting — see its file header for usage, outputs, and known issues.
Shrinking screenshots (optional)
Screenshots are captured with Playwright's plain `page.screenshot()` — the skill deliberately does **not** compress images internally, because optimizers depend on host tooling that is not guaranteed in the run environment. If a PNG is large and you want to trim it before committing, run any optimizer you have available on the captured file afterwards. None of these are required:
- `pngquant --quality 80-95 --strip --force --out foo.png foo.png` (lossy, smallest PNGs)
- `oxipng -o 4 --strip saf
Read more
name: ui-preview description: Capture headless-Chrome screenshots of the tingly-box frontend (running locally in mock mode) so frontend changes can be visually verified in environments without a real browser. Use when the user asks to "preview", "screenshot", "see the page", "show me the UI", "verify visually", or when frontend layout / component / styling changes need a sanity-check before review. Works in restricted/cloud sandboxes where Playwright's normal Chromium install is blocked.
Headless UI preview (Playwright + Chrome for Testing)
Designed for the remote-execution container where:
- `cdn.playwright.dev` / `dl.google.com` are blocked — use Chrome for Testing via `storage.googleapis.com`.
- Ubuntu's `chromium-browser` is a snap stub and won't run.
- MCP servers can't be registered mid-session — use Playwright's Node API directly.
Setup (run once per fresh container)
> ⚠️ **This project uses pnpm** (`frontend/pnpm-lock.yaml` is the source of truth). > Do **NOT** run `npm i` here — npm ignores the pnpm lockfile and re-resolves > transitive deps, which pulls **broken** versions (e.g. `es-toolkit@1.47.0` vs the > pinned `1.46.1`) that crash the whole SPA at load. See *Troubleshooting* below.
`playwright` is already a committed `devDependency` in `frontend/package.json`, so a plain install provides it — no ad-hoc add/revert needed. `@emotion/react` / `@emotion/styled` are likewise already declared.
# 1. Install deps from the lockfile (also fixes a node_modules previously # polluted by npm — wipe it first with `rm -rf node_modules` if you suspect that). cd frontend pnpm install --frozen-lockfile # 2. Download Chrome for Testing (Playwright's own Chromium download is blocked) mkdir -p /tmp/chrome && cd /tmp/chrome curl -fsSL -o chrome.zip \ "https://storage.googleapis.com/chrome-for-testing-public/148.0.7778.96/linux64/chrome-linux64.zip" unzip -q chrome.zip # → /tmp/chrome/chrome-linux64/chrome
**Do NOT** modify `frontend/package.json` / `frontend/pnpm-lock.yaml` for tooling anymore — everything needed is already committed. If a previous session left them dirty (or npm polluted the tree), reset with:
cd <repo> && git checkout -- frontend/package.json frontend/pnpm-lock.yaml pnpm -C frontend install --frozen-lockfile
Dev server
cd frontend USE_MOCK=true node_modules/.bin/vite --mode mock --port 3000 & until curl -fs http://localhost:3000 >/dev/null; do sleep 1; done
`USE_MOCK=true` must be a shell env var (read by `vite.config.ts` before `.env.mock`).
Auth seeding
Every script must seed `localStorage.user_auth_token` via `addInitScript` or the app redirects to the login screen:
await page.addInitScript(() => {
localStorage.setItem('user_auth_token', 'mock-token-for-screenshots');
});First-run onboarding guides
Mock mode defaults these **off** (`src/mocks/mockConfig.ts`), so a fresh browser context no longer pops the "Direct Routing Guide" dialog and steals focus/clicks — no Escape-key dismissal needed before interacting with the page. If a script specifically needs to screenshot or assert the first-run guide itself, force it on per-navigation with a query param (no code change, no shared state between runs):
await page.goto('http://localhost:3000/agent/openai?mockOnboarding=on', { waitUntil: 'networkidle' });`?mockOnboarding=off` is the (redundant) explicit default, useful when a test wants to assert the off state regardless of what a prior run left in localStorage.
Data profile: populated vs. newcomer
The onboarding flag above only controls dialogs. What data the mock backend "has" is a separate switch (`src/mocks/mockConfig.ts`):
await page.goto('http://localhost:3000/credentials?mockData=newcomer', { waitUntil: 'networkidle' });- `?mockData=populated` (default): the full demo data set.
- `?mockData=newcomer`: a fresh install. It has no credentials, quota, usage,
request logs or Claude Code profiles, and the built-in rules have no services. Use it to check that each page lands sensibly before the user has added anything. It also turns the first-run guides on, unless `?mockOnboarding=off` is given.
The profile is kept in `sessionStorage` for the tab, so in-app navigation stays in the chosen world. Pass `?mockData=populated` to switch back, or use a fresh browser context.
Scripts
All scripts live here and are run from `frontend/`. They use `createRequire(cwd)` to resolve `playwright` from `frontend/node_modules` regardless of the script's location:
import { createRequire } from 'module';
const { chromium } = createRequire('file://' + process.cwd() + '/')('playwright');| Script | Mode | Purpose | |--------|------|---------| | [`screenshot.mjs`](screenshot.mjs) | mock | **Ad-hoc template** — copy here and customise; do NOT commit | | [`docs-screenshots.mjs`](docs-screenshots.mjs) | mock | All `docs/images/` product screenshots (`<group>-<index>-<name>.png`, sidebar order) + theme previews; GIF frames listed in `docs/images/gif-frames.txt` | | [`regression-credentials.mjs`](regression-credentials.mjs) | mock | Assertion-based regression for `/credentials` Add API Key flow | | [`scenario-routing-graph.mjs`](scenario-routing-graph.mjs) | real backend | Codex routing graph screenshots (requires running Go server) |
Each script is self-documenting — see its file header for usage, outputs, and known issues.
Shrinking screenshots (optional)
Screenshots are captured with Playwright's plain `page.screenshot()` — the skill deliberately does **not** compress images internally, because optimizers depend on host tooling that is not guaranteed in the run environment. If a PNG is large and you want to trim it before committing, run any optimizer you have available on the captured file afterwards. None of these are required:
- `pngquant --quality 80-95 --strip --force --out foo.png foo.png` (lossy, smallest PNGs)
- `oxipng -o 4 --strip saf
Other skills on tingly-box.
update-libs
Update the libs/ SDK submodules (openai-go, anthropic-sdk-go, go-genai) to new upstream/fork versions, adapt tingly-box to API…
More skills from other plugins.
brainstorming
You MUST use this before any creative work - creating features, building components, adding functionality, modifying behavior, or…
ask-matt
Ask which skill or flow fits your situation. A router over the skills in this repo.
Questions about ui-preview.
- Which coding agents can use ui-preview?
- ui-preview installs in Claude Code, Codex, Cursor, Gemini CLI, GitHub Copilot, OpenCode and Windsurf with the skills CLI, using each agent's flag shown on this page. Flowy's auto-invocation, which fires a skill when your prompt matches, runs in Claude Code only.

