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 /settings route."

无论用户是否在都可 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。