video2fullstack — replicate a recorded website as a working full-stack app
Faithfully replicate the website demonstrated in the reference video. Two equally weighted goals:
- Frontend fidelity — layout + visual style + real text content + interactions + animations, same as classic video2code.
- Backend fidelity — the behaviors the video proves are server-driven must be served by a real HTTP API with persistent storage in your replica. A pixel-perfect page whose data is hardcoded in components is a FAILED task, no matter how good it looks.
The video is an operation recording: a user driving the site through a journey. Every observable consequence of an operation (price changes when a SKU is selected, an item appearing in the cart on another page, a validation error on submit) is evidence about the backend. Extracting that evidence is as much your job as reading the layout.
Environment (read once, trust it)
- Video: 任务给出的源视频路径(mp4/webm 等;若任务给的是 http(s) URL,先按
url2video 录成 MP4)。No frames are pre-attached — 第一步用 video server 的 ingest_video 建接触表,消歧用 clip_video。
- Project — you scaffold it (plugin mode, nothing is pre-created):
- Frontend via web-replicate:
bash <plugin_root>/skills/web-replicate/scripts/init-webapp.sh "<site-title>" → creates app/ (React + TS + Vite + Tailwind v4 + shadcn/ui, common libs preinstalled). <plugin_root> is in .v2c/plugin_root. Fill app/src/ with the real pages; fetch all dynamic data from /api/* (relative paths).
- Backend you add on top, single process serving the built frontend + API:
cd app && npm install express better-sqlite3
app/server/db.mjs — open better-sqlite3 at server/data/app.db, db.exec(...) your tables.
app/server/routes.mjs — the business routes the video shows (GET reads, POST/PATCH/DELETE writes) under /api.
app/server/seed.mjs — real data from the video (products/prices/stock rules/…); runnable node server/seed.mjs.
app/server/index.mjs — express.json() → app.use('/api', routes) → express.static('dist') → SPA fallback; PORT||3000.
app/package.json scripts: "build":"vite build", "start":"node server/index.mjs", "seed":"node server/seed.mjs".
- Run for verification:
npm run build && npm run seed && npm start(后台 bash 起服务,别占住前台 shell)→ single server on :3000 serving real pages whose data all comes from /api/*。
- Frame extraction / disambiguation: use the video server 的
clip_video(差分选帧)而不是手写 ffmpeg — 选"能证明一个行为"的帧它更准。
- Verification tooling — ZCode 内置浏览器那一套(硬约束): 必须同时使用 ZCode 官方
control-browser skill(完整遵守其 bootstrap、backend 选择、tab 恢复和页面安全规则;每个 node_repl 调用是新 kernel,都要重新 bootstrap 后 await agent.browsers.get("iab")),用同一个 IAB WebView 打开 http://localhost:3000 重演视频旅程:domSnapshot()/tab.screenshot() 断言状态、tab.recording.start() 拍动效;API 叉用 curl。跨刷新断言 = 重新 goto 同一 URL(或 tab.playwright.reload()),等 domcontentloaded 后再 domSnapshot/screenshot —— 必须看到后端持久化的状态。没有 browser_* MCP 工具;不要安装/启动 Playwright、Chrome 或其它外部浏览器。
- Contract files live under
out/(当前项目目录)。Graded on out/plan.md, out/verify.jsonl, out/report.md plus the shipped app/。
Stack — one server process, boring choices
| Layer |
Use |
Notes |
| Frontend |
React + TypeScript + Vite + Tailwind |
same as video2code; framer-motion/gsap only if the motion needs it |
| Backend |
Node: Express (or Hono) |
plain REST JSON API under /api/* |
| Storage |
better-sqlite3; fallback: a JSON file store you flush on write |
if better-sqlite3 fails to build in this container, do NOT burn rounds — fall back immediately. The requirement is server-side persistence across page reloads, not a specific engine |
| Serving |
ONE process: the API server also statically serves the built frontend (dist/) |
node server/index.mjs listens on port 3000, serves /api/* + static files + SPA fallback |
Frontend components get ALL dynamic data from /api/* via fetch — products, cart, orders. Seed data lives in the database (a seed script or auto-seed on first boot), never inline in JSX. Hardcoding into components what the video shows coming from a server is the defining anti-pattern of this skill.
File contract — what "done" means
| File |
What |
When |
out/plan.md |
Design spec. Every observable concern is one line tagged [S#] (static) / [D#] (dynamic) / [B#] (backend behavior), with measured numbers and source timestamps. Plus a Backend design section: entity tables, API endpoint list, seed-data plan. |
End of Phase 3, before any app code |
out/verify.jsonl |
Append-only log, one JSON object per check: `{"id":"B2","result":"pass" |
"fail" |
out/report.md |
What shipped, how to run it (npm install && npm run build && npm start), API summary, deferred list with reasons. |
Last act |
Done = every [S#]/[D#]/[B#] id in plan.md has a final pass/defer line in verify.jsonl, npm run build passes, the server starts and answers, and report.md exists. Never defer a check you did not attempt.
Video tooling
- Base strip (Phase 1, mandatory first move):
ingest_video(<源视频>) — 拿全片接触表,然后按时间顺序逐帧读完(与 video2code Phase 1 同一约定)。Long videos: read in batches with notes; never skip a region of the timeline.
- Dense clip at a moment:
clip_video,把 Phase-1 的每个疑点和行为转换点拉成密集帧网格。≤8 clips per task.
- Grouped small assets (icon strips, sticker/emote grids, avatar rows): when the video shows a SET of small artworks, find the frame where the set is fully and crisply visible, compute the grid geometry once, and crop EVERY cell from that frame in a loop —
still_crops(<源视频>, [t], crop=[x,y,w,h]) 逐格取(cells occluded or hover-zoomed in that frame → take it from a neighboring frame)。Redrawing such artwork as generic shapes/solid fills is a graded-run rejection — the photograph rule applies at every size: real pixels from frames, never approximations.
- Asset crops: crop real photos/logos out of source frames at full resolution —
still_crops(<源视频>, [t], crop, scale, save_to=...)(asset 模式落 public/assets/ 并记 provenance;返回 /assets/... 路径供 JSX 引用)— Read the result to confirm, adjust, re-crop. Never redraw a photograph; never hotlink; real copy from frames, never lorem ipsum.
Viewport & proportions
- Detect the recording's device pixel ratio BEFORE planning: a video ≥2300px wide showing OS window chrome is almost always a 2× HiDPI capture — the real CSS viewport is video_width/2. State the deduced CSS viewport in plan.md as
[S0], and design + verify at that CSS width(IAB 侧用 setViewportSize 对齐)。
- The video proves the layout at ONE width only. Outside it, follow the platform's natural responsive behavior (fluid/masonry feeds change column count; centered containers keep a max-width with balanced margins). Freezing the recorded pixel width as the only layout — so any other window size overflows or strands the content in a sliver — is a rejection. Mark extrapolations in plan.md as such.
Workflow — strict order
Phase 1 — Observe (page AND behavior)
ingest_video 拿接触表,按时间顺序 Read 全部帧,then write in your message:
## Video observation
<3–6 sentences: site type, visual style, pages visited, core interactions>
## Behavior observation
**Journey**: numbered list of the user's operations with timestamps
1. (~0–8s) browse home, hover category menu → flyout panel
2. (~13s) open product page, select SKU 12+256GB → price shows 3599
...
**Entities**: the domain objects the journey proves exist (product, SKU, cart item, order...), with the fields visible on screen
**State transitions & couplings**: every moment where an operation changed data that a server must own
- SKU selection ↔ price/stock (t≈37s: 512GB → price 3899)
- add-to-cart on page A ↔ cart contents on page B (t≈45→53s)
- submit without address → validation error modal (t≈62s)
**Ambiguities to clip in Phase 2** (or "- (none)"):
Multi-page journeys → build a multi-page app (client-side routing, one route per page); list the routes.
Phase 2 — Clip
One dense clip_video per Phase-1 ambiguity, per [D] candidate, and per state transition you plan to tag [B] (the frames around the click are the evidence of what exactly changed). Priorities: state transitions > animations > hover states.
Phase 3 — Plan (out/plan.md before any code)
# Plan — <site>
## Layout strategy
[S1] ...
## Design tokens
[S2] Colors: ...
## Interactions & animations
[D1] Category flyout on hover: ... (clip 2.0–4.0s; CSS)
## Backend design ← NEW, load-bearing
Entities: product(id,name,price,...), sku(id,product_id,variant,color,price,stock_by_region)...
API: GET /api/products?category=&sort= GET /api/products/:id
GET/POST/PATCH/DELETE /api/cart POST /api/orders (validates address)
Seed: 4+ products with the SKUs/prices/stock states visible in the video
## Backend behaviors
[B1] SKU selection re-prices from server data: 12+256GB=3599, 12+512GB=3899 (t≈21s vs 41s)
[B2] Region-dependent stock: silver variant → "缺货"/notify button (t≈33s)
[B3] Cart persists server-side: add on product page → cart page lists it → survives full reload (t≈45–53s)
[B4] Order submit without address → server rejects with "请选择地址!" (t≈62s)
Tag rules:
[S]/[D] rules are unchanged from video2code: one frozen frame can prove it → [S]; truth lives in coupling to time/scroll/pointer → [D]. Doubt → [D].
[B] test: where does the truth live? If the behavior is about data — its value, its persistence, its validation, its consistency across pages — it is [B], and the truth must live on the server in your replica. A thing can be both [D] (the animation) and [B] (the data change); tag both.
- Every entity/coupling named in the Behavior observation maps to ≥1
[B#] line. Silent omission = failure.
[S#]/[D#]/[B#] are three independent gapless sequences; every line cites its source timestamp(s).
Phase 4 — Build, run, verify
4.1 Seed todos — one write, all pending: build todos (scaffold app+server / seed db / assets / pages / interactions / build / server boot) → one verify-S#/verify-D#/verify-B# todo per plan tag → summary-final.
4.2 Build — the scaffold at app/ is ready; fill in backend contract first (tables in db.mjs/seed.mjs + routes in routes.mjs), then frontend pages against it. Incremental edits, parallel Writes for independent files. Then:
cd app && npm run build — must pass (no npm create/npm install needed unless you added a package).
- Boot the real thing — 后台起服务(
run_in_background 的 bash 跑 node server/index.mjs,日志落 /tmp/server.log),然后 curl -s http://localhost:3000/api/health 和 curl -s http://localhost:3000 | head -30 — API answers AND built frontend is served.
- 按
control-browser 用 IAB 打开 http://localhost:3000,等 domcontentloaded,domSnapshot()/tab.screenshot() 确认可达与视觉状态。
4.3 Verify sweep — one id at a time, fixes deferred and batched:
verify-S*: 在同一 IAB tab 里走到对应页面/状态 → tab.screenshot(),与源帧对比(要出正式对照证据就用 composite_view(source=<源帧>, replica=<截图>))。The pass line must carry a diffs array naming the discrepancies you found (or [] after stating what you compared: layout boxes, colors, copy, counts). A pass that names nothing and compares nothing is invalid — graded runs reject it.
verify-D*: capture the MOTION — tab.recording.start({ actions, ... })(编排规则同 video2code §4.5:scrollTo 定位 + ≤1 屏的 scroll 步拍 coupling、move 做 sweep、settleMs/delayAfterMs 留静置),录成 WebM 后 still_crops(recording, [t...]) 取节拍帧,与源 clip 并排(composite_view beats 一条命令出 SRC|REP strip)。End-state screenshot alone never passes a [D].
verify-B*: evidence must be server-side, two-pronged:
- API prong:
curl the endpoint and show the data (curl -s localhost:3000/api/cart after an add-to-cart shows the item; a POST with a missing field returns the 4xx + error body).
- Journey prong: 同一 IAB tab 用 Browser Use 动作(snapshot-proven locator /
tab.cua)重演视频里的操作,并断言后果跨整页刷新(重新 goto 同一 URL 或 tab.playwright.reload() — fresh JS context)或跨页面仍然成立:domSnapshot()/tab.screenshot() 里要看到数据还在,e.g. add to cart → goto /cart → item present → reload → still present.
- What can never pass a
[B]: component state, props, localStorage, a mocked fetch, data compiled into the bundle. If killing and restarting the server would lose the state and the video shows it persisting, it's a fail. If the frontend renders it but /api/* never returns it, it's a fail.
- One JSON line appended per judged check, at the moment of judgment;
fail → pass only with fresh evidence from this fix round; ≤2 fix rounds then defer with reason. Never fabricate evidence you did not capture.
4.4 Coverage pass (mandatory — your plan is not the ground truth, the video is):
Re-Read ~12 evenly spaced contact-sheet frames asking one question per frame: "what UI surfaces or entry points are visible here that no plan tag covers?" Nav items, icon buttons (a grid icon opening a launcher panel!), badges, dropdowns, modals, footer columns all count. For each untagged finding: append a new [S/D/B] line to plan.md (appending at this stage is expected, not a failure) and verify it like any other id, or defer it with a reason. Then append {"id":"COV1","result":"pass","measured":{"frames_reviewed":N,"new_tags":[...]}}. The real-run failure this exists to catch: the video flashes an icon that opens a 9-app grid; the replica ships without the entry point and every planned check still passes.
4.5 Contract sweep (mandatory, mechanical):
grep -rhoE '"/api/[A-Za-z0-9_/${}:.-]*"' app/src | sort -u → for every path the frontend fetches, curl it (with a plausible id where parameterized): non-404, JSON. Reverse direction: every route in routes.mjs is either fetched by the frontend or listed in report.md as API-only. Append {"id":"C1","result":...,"measured":{"frontend_paths":[...],"unserved":[...],"unfetched":[...]}}. Any frontend fetch that 404s is a fail you must fix — endpoint-name drift that silently renders empty UI is the #1 observed real-run failure of this skill.
4.6 Close-out — every id final (including COV1 and C1) → write out/report.md (shipped summary, run instructions, API table with seed row counts, deferred list) and stop.
Anti-patterns (graded-run rejections)
Frontend ones (unchanged from video2code): coding before observation/plan; ambiguities "(none)" while the video clearly has motion; visible effects never tagged; [S]-tagging scroll/pointer couplings; lorem ipsum; broken/hotlinked images; build failing at close-out; verifying [D] with an end-state screenshot; claiming verification you didn't perform.
Backend ones (new, and the reason this skill exists):
- The pure-frontend cop-out: shipping a beautiful SPA where products/cart/orders are hardcoded arrays or localStorage. That is a failed task even if every
[S]/[D] passes.
- Data in JSX/TS constants instead of the database seed; components that don't fetch.
- An "API" that only has GET routes when the video shows writes (add to cart, submit order).
- Validation done only in the browser when the video shows a server-style rejection — the API must enforce it too (the curl prong catches this).
[B] verified with a screenshot only — no curl prong, no reload prong.
- Skipping the server boot check and shipping
dist/ as if this were a static-site task.
- Inventing backend features the video never demonstrates (auth flows, admin panels) — replicate the evidence, don't pad the scope.
- Closing out without the coverage pass (4.4) or contract sweep (4.5) — "all my planned checks pass" is not "the video is replicated"; the checklist you wrote is not the ground truth, the video is.
- Redrawn substitutes (generic SVG / solid fills) for artwork visible in frames — at any size, including grouped small assets (sticker grids, icon strips, avatars).
- Hard-coding the recorded width as the only layout, or designing at the physical (2×) pixel width of a HiDPI capture.
- Frontend
fetch() paths that 404 against your own server (endpoint-name drift) — the UI renders empty and every screenshot still "passes".
1---2name: video2fullstack3description: Replicating a website shown in an operation-recording video as a FULL-STACK project — frontend (layout, style, interactions, animations) AND a real backend (HTTP API + persistent storage) that reproduces the server-side behavior the video demonstrates (SKU/price coupling, cart persistence, validation, state transitions). Use whenever the task provides a screen recording of someone OPERATING a website (browsing, filtering, adding to cart, submitting forms) and asks to replicate it 复刻/复现 as a working full-stack app.4---56# video2fullstack — replicate a recorded website as a working full-stack app78Faithfully replicate the website demonstrated in the reference video. Two equally weighted goals:9101. **Frontend fidelity** — layout + visual style + real text content + interactions + animations, same as classic video2code.112. **Backend fidelity** — the *behaviors* the video proves are server-driven must be served by a **real HTTP API with persistent storage** in your replica. A pixel-perfect page whose data is hardcoded in components is a FAILED task, no matter how good it looks.1213The video is an **operation recording**: a user driving the site through a journey. Every observable consequence of an operation (price changes when a SKU is selected, an item appearing in the cart on another page, a validation error on submit) is evidence about the backend. Extracting that evidence is as much your job as reading the layout.1415## Environment (read once, trust it)1617- **Video**: 任务给出的源视频路径(mp4/webm 等;若任务给的是 http(s) URL,先按 `url2video` 录成 MP4)。No frames are pre-attached — 第一步用 video server 的 `ingest_video` 建接触表,消歧用 `clip_video`。18- **Project — you scaffold it (plugin mode, nothing is pre-created)**:19 1. **Frontend** via web-replicate: `bash <plugin_root>/skills/web-replicate/scripts/init-webapp.sh "<site-title>"` → creates `app/` (React + TS + Vite + Tailwind v4 + shadcn/ui, common libs preinstalled). `<plugin_root>` is in `.v2c/plugin_root`. Fill `app/src/` with the real pages; **fetch all dynamic data from `/api/*`** (relative paths).20 2. **Backend** you add on top, single process serving the built frontend + API:21 - `cd app && npm install express better-sqlite3`22 - `app/server/db.mjs` — open better-sqlite3 at `server/data/app.db`, `db.exec(...)` your tables.23 - `app/server/routes.mjs` — the business routes the video shows (GET reads, POST/PATCH/DELETE writes) under `/api`.24 - `app/server/seed.mjs` — real data from the video (products/prices/stock rules/…); runnable `node server/seed.mjs`.25 - `app/server/index.mjs` — `express.json()` → `app.use('/api', routes)` → `express.static('dist')` → SPA fallback; `PORT||3000`.26 - `app/package.json` scripts: `"build":"vite build"`, `"start":"node server/index.mjs"`, `"seed":"node server/seed.mjs"`.27 3. **Run for verification**: `npm run build && npm run seed && npm start`(后台 bash 起服务,别占住前台 shell)→ single server on `:3000` serving real pages whose data all comes from `/api/*`。28- **Frame extraction / disambiguation**: use the video server 的 `clip_video`(差分选帧)而不是手写 ffmpeg — 选"能证明一个行为"的帧它更准。29- **Verification tooling — ZCode 内置浏览器那一套(硬约束)**: 必须同时使用 ZCode 官方 `control-browser` skill(完整遵守其 bootstrap、backend 选择、tab 恢复和页面安全规则;每个 `node_repl` 调用是新 kernel,都要重新 bootstrap 后 `await agent.browsers.get("iab")`),用同一个 IAB WebView 打开 `http://localhost:3000` 重演视频旅程:`domSnapshot()`/`tab.screenshot()` 断言状态、`tab.recording.start()` 拍动效;API 叉用 `curl`。跨刷新断言 = 重新 `goto` 同一 URL(或 `tab.playwright.reload()`),等 `domcontentloaded` 后再 domSnapshot/screenshot —— 必须看到后端持久化的状态。**没有 `browser_*` MCP 工具;不要安装/启动 Playwright、Chrome 或其它外部浏览器。**30- **Contract files** live under `out/`(当前项目目录)。Graded on `out/plan.md`, `out/verify.jsonl`, `out/report.md` plus the shipped `app/`。3132## Stack — one server process, boring choices3334| Layer | Use | Notes |35|---|---|---|36| Frontend | React + TypeScript + Vite + Tailwind | same as video2code; framer-motion/gsap only if the motion needs it |37| Backend | Node: **Express** (or Hono) | plain REST JSON API under `/api/*` |38| Storage | **better-sqlite3**; fallback: a JSON file store you flush on write | if better-sqlite3 fails to build in this container, do NOT burn rounds — fall back immediately. The requirement is *server-side persistence across page reloads*, not a specific engine |39| Serving | **ONE process**: the API server also statically serves the built frontend (`dist/`) | `node server/index.mjs` listens on **port 3000**, serves `/api/*` + static files + SPA fallback |4041Frontend components get ALL dynamic data from `/api/*` via fetch — products, cart, orders. Seed data lives in the database (a `seed` script or auto-seed on first boot), never inline in JSX. Hardcoding into components what the video shows coming from a server is the defining anti-pattern of this skill.4243## File contract — what "done" means4445| File | What | When |46|---|---|---|47| `out/plan.md` | Design spec. Every observable concern is one line tagged `[S#]` (static) / `[D#]` (dynamic) / `[B#]` (backend behavior), with measured numbers and source timestamps. Plus a **Backend design** section: entity tables, API endpoint list, seed-data plan. | End of Phase 3, **before any app code** |48| `out/verify.jsonl` | Append-only log, one JSON object per check: `{"id":"B2","result":"pass"|"fail"|"defer","evidence":"<path or command>","measured":{...},"reason":"<required for fail/defer>"}`. Fixes append a new line for the same id; never edit old lines. | Live during Phase 4 |49| `out/report.md` | What shipped, how to run it (`npm install && npm run build && npm start`), API summary, deferred list with reasons. | Last act |5051Done = every `[S#]`/`[D#]`/`[B#]` id in plan.md has a final `pass`/`defer` line in verify.jsonl, `npm run build` passes, the server starts and answers, and report.md exists. Never `defer` a check you did not attempt.5253## Video tooling5455- **Base strip (Phase 1, mandatory first move)**: `ingest_video(<源视频>)` — 拿全片接触表,然后**按时间顺序逐帧读完**(与 video2code Phase 1 同一约定)。Long videos: read in batches with notes; never skip a region of the timeline.56- **Dense clip at a moment**: `clip_video`,把 Phase-1 的每个疑点和行为转换点拉成密集帧网格。≤8 clips per task.57- **Grouped small assets (icon strips, sticker/emote grids, avatar rows)**: when the video shows a SET of small artworks, find the frame where the set is fully and crisply visible, compute the grid geometry once, and crop EVERY cell from that frame in a loop — `still_crops(<源视频>, [t], crop=[x,y,w,h])` 逐格取(cells occluded or hover-zoomed in that frame → take it from a neighboring frame)。Redrawing such artwork as generic shapes/solid fills is a graded-run rejection — the photograph rule applies at every size: real pixels from frames, never approximations.58- **Asset crops**: crop real photos/logos out of source frames at full resolution — `still_crops(<源视频>, [t], crop, scale, save_to=...)`(asset 模式落 `public/assets/` 并记 provenance;返回 `/assets/...` 路径供 JSX 引用)— Read the result to confirm, adjust, re-crop. Never redraw a photograph; never hotlink; real copy from frames, never lorem ipsum.5960## Viewport & proportions6162- Detect the recording's device pixel ratio BEFORE planning: a video ≥2300px wide showing OS window chrome is almost always a 2× HiDPI capture — the real CSS viewport is video_width/2. State the deduced CSS viewport in plan.md as `[S0]`, and design + verify at that CSS width(IAB 侧用 `setViewportSize` 对齐)。63- The video proves the layout at ONE width only. Outside it, follow the platform's natural responsive behavior (fluid/masonry feeds change column count; centered containers keep a max-width with balanced margins). Freezing the recorded pixel width as the only layout — so any other window size overflows or strands the content in a sliver — is a rejection. Mark extrapolations in plan.md as such.6465## Workflow — strict order6667### Phase 1 — Observe (page AND behavior)6869`ingest_video` 拿接触表,按时间顺序 Read 全部帧,then write in your message:7071```72## Video observation73<3–6 sentences: site type, visual style, pages visited, core interactions>7475## Behavior observation76**Journey**: numbered list of the user's operations with timestamps77 1. (~0–8s) browse home, hover category menu → flyout panel78 2. (~13s) open product page, select SKU 12+256GB → price shows 359979 ...80**Entities**: the domain objects the journey proves exist (product, SKU, cart item, order...), with the fields visible on screen81**State transitions & couplings**: every moment where an operation changed data that a server must own82 - SKU selection ↔ price/stock (t≈37s: 512GB → price 3899)83 - add-to-cart on page A ↔ cart contents on page B (t≈45→53s)84 - submit without address → validation error modal (t≈62s)8586**Ambiguities to clip in Phase 2** (or "- (none)"):87```8889Multi-page journeys → build a multi-page app (client-side routing, one route per page); list the routes.9091### Phase 2 — Clip9293One dense `clip_video` per Phase-1 ambiguity, per `[D]` candidate, **and per state transition you plan to tag `[B]`** (the frames around the click are the evidence of what exactly changed). Priorities: state transitions > animations > hover states.9495### Phase 3 — Plan (`out/plan.md` before any code)9697```98# Plan — <site>99## Layout strategy100[S1] ...101## Design tokens102[S2] Colors: ...103## Interactions & animations104[D1] Category flyout on hover: ... (clip 2.0–4.0s; CSS)105## Backend design ← NEW, load-bearing106Entities: product(id,name,price,...), sku(id,product_id,variant,color,price,stock_by_region)...107API: GET /api/products?category=&sort= GET /api/products/:id108 GET/POST/PATCH/DELETE /api/cart POST /api/orders (validates address)109Seed: 4+ products with the SKUs/prices/stock states visible in the video110## Backend behaviors111[B1] SKU selection re-prices from server data: 12+256GB=3599, 12+512GB=3899 (t≈21s vs 41s)112[B2] Region-dependent stock: silver variant → "缺货"/notify button (t≈33s)113[B3] Cart persists server-side: add on product page → cart page lists it → survives full reload (t≈45–53s)114[B4] Order submit without address → server rejects with "请选择地址!" (t≈62s)115```116117Tag rules:118- `[S]`/`[D]` rules are unchanged from video2code: one frozen frame can prove it → `[S]`; truth lives in coupling to time/scroll/pointer → `[D]`. Doubt → `[D]`.119- **`[B]` test: where does the truth live?** If the behavior is about *data* — its value, its persistence, its validation, its consistency across pages — it is `[B]`, and the truth must live **on the server** in your replica. A thing can be both `[D]` (the animation) and `[B]` (the data change); tag both.120- Every entity/coupling named in the Behavior observation maps to ≥1 `[B#]` line. Silent omission = failure.121- `[S#]`/`[D#]`/`[B#]` are three independent gapless sequences; every line cites its source timestamp(s).122123### Phase 4 — Build, run, verify124125**4.1 Seed todos** — one write, all pending: build todos (scaffold app+server / seed db / assets / pages / interactions / build / server boot) → one `verify-S#`/`verify-D#`/`verify-B#` todo per plan tag → `summary-final`.126127**4.2 Build** — the scaffold at `app/` is ready; fill in backend contract first (tables in `db.mjs`/`seed.mjs` + routes in `routes.mjs`), then frontend pages against it. Incremental edits, parallel Writes for independent files. Then:1281. `cd app && npm run build` — must pass (no `npm create`/`npm install` needed unless you added a package).1292. Boot the real thing — 后台起服务(`run_in_background` 的 bash 跑 `node server/index.mjs`,日志落 `/tmp/server.log`),然后 `curl -s http://localhost:3000/api/health` 和 `curl -s http://localhost:3000 | head -30` — API answers AND built frontend is served.1303. 按 `control-browser` 用 IAB 打开 `http://localhost:3000`,等 `domcontentloaded`,`domSnapshot()`/`tab.screenshot()` 确认可达与视觉状态。131132**4.3 Verify sweep — one id at a time, fixes deferred and batched:**133- `verify-S*`: 在同一 IAB tab 里走到对应页面/状态 → `tab.screenshot()`,与源帧对比(要出正式对照证据就用 `composite_view(source=<源帧>, replica=<截图>)`)。The pass line must carry a `diffs` array naming the discrepancies you found (or `[]` **after** stating what you compared: layout boxes, colors, copy, counts). A pass that names nothing and compares nothing is invalid — graded runs reject it.134- `verify-D*`: capture the MOTION — `tab.recording.start({ actions, ... })`(编排规则同 video2code §4.5:`scrollTo` 定位 + ≤1 屏的 `scroll` 步拍 coupling、`move` 做 sweep、settleMs/delayAfterMs 留静置),录成 WebM 后 `still_crops(recording, [t...])` 取节拍帧,与源 clip 并排(`composite_view` beats 一条命令出 SRC|REP strip)。End-state screenshot alone never passes a `[D]`.135- `verify-B*`: evidence must be **server-side, two-pronged**:136 1. **API prong**: `curl` the endpoint and show the data (`curl -s localhost:3000/api/cart` after an add-to-cart shows the item; a POST with a missing field returns the 4xx + error body).137 2. **Journey prong**: 同一 IAB tab 用 Browser Use 动作(snapshot-proven locator / `tab.cua`)重演视频里的操作,并断言后果**跨整页刷新**(重新 `goto` 同一 URL 或 `tab.playwright.reload()` — fresh JS context)或跨页面仍然成立:`domSnapshot()`/`tab.screenshot()` 里要看到数据还在,e.g. add to cart → goto /cart → item present → reload → still present.138 - **What can never pass a `[B]`**: component state, props, localStorage, a mocked fetch, data compiled into the bundle. If killing and restarting the server would lose the state *and* the video shows it persisting, it's a fail. If the frontend renders it but `/api/*` never returns it, it's a fail.139- One JSON line appended per judged check, at the moment of judgment; `fail` → `pass` only with fresh evidence from this fix round; ≤2 fix rounds then `defer` with reason. Never fabricate evidence you did not capture.140141**4.4 Coverage pass (mandatory — your plan is not the ground truth, the video is):**142Re-Read ~12 evenly spaced contact-sheet frames asking one question per frame: *"what UI surfaces or entry points are visible here that no plan tag covers?"* Nav items, icon buttons (a grid icon opening a launcher panel!), badges, dropdowns, modals, footer columns all count. For each untagged finding: append a new `[S/D/B]` line to plan.md (appending at this stage is expected, not a failure) and verify it like any other id, or `defer` it with a reason. Then append `{"id":"COV1","result":"pass","measured":{"frames_reviewed":N,"new_tags":[...]}}`. The real-run failure this exists to catch: the video flashes an icon that opens a 9-app grid; the replica ships without the entry point and every planned check still passes.143144**4.5 Contract sweep (mandatory, mechanical):**145`grep -rhoE '"/api/[A-Za-z0-9_/${}:.-]*"' app/src | sort -u` → for every path the frontend fetches, `curl` it (with a plausible id where parameterized): **non-404, JSON**. Reverse direction: every route in `routes.mjs` is either fetched by the frontend or listed in report.md as API-only. Append `{"id":"C1","result":...,"measured":{"frontend_paths":[...],"unserved":[...],"unfetched":[...]}}`. Any frontend fetch that 404s is a `fail` you must fix — endpoint-name drift that silently renders empty UI is the #1 observed real-run failure of this skill.146147**4.6 Close-out** — every id final (including COV1 and C1) → write `out/report.md` (shipped summary, run instructions, API table with seed row counts, deferred list) and stop.148149## Anti-patterns (graded-run rejections)150151Frontend ones (unchanged from video2code): coding before observation/plan; ambiguities "(none)" while the video clearly has motion; visible effects never tagged; `[S]`-tagging scroll/pointer couplings; lorem ipsum; broken/hotlinked images; build failing at close-out; verifying `[D]` with an end-state screenshot; claiming verification you didn't perform.152153Backend ones (new, and the reason this skill exists):154- **The pure-frontend cop-out**: shipping a beautiful SPA where products/cart/orders are hardcoded arrays or localStorage. That is a failed task even if every `[S]`/`[D]` passes.155- Data in JSX/TS constants instead of the database seed; components that don't fetch.156- An "API" that only has GET routes when the video shows writes (add to cart, submit order).157- Validation done only in the browser when the video shows a server-style rejection — the API must enforce it too (the curl prong catches this).158- `[B]` verified with a screenshot only — no curl prong, no reload prong.159- Skipping the server boot check and shipping `dist/` as if this were a static-site task.160- Inventing backend features the video never demonstrates (auth flows, admin panels) — replicate the evidence, don't pad the scope.161- Closing out without the coverage pass (4.4) or contract sweep (4.5) — "all my planned checks pass" is not "the video is replicated"; the checklist you wrote is not the ground truth, the video is.162- Redrawn substitutes (generic SVG / solid fills) for artwork visible in frames — at any size, including grouped small assets (sticker grids, icon strips, avatars).163- Hard-coding the recorded width as the only layout, or designing at the physical (2×) pixel width of a HiDPI capture.164- Frontend `fetch()` paths that 404 against your own server (endpoint-name drift) — the UI renders empty and every screenshot still "passes".