UI Prototype
在 单 route 生成多种截然不同的 UI 变体,从 floating bottom bar 切换。用户在浏览器间 flip variant,选一个(或从每个偷 bit),然后扔掉其余。
若问题是 logic/state 而非 looks——wrong branch。用 LOGIC.md。
何时是该形态
- "What should this page look like?"
- "I want to see a few options for this dashboard before committing."
- "Try a different layout for the settings screen."
- 任何用户否则会在脑中花一天在三个 vague mockup 间选的时候。
两种 sub-shape — 强烈 prefer sub-shape A
UI prototype 在 与 app 其余部分 butt up 时更易 judge——real header、sidebar、data、density。孤立 throwaway route 是 vacuum:每个 variant 单独看都 fine。只要有 plausible existing page host variant 就 default sub-shape A。仅当 prototype genuinely 无 nearby home 才 sub-shape B。
Sub-shape A — 对现有 page 的调整(preferred)
Route 已存在。Variant 在 同 route render,由 ?variant= URL search param gate。现有 data fetching、params、auth 保留——仅 rendering swap。这是 default;除非 specific reason 否则选它。
若 prototype 是尚无 page 但 自然住在某 page 内 的东西(dashboard 新 section、settings 新 card、existing flow 新 step)——仍是 sub-shape A。在 host page 内 mount variant。
Sub-shape B — 新 page(last resort)
仅当被 prototype 的东西 genuinely 无 existing page 可住——如全新 top-level surface,或无法 sensible embed 的 flow。
Create throwaway route obey 项目 routing convention——不要 invent 新 top-level structure。命名 obviously prototype(path 或 filename 含 prototype)。同样 ?variant= pattern。
commit sub-shape B 前 sanity-check:真的无 existing page 可 embed?空 route hide populated page 会 expose 的设计问题。
两 sub-shape 的 floating bottom bar 相同。
流程
1. State the question and pick N
Default 3 variants。超过 5 不再 radically different 而成 noise——cap 在那里。
一行写下 plan,在 prototype 位置或 file 顶 comment:
"Three variants of the settings page, switchable via
?variant=, on the existing/settingsroute."
无论用户是否在都可 push back。
2. Generate radically different variants
Draft 每个 variant。Hold each one to:
- The page's purpose and the data it has access to.
- The project's component library / styling system (TailwindCSS, shadcn, MUI, plain CSS, whatever).
- A clear exported component name, e.g.
VariantA,VariantB,VariantC.
Variant 必须 structurally different——不同 layout、information hierarchy、primary affordance,非仅 colour。三个略 tweak 的 card grid 不是 UI prototype,是 wallpaper。若两 draft 太 similar,用 explicit "do not use a card grid" guidance redo 一个。
3. Wire them together
在 route 创建 single switcher component:
// pseudo-code — adapt to the project's framework
const variant = searchParams.get('variant') ?? 'A';
return (
<>
{variant === 'A' && <VariantA {...data} />}
{variant === 'B' && <VariantB {...data} />}
{variant === 'C' && <VariantC {...data} />}
<PrototypeSwitcher variants={['A','B','C']} current={variant} />
</>
);
Sub-shape A(existing page):switcher 上方保留所有 existing data fetching;仅 rendered subtree per variant 变。
Sub-shape B(new page):/prototype/<name> throwaway route mount 同样 switcher。
4. Build the floating switcher
屏幕 bottom-centre 小 fixed bar,三块:
- Left arrow — 上一 variant(wrap)。
- Variant label — 当前 variant key,若 variant export name 则一并显示。如
B — Sidebar layout。 - Right arrow — 下一 variant(wrap)。
Behaviour:
- Click arrow 更新 URL search param(用 framework router——Next 用
router.replace,React Router 用navigate等)使 variant shareable、reload-stable。 - Keyboard:
←→也 cycle。<input>、<textarea>、[contenteditable]focused 时不 intercept arrow。 - 与 page visually distinct(如 high-contrast pill、subtle shadow)——obviously 非被 evaluate 的设计一部分。
- Production build hidden——gate
process.env.NODE_ENV !== 'production'或等价,stray prototype merge 不能把 bar ship 给用户。
Switcher 放 single shared component,两 sub-shape reuse。位置随项目 shared UI。
5. Hand it over
Surface URL(与 ?variant= keys)。用户随时 flip。有趣 feedback 通常是 "I want the header from B with the sidebar from C"——那才是他们要的 design。
6. Capture the answer and clean up
Variant 胜出后,写下哪个及 why(commit message、ADR、issue,或 AFK 且用户未回时在 prototype 旁 NOTES.md)。然后:
- Sub-shape A — delete 输家 variant 与 switcher;将 winner fold 进 existing page。
- Sub-shape B — 将 winning variant promote 到 real route,delete throwaway route 与 switcher。
不要留 variant component 或 switcher lying around。Rot fast,confuse 下一位 reader。
Anti-patterns
- Variants that differ only in colour or copy. 那是 tweak,非 prototype。Real variant 在 structure 上 disagree。
- Sharing too much code between variants. Shared
<Header>fine;shared<Layout>defeat point。每个 variant 应 free throw out layout。 - Wiring variants to real mutations. Read-only prototype fine。若 variant 需 mutate,point stub——问题是 "what should this look like",非 "does the backend work"。
- Promoting the prototype directly to production. Variant code 在 prototype constraint 下写(无 test、minimal error handling)。fold in 时 properly rewrite。