Skip to content

Commit af59a65

Browse files
FE lab: add variant-driven design lab skill (#115)
* feat(skills): add lab - variant-driven frontend design skill Dev-only lab for exploring real-React section/page variants before writing production code. Cook variants one target at a time, lock the winner, assemble an additive /lab-view, then wire properly. Includes harness blueprint (cook/view/chrome with theme + toggleable mobile-sidebar) and a scope-question playbook. * docs(lab): toggleable mobile sidebar + visual showcase assets * fix(lab): use valid category 'core' + regenerate skills index The skills-index behaviour test enforces category in {core, domain, meta}; 'workflow' was invalid. lab is a process skill like blueprint/ship-gate, all of which are 'core'. Regenerated skills/INDEX.md to include lab.
1 parent 53bf2c8 commit af59a65

6 files changed

Lines changed: 342 additions & 0 deletions

File tree

skills/INDEX.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ Categories:
2222
| `deliver` | Use after all implementation tasks are complete. Runs final verification, confirms the branch is clean, detects the work |
2323
| `engineering-discipline` | Apply senior-level software engineering discipline including design patterns, SOLID principles, architectural reasoning, |
2424
| `forge-plan` | Use after blueprint design approval to produce a task-by-task implementation plan grounded in MCP-verified API calls. No |
25+
| `lab` | Use when designing or revamping a frontend section or whole page and you want to explore real-React variants in an isola |
2526
| `optimizer` | Teaches runtime analysis - deriving Big-O straight from code - and how to derive a better algorithm by removing redundan |
2627
| `parallel-dispatch` | Use when facing 2+ independent tasks that can be investigated or executed without shared state or sequential dependencie |
2728
| `run-plan` | Use when you have an existing plan, spec, or task list to execute. Validates the plan for gaps and MCP accuracy before a |

skills/lab/SKILL.md

Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
---
2+
name: lab
3+
category: core
4+
description: Use when designing or revamping a frontend section or whole page and you want to explore real-React variants in an isolated, dev-only lab before writing production code. Cook variants for one target at a time, lock the winner, watch an additive page assemble in /lab-view, then wire it properly. Forces real-stack previews (not throwaway HTML/Figma), desktop+mobile side by side, and a clean codebase with only the current target's scratch alive.
5+
---
6+
7+
# The Lab - variant-driven frontend design in the real stack
8+
9+
Design-by-mockup lies. HTML comps and Figma frames never survive contact with the
10+
real component tree, the real responsive rules, or the real design tokens - so what
11+
the user approves is not what ships. The Lab fixes that: explore variants **in the
12+
real framework** (real components, real breakpoints) but **isolated from production**,
13+
build the page **additively** one locked target at a time, and only write production
14+
code once the whole assembled page is approved.
15+
16+
## The Iron Laws
17+
18+
```
19+
1. ONE TARGET IN COOK
20+
Only the current target's variants exist as scratch code. Everything else is
21+
either a clean locked winner or not written yet. Never two targets in flight.
22+
23+
2. COOK EMPTIES ON LOCK
24+
The instant a target is locked, delete the losing variants and clear the cook.
25+
No variant graveyard. Git history is the backup if a loser is ever wanted back.
26+
27+
3. VIEW BEFORE CODE
28+
No production wiring until /lab-view (the whole assembled page) is approved.
29+
The lab is where decisions happen; production is where they get implemented.
30+
31+
4. SCAFFOLD IS THROWAWAY
32+
Scoped lab CSS, CDN fonts, and inline styles are prototype-only and NEVER ship.
33+
Final wiring re-implements with real design tokens + self-hosted fonts + proper
34+
folders. The lab buys speed of iteration, not shippable code.
35+
36+
5. DEV-ONLY
37+
Every lab route is gated by the framework's dev flag (e.g. import.meta.env.DEV).
38+
The lab must not exist in a production build.
39+
```
40+
41+
Violating the letter = violating the spirit.
42+
43+
## The 3 moving pieces
44+
45+
| Piece | What it is |
46+
|---|---|
47+
| **target** | The unit of work being refined. One section **or** one whole page - **never more than a page**. Bounds scope so the cook stays legible. |
48+
| **/lab-cook** | The kitchen. Renders **only the current target's variants**. Empties on lock. |
49+
| **/lab-view** | The connected additive page - every locked target stitched top-to-bottom. The realistic "this is the page" preview, before any production code. |
50+
51+
Both routes share the same **site navbar** (so previews sit under realistic chrome)
52+
and a **toggleable mobile-view sidebar** (a ~390px phone frame mirroring the main
53+
preview), so desktop and mobile can be judged together on demand.
54+
55+
## The harness layout (both routes)
56+
57+
```
58+
┌──────────────────────────────────────────────────┐
59+
│ [lab navbar - plain, very low height] [☾/☀][▢] │ ← theme toggle · mobile-sidebar toggle
60+
├───────────────────────────────────────┬┄┄┄┄┄┄┄┄┄┄┄┤
61+
│ ┌─ site navbar (shared) ────────────┐ ┊ mobile ┊
62+
│ ├───────────────────────────────────┤ ┊ sidebar ┊ ← toggled on/off
63+
│ │ MAIN preview (desktop width) │ ┊ (~390px, ┊ from the navbar
64+
│ │ cook → current target variants │ ┊ mirrors ┊
65+
│ │ view → full connected page │ ┊ main) ┊
66+
│ └───────────────────────────────────┘ ┊ ┊
67+
└───────────────────────────────────────┴┄┄┄┄┄┄┄┄┄┄┄┘
68+
```
69+
70+
- **lab navbar** top-right has exactly two controls: `[ light · dark ]` theme toggle
71+
(preview both themes) and `[ mobile ]` toggle that **shows/hides the mobile-view
72+
sidebar** (the ~390px phone mirror). Default state is the project's choice; the
73+
point is mobile is one click away, judged beside desktop, before any lock.
74+
- **cook** main = the variants of the target in flight.
75+
- **view** main = every locked target, connected.
76+
77+
Concrete React/route scaffolding: `references/harness-blueprint.md`.
78+
79+
## The labcycle (CLUD) - per target
80+
81+
| Step | Verb | What happens to the code |
82+
|---|---|---|
83+
| 1 | **Create** | Write N variants (default 3) of the target into the cook scratch. `/lab-cook` shows only these. |
84+
| 2 | **Update** | Not satisfied → rewrite the cook scratch with fresh variants, **same target**. Loop until satisfied. |
85+
| 3 | **Lock** | Promote the winner → extract into a clean locked component, add to the ordered locked list → it appears in `/lab-view`. |
86+
| 4 | **Destroy** | Delete the losing variants and **empty the cook scratch**. No dead code remains. |
87+
| 5 | **Next** | Repeat for the next target. |
88+
89+
## Two phases
90+
91+
**Phase 0 - Setup (once per project).** Build the harness: the lab navbar (+ theme &
92+
device toggles), the shared site navbar, the mobile-view sidebar, `/lab-cook`,
93+
`/lab-view`, and the cook→lock→view plumbing. All dev-gated. Carry any already-decided
94+
sections in as the first locked entries.
95+
96+
**Phase 1 - Steady state (every target, repeats).**
97+
98+
```
99+
1. Ask TARGET → which section, or the whole page? (never more than a page)
100+
2. Ask 1-3 SCOPE Qs → pin direction/constraints BEFORE building (references/scope-questions.md)
101+
3. Build VARIANTS → default 3, in /lab-cook, desktop main + mobile sidebar
102+
4. UPDATE (iterate) → not happy? fresh variants, same target, loop
103+
5. LOCK → winner → /lab-view; cook empties (Iron Laws 1 & 2)
104+
6. → next target
105+
```
106+
107+
When **/lab-view is complete and approved** → leave the lab and do **final wiring**
108+
(below). The lab's job is done; nothing in it ships as-is.
109+
110+
## Variant rules
111+
112+
- **Default 3 per round.** Genuinely distinct - a different *idea* or layout per
113+
variant, not three recolors of one. If the user dislikes all three, the next round
114+
should change the *approach*, not nudge pixels.
115+
- **Always desktop + mobile.** Never present a desktop-only variant; the mobile
116+
sidebar exists for this.
117+
- **Honest content only.** Real metrics, real copy. No fabricated numbers, logos, or
118+
testimonials - placeholder lies poison the decision.
119+
- **Label every variant**: short name + one-line note on the trade-off.
120+
- **One question to lock.** After showing variants, ask which to lock (or what to
121+
change), then act. Do not assume.
122+
123+
## Final wiring (handoff out of the lab)
124+
125+
Once `/lab-view` is approved, re-implement for production - this is a separate phase,
126+
not part of the lab:
127+
128+
| Throwaway (lab) | Ships (final) |
129+
|---|---|
130+
| scoped `lab.css`, hard-coded OKLCH | real design-system tokens (`design-tokens` skill) |
131+
| Google-fonts CDN `<link>` | self-hosted fonts (`@fontsource`, etc.) |
132+
| inline `style={{}}` | token-backed utilities / component styles |
133+
| `src/lab/` scratch | `components/sections/` proper folder + reusable parts |
134+
| dev-gated `/lab-view` | swap into the real production route |
135+
136+
Then run `ship-gate` (and `designer_verify_implementation` if a `DESIGN.md` exists)
137+
before claiming done.
138+
139+
## Red flags - STOP
140+
141+
| Thought | Reality |
142+
|---|---|
143+
| "I'll keep all sections' variants in one registry" | That's a board, not a cook. One target's scratch at a time (Iron Law 1). |
144+
| "Lock it but keep the other two around just in case" | No graveyard. Delete on lock; git remembers (Iron Law 2). |
145+
| "The lab looks great, let me ship this code" | Lab CSS/fonts/inline are throwaway. Wire it properly first (Iron Law 4). |
146+
| "I'll just tweak the production landing directly" | Then you lose the isolated additive view. Use the lab. |
147+
| "Three variants but they're basically the same" | Distinct ideas, not recolors. Change the approach. |
148+
| "Show desktop now, mobile later" | Toggle the mobile sidebar on and judge both before locking. |
149+
| "I'll start building before asking scope" | Ask target + 1-3 scope Qs first, or you build the wrong thing. |
150+
| "Mount the lab route unconditionally" | Dev-gate it. The lab never ships (Iron Law 5). |
151+
| "Use realistic-looking fake stats" | Honest content only. Fake proof poisons the decision. |
152+
153+
## Integration
154+
155+
- **Upstream**: `designer` (a `DESIGN.md` contract sets the visual language each
156+
variant must honor) and `brainstorming` (settle intent before cooking).
157+
- **During**: `behaviour-analysis` on a locked target before moving on (states,
158+
edge cases, a11y).
159+
- **Final wiring**: `design-tokens` (migrate scoped CSS → tokens), `frontend-design`
160+
/ `shadcn-expert` (production components), then `ship-gate` + `designer_verify_implementation`.
161+
162+
The lab does not replace the design decision (that is `designer`); it is the place to
163+
make and validate that decision in the real stack, one target at a time, before
164+
committing production code.

skills/lab/assets/lab-build.png

246 KB
Loading

skills/lab/assets/lab-view.png

324 KB
Loading
Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
# Harness blueprint (Vite + React)
2+
3+
Concrete Phase 0 scaffolding. Adapt the dev flag and router to the project's stack.
4+
Goal: `/lab-cook` (current target only) and `/lab-view` (locked targets assembled),
5+
both under a lab navbar (theme + device toggles) with a pinned mobile sidebar, all
6+
dev-only. Everything here is throwaway prototype scaffolding (Iron Law 4).
7+
8+
## File layout
9+
10+
```
11+
src/lab/
12+
lab.css scoped prototype styles (OKLCH, mirrors the design system) - throwaway
13+
shared.tsx prototype primitives + a useLabFonts() CDN-font loader
14+
chrome.tsx LabShell: lab navbar (theme + device toggles) + mobile sidebar
15+
cook.tsx /lab-cook - renders ONLY the current target's variants
16+
view.tsx /lab-view - assembles locked/* in order
17+
current-target.tsx the ONE target in flight + its N variant components (the only scratch)
18+
locked/
19+
<section>.tsx one clean component per locked target (the winners)
20+
index.ts ordered export list -> what /lab-view renders
21+
```
22+
23+
`current-target.tsx` is the only file that changes per round. On lock: move the
24+
winner into `locked/<section>.tsx`, append it to `locked/index.ts`, then blank
25+
`current-target.tsx` (Iron Laws 1 & 2).
26+
27+
## Routing (dev-gated)
28+
29+
```tsx
30+
const IS_DEV = import.meta.env.DEV;
31+
const LabCook = React.lazy(() => import("@/lab/cook"));
32+
const LabView = React.lazy(() => import("@/lab/view"));
33+
34+
// inside <Routes>:
35+
{IS_DEV && <Route path="/lab-cook" element={<LabCook />} />}
36+
{IS_DEV && <Route path="/lab-view" element={<LabView />} />}
37+
```
38+
39+
Keep the real production routes untouched. Optionally alias the production landing
40+
route to `<LabView/>` *only* when `IS_DEV`, so the additive page shows up where the
41+
user expects it - but never in a production build.
42+
43+
## LabShell (chrome.tsx) - navbar + mobile sidebar
44+
45+
```tsx
46+
export function LabShell({ title, children }: { title: string; children: ReactNode }) {
47+
const [theme, setTheme] = useState<"light" | "dark">("light");
48+
const [showMobile, setShowMobile] = useState(true); // mobile sidebar toggle
49+
useLabFonts();
50+
return (
51+
<div data-theme={theme} className="lab-root">
52+
{/* lab navbar: plain, very low height, two controls top-right */}
53+
<header className="lab-navbar">
54+
<span className="lab-title">{title}</span>
55+
<div className="lab-controls">
56+
<button onClick={() => setTheme(t => (t === "light" ? "dark" : "light"))}>
57+
{theme === "light" ? "☾ dark" : "☀ light"}
58+
</button>
59+
<button
60+
aria-pressed={showMobile}
61+
onClick={() => setShowMobile(v => !v)}
62+
>
63+
▢ mobile
64+
</button>
65+
</div>
66+
</header>
67+
68+
<div className="lab-body">
69+
{/* MAIN preview - desktop width */}
70+
<main className="lab-main">
71+
<SiteNavbar /> {/* shared real site navbar, inside the preview */}
72+
{children}
73+
</main>
74+
75+
{/* mobile sidebar - toggled from the navbar, mirrors the same content at ~390px */}
76+
{showMobile && (
77+
<aside className="lab-mobile-sidebar">
78+
<div className="phone" style={{ width: 390 }}>
79+
<SiteNavbar mobile />
80+
{children}
81+
</div>
82+
</aside>
83+
)}
84+
</div>
85+
</div>
86+
);
87+
}
88+
```
89+
90+
Notes:
91+
- The `[mobile]` toggle shows/hides the mobile-view sidebar (the constant ~390px
92+
reference). Main preview stays desktop width; flip the sidebar on to judge mobile
93+
beside it before locking.
94+
- `data-theme` drives the scoped token set (light / warm-charcoal dark) in `lab.css`.
95+
- `SiteNavbar` is the real site nav (or a faithful stand-in) so previews sit under
96+
realistic chrome and the mobile sidebar shows the mobile drawer.
97+
98+
## cook.tsx - current target only
99+
100+
```tsx
101+
import { variants } from "@/lab/current-target"; // [{ id, label, note, C }]
102+
103+
export default function LabCook() {
104+
return (
105+
<LabShell title="lab-cook · <target name>">
106+
{variants.map(v => (
107+
<section key={v.id} className="lab-variant">
108+
<div className="lab-variant-label"><b>{v.id}</b> {v.label} - {v.note}</div>
109+
<v.C />
110+
</section>
111+
))}
112+
</LabShell>
113+
);
114+
}
115+
```
116+
117+
When the cook is empty (between targets), `variants` is `[]` and the page reads
118+
"empty - awaiting next target."
119+
120+
## view.tsx - locked targets assembled
121+
122+
```tsx
123+
import { LOCKED } from "@/lab/locked"; // ordered [{ id, C }]
124+
125+
export default function LabView() {
126+
return (
127+
<LabShell title="lab-view · additive">
128+
{LOCKED.map(({ id, C }) => <C key={id} />)}
129+
</LabShell>
130+
);
131+
}
132+
```
133+
134+
## Mirroring the design system in lab.css
135+
136+
So variants read true, the scoped `lab.css` should copy the project's real tokens
137+
(cream/ink surfaces, plum/accent, shadows, radii) as plain OKLCH custom properties,
138+
plus `[data-theme="dark"]` overrides. Load the intended fonts from a CDN in
139+
`useLabFonts()` for the prototype. None of this ships - final wiring replaces it with
140+
the real token files and self-hosted fonts.
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Scope-question playbook
2+
3+
After the user names a **target** and before building variants, ask **1-3** scope
4+
questions. The goal is to pin the variant space so the three variants are distinct
5+
*and* all in the right neighborhood - not to interrogate. Pick the 1-3 that most
6+
change what you build. Skip anything already answered by a `DESIGN.md` or prior turn.
7+
8+
## When to ask vs. just build
9+
10+
- Ask when the answer **changes the variants** (feel, layout family, content source).
11+
- Don't ask what you can read (existing design tokens, the locked sections above this
12+
one, the project's component library).
13+
- Never ask "is this ok?" - that's what locking is for.
14+
15+
## The menu (choose 1-3)
16+
17+
| Dimension | Why it matters | Example question |
18+
|---|---|---|
19+
| **Feel / direction** | Biggest lever; wrong feel = all variants rejected | "Dark/technical, warm-editorial, or playful for this one?" |
20+
| **Job of the section** | Determines content + hierarchy | "Is this section's job to convince, explain, or convert?" |
21+
| **Hero element** | What the variants are built around | "Should the visual lead be the live trace, a static diagram, or copy-first?" |
22+
| **Content truth** | Honest content only (Iron rule) | "What real numbers/copy can I use here? Omit anything not real." |
23+
| **Layout family** | Bounds the 3 variants | "Split, centered, or full-bleed - or should I try one of each?" |
24+
| **Density / length** | Affects rhythm and mobile | "Compact band or a taller, breathing section?" |
25+
| **Reference** | Fast calibration | "Any site whose version of this section you like?" |
26+
27+
## Default if the user is terse
28+
29+
If the user says "just build it," default to: **3 variants spanning different layout
30+
families** (e.g. split / centered / full-bleed), same feel as the locked sections
31+
above, honest content pulled from the codebase. Then let the lock + iterate loop
32+
converge.
33+
34+
## After variants are shown
35+
36+
One closing question only: **"which locks, or what changes?"** Then act - lock and
37+
move on, or iterate the same target with a changed *approach* (not pixel nudges).

0 commit comments

Comments
 (0)