Distilled from the bonbon-ai session 2026-09-19: 12 screens × desktop/mobile + 10 overlays of fe/ (Next.js 14,
Tailwind 3) packed in 426 KB, Playwright 22 images, 0 console errors.
WHAT
Purpose and context
- Purpose: a single
.htmlfile opened by double-click (no server, no network, no login) faithfully reproduces the real app's interface — layout, color tokens, fonts, icons, states (loading/empty/error/overdue…), overlays (modal, sheet, toast, tour) — with hard-coded placeholder data. - Trigger (when to use):
- Need to show the interface to people who can't run the app (boss, designer, client, reviewer, wiki documentation).
- Need a mockup with sample data to discuss UX/QC without touching real data or the backend.
- User says 'snapshot UI', 'pack the interface into 1 html file', 'compress the project's UIUX', or
/ui-snapshot.
- Non-goals: no redesign (→ hallmark/redesign), no static screenshots of the running app (→ playwright-verify), no real data, no business logic/API (action buttons only change state within the page).
Mental model
Why PORT instead of building/SSR-ing the real app: the real app needs auth + backend + real data; a snapshot using the real DOM would drag along sensitive data and a large bundle. Porting to vanilla JS with classes copied verbatim + compiling Tailwind with the real config yields pixel-perfect output without carrying anything else.
đọc HẾT JSX các trang → liệt kê màn + trạng thái + overlay → viết app.js (router hash, state, data cứng, 1 hàm/màn, nhánh desktop/mobile) → build.py (Tailwind config thật + font + icon subset + asset data URI) → shot.mjs verify → sửa → publish.
Input and output contract
| Field | Required? | Meaning | |
|---|---|---|---|
| In | frontend directory (--fe) with node_modules |
yes | to use the project's tailwindcss CLI + config |
| In | list of pages/routes | yes (auto-detect app/**/page.tsx, pages/, src/routes) |
scope "entire UI" |
| In | size threshold | no (5 MB) | --max-mb |
| In | main font / logo / banner | no | --font, --asset NAME=path |
| Out | <repo>/…/DDMMYY-<project>-uiux-snapshot.html |
yes | single file, offline |
| Out | report: number of screens, KB, verify images, deviations from the real app | yes | honest, list the simulated parts |
Rules and capabilities
- RULE-01 (MUST): Read ALL the JSX files of each page before porting (including mobile branches, empty/loading/error, modal, sheet). Porting from memory = UI drift.
- RULE-02 (MUST): Tailwind classes copied VERBATIM from the JSX, written as literal strings in
app.js(JIT only sees literals; dynamic concatenationbg-${x}will lose the class). - RULE-03 (MUST): Hardcoded placeholder data, never real data (no API calls, no copying from network capture/DB). Dates use relative offsets (
D(-9)) so the overdue state stays stable over time. - RULE-04 (MUST): Icon names written as quoted strings (
"search") sobuild.pycan subset the font; for dynamic icons, put the full name in the map. - RULE-05 (MUST): Same breakpoint as the app (e.g. 768px) and render the desktop/mobile branch with JS (do not rely on
md:when there is a forced mobile mode in the phone frame). - RULE-06 (MUST): External assets (CDN images, Google fonts) must be embedded or replaced with a background/gradient — the file must run offline. Clearly note every replacement in the report.
- RULE-07 (MUST): Verify with
shot.mjsboth desktop + mobile, read the images,NO ERRORSonly then report done; exceeding--max-mb= FAIL. - RULE-08 (SHOULD): Have a demo panel ("UI" button) to jump to every screen + toggle states (loading/stale, error, toast, tour, AI states…), and Auto/Desktop/Mobile mode.
- Capabilities: read the codebase; the project's Python 3 + Node + Tailwind CLI; network during build (Google Fonts, codepoints); Playwright for verification.
Failure boundaries
- Project doesn't use Tailwind →
build.pyleaves CSS empty: copy the CSS module/styled into the template by hand, or clarify with the user. - No network at build time → fonts/icons can't be embedded → blocked (or accept a system font fallback, stated clearly).
- File exceeds the threshold → reduce: drop unnecessary font weights, smaller
sips -Zimages, less sample data; do not cut screens. - Other frameworks (Vue/Svelte/Angular template) → still apply, just change the source for reading classes.
HOW
Main workflow
| Step | Type | Inputs | Action | Outputs/exit | Failure/next |
|---|---|---|---|---|---|
| W01 | deterministic | --fe |
Scan routes + layout + shared components; read tailwind.config, globals.css, design.md if present |
list of screens + tokens | no Tailwind → FB1 |
| W02 | judgment | JSX of each page | Read ALL of each page (RULE-01), note every state/overlay/mobile branch | inventory of screens × states | — |
| W03 | effect | inventory | Create <scratch>/app.js from assets/app-skeleton.js, template.html from assets/template.html (replace __TITLE__, __FONT__); port each screen, classes verbatim, hardcoded data |
app.js | — |
| W04 | deterministic | app.js + template | python3 scripts/build.py --fe <fe> --src <scratch> --out <file> --font "<Font>" --asset LOGO=public/logo.png |
file HTML + KB | Tailwind error → see stderr; >max → FB3 |
| W05 | deterministic | file | node scripts/shot.mjs <file> <scratch>/shots d-home=home m-home=home … (install playwright into scratch if missing) then READ the image |
image + NO ERRORS |
error → fix app.js, repeat W04 |
| W06 | effect | file | Place into repo (e.g. llmwiki/html/DDMMYY-<project>-uiux-snapshot.html), write wiki log if the project has one; report number of screens, KB, simulated spots |
report | — |
Branches
| ID | Kind | Guard | Behavior | Skip / failure | Rejoin |
|---|---|---|---|---|---|
| B01 | conditional | page has dead-code UI (state has no entry path) | still port but only open via demo panel, note "legacy" | — | W03 |
| B02 | conditional | app has a devtool page dark/different from design system | port using inline style like the original | — | W03 |
| B03 | recovery | mobile mode on wide screen breaks layout | frame #frame has transform to fixed stick to frame; #app is the scroll area; override .h-screen/.min-h-screen = 100% (already in template) |
— | W05 |
Validation and stopping
Deterministic: build.py exit 0 and below threshold; shot.mjs print NO ERRORS for every desktop + mobile screen. Needs eyes: compare images with real app/Figma (colors, spacing, Vietnamese text with diacritics). Stop when every screen in inventory has a clean image and the report has listed simulated spots.
Examples
- Positive: bonbon-ai
fe/→ login, dashboard overview/pending/overdue, filter by department/contract type, ticket detail 3 tabs, VS Code overlay, AI tracker, sheet/modal/toast/tour → 426 KB, 79 icons, 22 verify images, 0 errors. - Boundary: project with 3 MB hero background image from CDN → replace with gradient + note "different from real app: login background" instead of embedding causing the file to exceed threshold.
Deterministic test
python3 scripts/build.py --fe <fe> --src assets-copy --out /tmp/t.html with assets/app-skeleton.js (rename app.js) + assets/template.html must output file < 1 MB, then node scripts/shot.mjs /tmp/t.html /tmp/shots d-home=home m-home=home print NO ERRORS.