Fixture. This page is the stdout of
wayfinder skill prototype/ui, run against a config with tracker value github cli and two registered ticket skills (grill-design, pre-mortem). Change either and this render changes with it.A supporting file.
prototype/ui is disclosed by prototype, which is where an agent is told to fetch it. It carries no station of its own on the route.$ wayfinder skill prototype/ui112 lines
1# UI Prototype
2
3Generate **several radically different UI variations** on a single route, switchable from a floating bottom bar. The user flips between variants in the browser, picks one (or steals bits from each), then throws the rest away.
4
5If the question is about logic/state rather than what something looks like — wrong branch. Use [LOGIC.md](LOGIC.md).
6
7## When this is the right shape
8
9- "What should this page look like?"
10- "I want to see a few options for this dashboard before committing."
11- "Try a different layout for the settings screen."
12- Any time the user would otherwise spend a day picking between three vague mockups in their head.
13
14## Two sub-shapes — strongly prefer sub-shape A
15
16A UI prototype is much easier to judge when it's **butting up against the rest of the app** — real header, real sidebar, real data, real density. A throwaway route on its own is a vacuum: every variant looks fine in isolation. Default to sub-shape A whenever there's a plausible existing page to host the variants. Only reach for sub-shape B if the prototype genuinely has no nearby home.
17
18### Sub-shape A — adjustment to an existing page (preferred)
19
20The route already exists. Variants are rendered **on the same route**, gated by a `?variant=` URL search param. The existing data fetching, params, and auth all stay — only the rendering swaps. This is the default; pick it unless there's a specific reason not to.
21
22If the prototype is for something that doesn't yet have a page but *would naturally live inside one* (a new section of the dashboard, a new card on the settings screen, a new step in an existing flow) — that's still sub-shape A. Mount the variants inside the host page.
23
24### Sub-shape B — a new page (last resort)
25
26Only use this when the thing being prototyped genuinely has no existing page to live inside — e.g. an entirely new top-level surface, or a flow that can't be embedded anywhere sensible.
27
28Create a **throwaway route** following whatever routing convention the project already uses — don't invent a new top-level structure. Name it so it's obviously a prototype (e.g. include the word `prototype` in the path or filename). Same `?variant=` pattern.
29
30Before committing to sub-shape B, sanity-check: is there really no existing page this could be embedded in? An empty route hides design problems that a populated one would expose.
31
32In both sub-shapes the floating bottom bar is identical.
33
34## Process
35
36### 1. State the question and pick N
37
38Default to **3 variants**. More than 5 stops being radically different and starts being noise — cap there.
39
40Write down the plan in one line, in the prototype's location or a top-of-file comment:
41
42> "Three variants of the settings page, switchable via `?variant=`, on the existing `/settings` route."
43
44This works whether the user is here to push back or not.
45
46### 2. Generate radically different variants
47
48Draft each variant. Hold each one to:
49
50- The page's purpose and the data it has access to.
51- The project's component library / styling system (TailwindCSS, shadcn, MUI, plain CSS, whatever).
52- A clear exported component name, e.g. `VariantA`, `VariantB`, `VariantC`.
53
54Variants must be **structurally different** — different layout, different information hierarchy, different primary affordance, not just different colours. Three slightly-tweaked card grids isn't a UI prototype, it's wallpaper. If two drafts come out too similar, redo one with explicit "do not use a card grid" guidance.
55
56### 3. Wire them together
57
58Create a single switcher component on the route:
59
60```tsx
61// pseudo-code — adapt to the project's framework
62const variant = searchParams.get('variant') ?? 'A';
63return (
64 <>
65 {variant === 'A' && <VariantA {...data} />}
66 {variant === 'B' && <VariantB {...data} />}
67 {variant === 'C' && <VariantC {...data} />}
68 <PrototypeSwitcher variants={['A','B','C']} current={variant} />
69 </>
70);
71```
72
73For sub-shape A (existing page): keep all the existing data fetching above the switcher; only the rendered subtree changes per variant.
74
75For sub-shape B (new page): the throwaway route under `/prototype/<name>` mounts the same switcher.
76
77### 4. Build the floating switcher
78
79A small fixed-position bar at the bottom-centre of the screen with three pieces:
80
81- **Left arrow** — cycles to the previous variant (wraps around).
82- **Variant label** — shows the current variant key and, if the variant exports a name, that name too. e.g. `B — Sidebar layout`.
83- **Right arrow** — cycles forward (wraps around).
84
85Behaviour:
86
87- Clicking an arrow updates the URL search param (use the framework's router — `router.replace` on Next, `navigate` on React Router, etc) so the variant is shareable and reload-stable.
88- Keyboard: `←` and `→` arrow keys also cycle. Don't intercept arrow keys when an `<input>`, `<textarea>`, or `[contenteditable]` is focused.
89- Visually distinct from the page (e.g. high-contrast pill, subtle shadow) so it's obviously not part of the design being evaluated.
90- Hidden in production builds — gate on `process.env.NODE_ENV !== 'production'` or an equivalent check, so a stray prototype merge can't ship the bar to users.
91
92Put the switcher in a single shared component so both sub-shapes can reuse it. Locate it wherever shared UI lives in the project.
93
94### 5. Hand it over
95
96Surface the URL (and the `?variant=` keys). The user will flip through whenever they get to it. The interesting feedback is usually **"I want the header from B with the sidebar from C"** — that's the actual design they want.
97
98### 6. Capture the answer and clean up
99
100Once a variant has won, capture the answer — which variant and why — then capture the prototype the way the [SKILL](SKILL.md) describes. Fold the winner into the real code and move the rest onto the throwaway branch, not into main:
101
102- **Sub-shape A** — fold the winner into the existing page; drop the losing variants and the switcher from main.
103- **Sub-shape B** — promote the winning variant to a real route; drop the throwaway route and the switcher from main.
104
105The full set of variants is the primary source, so it lands on the throwaway branch, not the bin — variant components and the switcher left in the main branch rot fast and confuse the next reader.
106
107## Anti-patterns
108
109- **Variants that differ only in colour or copy.** That's a tweak, not a prototype. Real variants disagree about structure.
110- **Sharing too much code between variants.** A shared `<Header>` is fine; a shared `<Layout>` defeats the point. Each variant should be free to throw out the layout.
111- **Wiring variants to real mutations.** Read-only prototypes are fine. If a variant needs to mutate, point it at a stub — the question is "what should this look like", not "does the backend work".
112- **Promoting the prototype directly to production.** The variant code was written under prototype constraints (no tests, minimal error handling). Rewrite it properly when you fold it in.