name: frontend-prototype-builder description: | Build deployable frontend prototypes from Figma designs and test cases. Complete workflow: ensure services → validate data → generate pages → test with browser.
Use when: (1) "构建前端原型" or "build frontend prototype" (2) "从 Figma 生成页面" or "generate pages from Figma" (3) "修复前端 404 问题" or "debug frontend 404" (4) "端到端测试前端" or "E2E test frontend" (5) Need to create testable UI for PM/designers
Inputs: specs/design/pages.yaml, specs/design/figma/, specs/testcases/*.md Outputs: Working frontend pages in frontend/app/ and frontend/components/pages/
Frontend Prototype Builder SOP
Build 70-80% deployable prototypes from Figma designs + test cases that PMs and designers can test with real data.
Prerequisites
Before starting, ensure:
-
specs/design/pages.yamlexists (runux-spec-generatorif missing) -
specs/design/figma/has design docs (runfigma-resource-extractorif missing) -
.synnovator/generated/openapi.yamlexists (runschema-to-openapiif missing) -
frontend/lib/api-client.tsexists (runopenapi-to-componentsif missing)
Workflow Overview
Phase 0: Health Check
↓
Phase 1: Start Services
↓
Phase 2: Validate Database
↓
Phase 3: Generate/Update Pages
↓
Phase 4: Visual Validation (screenshots)
↓
Phase 5: E2E Browser Testing
↓
Phase 6: Fix Issues & Iterate
Phase 0: Health Check
0.1 Check Required Files
# Check if critical files exist
ls -la specs/design/pages.yaml
ls -la .synnovator/generated/openapi.yaml
ls -la frontend/lib/api-client.ts
ls -la frontend/lib/types.ts
If any missing, run the corresponding skill:
- Missing
pages.yaml→/ui-spec-generator - Missing
openapi.yaml→/schema-to-openapi - Missing
api-client.tsortypes.ts→/openapi-to-components
0.2 Check Service Status
# Check if ports are in use
lsof -i :8000 # Backend (FastAPI)
lsof -i :3000 # Frontend (Next.js)
Phase 1: Start Services
1.1 Start Backend (if not running)
# Terminal 1: Start backend
make backend
# Or manually:
# cd /path/to/project && uv run uvicorn app.main:app --reload --port 8000
Wait for: Uvicorn running on http://127.0.0.1:8000
1.2 Verify Backend Health
curl http://localhost:8000/health
# Expected: {"status":"ok"}
1.3 Start Frontend (if not running)
# Terminal 2: Start frontend
make frontend
# Or manually:
# cd frontend && npm run dev
Wait for: Ready in X.Xs
1.4 Verify Frontend Access
curl -I http://localhost:3000
# Expected: HTTP/1.1 200 OK
Phase 2: Validate Database
2.1 Check Database Has Data
# Quick check via API
curl http://localhost:8000/api/users | jq '.total'
curl http://localhost:8000/api/posts | jq '.total'
curl http://localhost:8000/api/categories | jq '.total'
If totals are 0, import test data:
2.2 Import Test Data (if needed)
Use the data-importer skill:
/data-importer
Or manually:
uv run python -m scripts.import_data
2.3 Verify Data Import
# Verify specific entities exist
curl http://localhost:8000/api/posts | jq '.items[0]'
curl http://localhost:8000/api/users | jq '.items[0]'
Phase 3: Generate/Update Pages
3.1 Identify Pages to Generate
Read specs/design/pages.yaml and check which pages exist:
# pages.yaml structure
pages:
- id: home
route: /
pen_file: components/home.pen
...
3.2 Page Generation Checklist
For each page in pages.yaml:
| Step | Check | Action if Missing |
|---|---|---|
| 1 | frontend/app/{route}/page.tsx exists |
Create route file |
| 2 | frontend/components/pages/{id}.tsx exists |
Create component |
| 3 | Component imports from @/lib/api-client |
Add API imports |
| 4 | Component uses correct data fetching | Add useEffect/Server Component |
| 5 | Navigation uses router.push() or <Link> |
Add navigation |
| 6 | Actions call API functions | Wire up handlers |
3.3 Page Component Template
// frontend/app/{route}/page.tsx
import { PageComponent } from "@/components/pages/{page-id}"
export default function Page({ params }: { params: { id?: string } }) {
return <PageComponent {...(params.id ? { id: Number(params.id) } : {})} />
}
// frontend/components/pages/{page-id}.tsx
"use client"
import { useState, useEffect } from "react"
import { useRouter } from "next/navigation"
import { listPosts, getPost, likePost } from "@/lib/api-client"
import type { Post } from "@/lib/types"
export function PageComponent({ id }: { id?: number }) {
const router = useRouter()
const [data, setData] = useState<Post[]>([])
const [loading, setLoading] = useState(true)
useEffect(() => {
async function fetchData() {
try {
const result = await listPosts()
setData(result.items)
} catch (err) {
console.error("Failed to fetch:", err)
} finally {
setLoading(false)
}
}
fetchData()
}, [])
// Handler example
async function handleLike(postId: number) {
try {
await likePost(postId, 1) // userId=1 for prototype
// Refetch or optimistic update
} catch (err) {
console.error("Like failed:", err)
}
}
if (loading) return <div>Loading...</div>
return (
<div>
{data.map(item => (
<div
key={item.id}
onClick={() => router.push(`/posts/${item.id}`)}
className="cursor-pointer"
>
{item.title}
<button onClick={(e) => { e.stopPropagation(); handleLike(item.id) }}>
Like
</button>
</div>
))}
</div>
)
}
3.4 Implement UX Interaction Specs (CRITICAL)
This step is mandatory. Skipping it produces visual-only prototypes with no interactivity.
For each page, read the corresponding specs/ux/pages/{page-id}.yaml and implement all defined interactions.
Interaction Translation Table:
| UX Spec Definition | React Implementation |
|---|---|
trigger: click, action: navigate, target: "/path" |
onClick={() => router.push("/path")} |
trigger: tab_change with filter |
onClick={() => updateSearchParams(key, value)} or router.push(newUrl) |
trigger: select, action: update_url_param |
onChange handler + useSearchParams + router.push |
trigger: click, action: toggle_like |
onClick={handleLike} with API call |
trigger: submit |
onSubmit or onClick={handleSubmit} with API call |
trigger: click, action: open_modal |
onClick={() => setModalOpen(true)} |
trigger: click, action: show_dropdown |
Use shadcn DropdownMenu component |
Implementation Pattern for Tabs with URL Filtering:
// 1. Define route mapping
const tabRoutes: Record<string, string> = {
"热门": "/",
"提案广场": "/proposals",
"找队友": "/posts?tag=find-teammate",
"找点子": "/posts?tag=find-idea",
}
// 2. Add onClick to each tab
<Badge onClick={() => router.push(tabRoutes[tab] ?? "/")}>
{tab}
</Badge>
Implementation Pattern for Filter Dropdowns:
// 1. Use useSearchParams
const searchParams = useSearchParams()
// 2. Create update helper
function updateSearchParam(key: string, value: string | null) {
const next = new URLSearchParams(searchParams.toString())
if (value === null) next.delete(key)
else next.set(key, value)
router.push(`${pathname}?${next.toString()}`)
}
// 3. Wire to component
<DropdownMenuItem onClick={() => updateSearchParam("status", "published")}>
已发布
</DropdownMenuItem>
Checklist for Each Page:
| Step | Check | Source |
|---|---|---|
| 1 | List all interactions from specs/ux/pages/{page}.yaml |
UX spec |
| 2 | For each trigger: click → verify onClick exists |
Code audit |
| 3 | For each trigger: tab_change → verify URL update logic |
Code audit |
| 4 | For each trigger: submit → verify API call wired |
Code audit |
| 5 | Shared components from uses_shared have interactions |
specs/ux/global/shared-components.yaml |
3.5 Common Issues & Fixes
| Issue | Symptom | Fix |
|---|---|---|
| 404 on API | Console: "API 404" | Check backend running, route prefix is /api |
| Buttons don't work | No response | Add onClick handlers, check console for errors |
| Navigation fails | Page not found | Check dynamic route [id] folder exists |
| Data not loading | Empty page | Check useEffect runs, API returns data |
| CORS error | Console: blocked | Backend CORS includes localhost:3000 |
| Tabs don't filter | Click has no effect | Missing onClick → add router.push(tabRoutes[tab]) |
| Dropdowns don't filter | Selection ignored | Missing onChange → use updateSearchParam() helper |
| Like button static | No count update | Missing API call → implement handleLike() with likePost() |
Phase 4: Visual Validation
4.1 Take Screenshots
Use agent-browser to capture current state:
/agent-browser screenshot http://localhost:3000 --output screenshots/home.png
/agent-browser screenshot http://localhost:3000/posts/1 --output screenshots/post-detail.png
4.2 Compare with Figma
Check screenshots against:
specs/design/figma/pages/*.mdfor expected layoutspecs/design/pages.yamlfor component structure
Phase 5: E2E Browser Testing
5.1 Run Test Cases
Use agent-browser to execute test scenarios from specs/testcases/:
/agent-browser test http://localhost:3000 --scenario "Navigate to post detail"
Test checklist:
- Home page loads with data
- Can click on post card → navigates to detail
- Can click like button → count updates
- Can submit comment → appears in list
- Navigation links work
5.2 Automated E2E Script
// tests/e2e/prototype.spec.ts
import { test, expect } from '@playwright/test'
test('home page loads posts', async ({ page }) => {
await page.goto('http://localhost:3000')
await expect(page.locator('[data-testid="post-card"]')).toHaveCount(3)
})
test('navigate to post detail', async ({ page }) => {
await page.goto('http://localhost:3000')
await page.click('[data-testid="post-card"]:first-child')
await expect(page).toHaveURL(/\/posts\/\d+/)
await expect(page.locator('h1')).toBeVisible()
})
test('like button works', async ({ page }) => {
await page.goto('http://localhost:3000/posts/1')
const likeBtn = page.locator('[data-testid="like-btn"]')
const countBefore = await likeBtn.textContent()
await likeBtn.click()
await expect(likeBtn).not.toHaveText(countBefore!)
})
Phase 6: Fix Issues & Iterate
6.1 Common Fix Patterns
Issue: "API 404: Post not found"
// Check: Is the post ID valid?
// Fix: Ensure database has data with that ID
curl http://localhost:8000/api/posts
Issue: "TypeError: Cannot read property 'items' of undefined"
// Check: API response structure
// Fix: Add null checks
const result = await listPosts()
setData(result?.items ?? [])
Issue: "Click does nothing"
// Check: Event handler attached?
// Fix: Add onClick and verify function exists
<Button onClick={() => console.log('clicked')}>Test</Button>
Issue: "Hydration mismatch"
// Check: Server/client rendering difference
// Fix: Use "use client" directive, or dynamic import
"use client" // Add at top of file
6.2 Iteration Cycle
1. Run E2E tests
↓
2. Identify failures
↓
3. Check console/network for errors
↓
4. Apply fix from patterns above
↓
5. Re-run tests
↓
6. Repeat until all tests pass
Quick Reference: Skill Chain
| Phase | Skill to Use | Purpose |
|---|---|---|
| Pre-0 | figma-resource-extractor |
Extract Figma designs |
| Pre-0 | schema-to-openapi |
Generate OpenAPI spec |
| Pre-0 | openapi-to-components |
Generate API client + types |
| Pre-0 | ux-spec-generator |
Generate pages.yaml |
| 2 | data-importer |
Import test data |
| 3 | (this skill) | Generate page components |
| 4-5 | agent-browser |
E2E testing |
Troubleshooting Checklist
Before reporting "it doesn't work", verify:
- Backend server running on :8000 (
curl localhost:8000/health) - Frontend server running on :3000 (
curl -I localhost:3000) - Database has test data (
curl localhost:8000/api/posts | jq .total) - No console errors in browser DevTools
- Network tab shows successful API responses
- Dynamic route folders exist (
app/posts/[id]/page.tsx) - Components import from
@/lib/api-client - Event handlers are wired (check onClick props)
Output Checklist
A successful prototype build produces:
- All pages from
pages.yamlhave route files - All pages fetch and display real data
- Navigation between pages works
- Interactive elements (like, comment, follow) call APIs
- All interactions from
specs/ux/pages/*.yamlare implemented (tabs, filters, buttons) - Tabs/filters update URL params and trigger data refetch
- E2E tests pass for core user journeys
- PM/designer can click through the prototype
Interaction Verification Checklist
Before marking a page complete, verify these interactions work:
Global (all pages)
- Logo click → navigates to
/ - Search bar → expands on focus (or navigates on submit)
- "发布新内容" button → opens publish menu or navigates
- Sidebar nav items → navigate to correct routes
- User avatar dropdown → shows menu options
Tabs & Filters
- Each tab click → updates URL or navigates
- Filter dropdowns → update URL params
- Active state → visually indicates current selection
Content Cards
- Card click → navigates to detail page
- Like button → calls API, updates count
- Share button → opens share modal or copies link
Forms
- Submit button → calls API
- Input validation → shows errors
- Success → shows toast or redirects