Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions skills/INDEX.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Categories:
| `deliver` | Use after all implementation tasks are complete. Runs final verification, confirms the branch is clean, detects the work |
| `engineering-discipline` | Apply senior-level software engineering discipline including design patterns, SOLID principles, architectural reasoning, |
| `forge-plan` | Use after blueprint design approval to produce a task-by-task implementation plan grounded in MCP-verified API calls. No |
| `lab` | Use when designing or revamping a frontend section or whole page and you want to explore real-React variants in an isola |
| `optimizer` | Teaches runtime analysis - deriving Big-O straight from code - and how to derive a better algorithm by removing redundan |
| `parallel-dispatch` | Use when facing 2+ independent tasks that can be investigated or executed without shared state or sequential dependencie |
| `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 |
Expand Down
164 changes: 164 additions & 0 deletions skills/lab/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
---
name: lab
category: core
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.
---

# The Lab - variant-driven frontend design in the real stack

Design-by-mockup lies. HTML comps and Figma frames never survive contact with the
real component tree, the real responsive rules, or the real design tokens - so what
the user approves is not what ships. The Lab fixes that: explore variants **in the
real framework** (real components, real breakpoints) but **isolated from production**,
build the page **additively** one locked target at a time, and only write production
code once the whole assembled page is approved.

## The Iron Laws

```
1. ONE TARGET IN COOK
Only the current target's variants exist as scratch code. Everything else is
either a clean locked winner or not written yet. Never two targets in flight.

2. COOK EMPTIES ON LOCK
The instant a target is locked, delete the losing variants and clear the cook.
No variant graveyard. Git history is the backup if a loser is ever wanted back.

3. VIEW BEFORE CODE
No production wiring until /lab-view (the whole assembled page) is approved.
The lab is where decisions happen; production is where they get implemented.

4. SCAFFOLD IS THROWAWAY
Scoped lab CSS, CDN fonts, and inline styles are prototype-only and NEVER ship.
Final wiring re-implements with real design tokens + self-hosted fonts + proper
folders. The lab buys speed of iteration, not shippable code.

5. DEV-ONLY
Every lab route is gated by the framework's dev flag (e.g. import.meta.env.DEV).
The lab must not exist in a production build.
```

Violating the letter = violating the spirit.

## The 3 moving pieces

| Piece | What it is |
|---|---|
| **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. |
| **/lab-cook** | The kitchen. Renders **only the current target's variants**. Empties on lock. |
| **/lab-view** | The connected additive page - every locked target stitched top-to-bottom. The realistic "this is the page" preview, before any production code. |

Both routes share the same **site navbar** (so previews sit under realistic chrome)
and a **toggleable mobile-view sidebar** (a ~390px phone frame mirroring the main
preview), so desktop and mobile can be judged together on demand.

## The harness layout (both routes)

```
┌──────────────────────────────────────────────────┐
│ [lab navbar - plain, very low height] [☾/☀][▢] │ ← theme toggle · mobile-sidebar toggle
├───────────────────────────────────────┬┄┄┄┄┄┄┄┄┄┄┄┤
│ ┌─ site navbar (shared) ────────────┐ ┊ mobile ┊
│ ├───────────────────────────────────┤ ┊ sidebar ┊ ← toggled on/off
│ │ MAIN preview (desktop width) │ ┊ (~390px, ┊ from the navbar
│ │ cook → current target variants │ ┊ mirrors ┊
│ │ view → full connected page │ ┊ main) ┊
│ └───────────────────────────────────┘ ┊ ┊
└───────────────────────────────────────┴┄┄┄┄┄┄┄┄┄┄┄┘
```

- **lab navbar** top-right has exactly two controls: `[ light · dark ]` theme toggle
(preview both themes) and `[ mobile ]` toggle that **shows/hides the mobile-view
sidebar** (the ~390px phone mirror). Default state is the project's choice; the
point is mobile is one click away, judged beside desktop, before any lock.
- **cook** main = the variants of the target in flight.
- **view** main = every locked target, connected.

Concrete React/route scaffolding: `references/harness-blueprint.md`.

## The labcycle (CLUD) - per target

| Step | Verb | What happens to the code |
|---|---|---|
| 1 | **Create** | Write N variants (default 3) of the target into the cook scratch. `/lab-cook` shows only these. |
| 2 | **Update** | Not satisfied → rewrite the cook scratch with fresh variants, **same target**. Loop until satisfied. |
| 3 | **Lock** | Promote the winner → extract into a clean locked component, add to the ordered locked list → it appears in `/lab-view`. |
| 4 | **Destroy** | Delete the losing variants and **empty the cook scratch**. No dead code remains. |
| 5 | **Next** | Repeat for the next target. |

## Two phases

**Phase 0 - Setup (once per project).** Build the harness: the lab navbar (+ theme &
device toggles), the shared site navbar, the mobile-view sidebar, `/lab-cook`,
`/lab-view`, and the cook→lock→view plumbing. All dev-gated. Carry any already-decided
sections in as the first locked entries.

**Phase 1 - Steady state (every target, repeats).**

```
1. Ask TARGET → which section, or the whole page? (never more than a page)
2. Ask 1-3 SCOPE Qs → pin direction/constraints BEFORE building (references/scope-questions.md)
3. Build VARIANTS → default 3, in /lab-cook, desktop main + mobile sidebar
4. UPDATE (iterate) → not happy? fresh variants, same target, loop
5. LOCK → winner → /lab-view; cook empties (Iron Laws 1 & 2)
6. → next target
```

When **/lab-view is complete and approved** → leave the lab and do **final wiring**
(below). The lab's job is done; nothing in it ships as-is.

## Variant rules

- **Default 3 per round.** Genuinely distinct - a different *idea* or layout per
variant, not three recolors of one. If the user dislikes all three, the next round
should change the *approach*, not nudge pixels.
- **Always desktop + mobile.** Never present a desktop-only variant; the mobile
sidebar exists for this.
- **Honest content only.** Real metrics, real copy. No fabricated numbers, logos, or
testimonials - placeholder lies poison the decision.
- **Label every variant**: short name + one-line note on the trade-off.
- **One question to lock.** After showing variants, ask which to lock (or what to
change), then act. Do not assume.

## Final wiring (handoff out of the lab)

Once `/lab-view` is approved, re-implement for production - this is a separate phase,
not part of the lab:

| Throwaway (lab) | Ships (final) |
|---|---|
| scoped `lab.css`, hard-coded OKLCH | real design-system tokens (`design-tokens` skill) |
| Google-fonts CDN `<link>` | self-hosted fonts (`@fontsource`, etc.) |
| inline `style={{}}` | token-backed utilities / component styles |
| `src/lab/` scratch | `components/sections/` proper folder + reusable parts |
| dev-gated `/lab-view` | swap into the real production route |

Then run `ship-gate` (and `designer_verify_implementation` if a `DESIGN.md` exists)
before claiming done.

## Red flags - STOP

| Thought | Reality |
|---|---|
| "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). |
| "Lock it but keep the other two around just in case" | No graveyard. Delete on lock; git remembers (Iron Law 2). |
| "The lab looks great, let me ship this code" | Lab CSS/fonts/inline are throwaway. Wire it properly first (Iron Law 4). |
| "I'll just tweak the production landing directly" | Then you lose the isolated additive view. Use the lab. |
| "Three variants but they're basically the same" | Distinct ideas, not recolors. Change the approach. |
| "Show desktop now, mobile later" | Toggle the mobile sidebar on and judge both before locking. |
| "I'll start building before asking scope" | Ask target + 1-3 scope Qs first, or you build the wrong thing. |
| "Mount the lab route unconditionally" | Dev-gate it. The lab never ships (Iron Law 5). |
| "Use realistic-looking fake stats" | Honest content only. Fake proof poisons the decision. |

## Integration

- **Upstream**: `designer` (a `DESIGN.md` contract sets the visual language each
variant must honor) and `brainstorming` (settle intent before cooking).
- **During**: `behaviour-analysis` on a locked target before moving on (states,
edge cases, a11y).
- **Final wiring**: `design-tokens` (migrate scoped CSS → tokens), `frontend-design`
/ `shadcn-expert` (production components), then `ship-gate` + `designer_verify_implementation`.

The lab does not replace the design decision (that is `designer`); it is the place to
make and validate that decision in the real stack, one target at a time, before
committing production code.
Binary file added skills/lab/assets/lab-build.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added skills/lab/assets/lab-view.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
140 changes: 140 additions & 0 deletions skills/lab/references/harness-blueprint.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
# Harness blueprint (Vite + React)

Concrete Phase 0 scaffolding. Adapt the dev flag and router to the project's stack.
Goal: `/lab-cook` (current target only) and `/lab-view` (locked targets assembled),
both under a lab navbar (theme + device toggles) with a pinned mobile sidebar, all
dev-only. Everything here is throwaway prototype scaffolding (Iron Law 4).

## File layout

```
src/lab/
lab.css scoped prototype styles (OKLCH, mirrors the design system) - throwaway
shared.tsx prototype primitives + a useLabFonts() CDN-font loader
chrome.tsx LabShell: lab navbar (theme + device toggles) + mobile sidebar
cook.tsx /lab-cook - renders ONLY the current target's variants
view.tsx /lab-view - assembles locked/* in order
current-target.tsx the ONE target in flight + its N variant components (the only scratch)
locked/
<section>.tsx one clean component per locked target (the winners)
index.ts ordered export list -> what /lab-view renders
```

`current-target.tsx` is the only file that changes per round. On lock: move the
winner into `locked/<section>.tsx`, append it to `locked/index.ts`, then blank
`current-target.tsx` (Iron Laws 1 & 2).

## Routing (dev-gated)

```tsx
const IS_DEV = import.meta.env.DEV;
const LabCook = React.lazy(() => import("@/lab/cook"));
const LabView = React.lazy(() => import("@/lab/view"));

// inside <Routes>:
{IS_DEV && <Route path="/lab-cook" element={<LabCook />} />}
{IS_DEV && <Route path="/lab-view" element={<LabView />} />}
```

Keep the real production routes untouched. Optionally alias the production landing
route to `<LabView/>` *only* when `IS_DEV`, so the additive page shows up where the
user expects it - but never in a production build.

## LabShell (chrome.tsx) - navbar + mobile sidebar

```tsx
export function LabShell({ title, children }: { title: string; children: ReactNode }) {
const [theme, setTheme] = useState<"light" | "dark">("light");
const [showMobile, setShowMobile] = useState(true); // mobile sidebar toggle
useLabFonts();
return (
<div data-theme={theme} className="lab-root">
{/* lab navbar: plain, very low height, two controls top-right */}
<header className="lab-navbar">
<span className="lab-title">{title}</span>
<div className="lab-controls">
<button onClick={() => setTheme(t => (t === "light" ? "dark" : "light"))}>
{theme === "light" ? "☾ dark" : "☀ light"}
</button>
<button
aria-pressed={showMobile}
onClick={() => setShowMobile(v => !v)}
>
▢ mobile
</button>
</div>
</header>

<div className="lab-body">
{/* MAIN preview - desktop width */}
<main className="lab-main">
<SiteNavbar /> {/* shared real site navbar, inside the preview */}
{children}
</main>

{/* mobile sidebar - toggled from the navbar, mirrors the same content at ~390px */}
{showMobile && (
<aside className="lab-mobile-sidebar">
<div className="phone" style={{ width: 390 }}>
<SiteNavbar mobile />
{children}
</div>
</aside>
)}
</div>
</div>
);
}
```

Notes:
- The `[mobile]` toggle shows/hides the mobile-view sidebar (the constant ~390px
reference). Main preview stays desktop width; flip the sidebar on to judge mobile
beside it before locking.
- `data-theme` drives the scoped token set (light / warm-charcoal dark) in `lab.css`.
- `SiteNavbar` is the real site nav (or a faithful stand-in) so previews sit under
realistic chrome and the mobile sidebar shows the mobile drawer.

## cook.tsx - current target only

```tsx
import { variants } from "@/lab/current-target"; // [{ id, label, note, C }]

export default function LabCook() {
return (
<LabShell title="lab-cook · <target name>">
{variants.map(v => (
<section key={v.id} className="lab-variant">
<div className="lab-variant-label"><b>{v.id}</b> {v.label} - {v.note}</div>
<v.C />
</section>
))}
</LabShell>
);
}
```

When the cook is empty (between targets), `variants` is `[]` and the page reads
"empty - awaiting next target."

## view.tsx - locked targets assembled

```tsx
import { LOCKED } from "@/lab/locked"; // ordered [{ id, C }]

export default function LabView() {
return (
<LabShell title="lab-view · additive">
{LOCKED.map(({ id, C }) => <C key={id} />)}
</LabShell>
);
}
```

## Mirroring the design system in lab.css

So variants read true, the scoped `lab.css` should copy the project's real tokens
(cream/ink surfaces, plum/accent, shadows, radii) as plain OKLCH custom properties,
plus `[data-theme="dark"]` overrides. Load the intended fonts from a CDN in
`useLabFonts()` for the prototype. None of this ships - final wiring replaces it with
the real token files and self-hosted fonts.
37 changes: 37 additions & 0 deletions skills/lab/references/scope-questions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# Scope-question playbook

After the user names a **target** and before building variants, ask **1-3** scope
questions. The goal is to pin the variant space so the three variants are distinct
*and* all in the right neighborhood - not to interrogate. Pick the 1-3 that most
change what you build. Skip anything already answered by a `DESIGN.md` or prior turn.

## When to ask vs. just build

- Ask when the answer **changes the variants** (feel, layout family, content source).
- Don't ask what you can read (existing design tokens, the locked sections above this
one, the project's component library).
- Never ask "is this ok?" - that's what locking is for.

## The menu (choose 1-3)

| Dimension | Why it matters | Example question |
|---|---|---|
| **Feel / direction** | Biggest lever; wrong feel = all variants rejected | "Dark/technical, warm-editorial, or playful for this one?" |
| **Job of the section** | Determines content + hierarchy | "Is this section's job to convince, explain, or convert?" |
| **Hero element** | What the variants are built around | "Should the visual lead be the live trace, a static diagram, or copy-first?" |
| **Content truth** | Honest content only (Iron rule) | "What real numbers/copy can I use here? Omit anything not real." |
| **Layout family** | Bounds the 3 variants | "Split, centered, or full-bleed - or should I try one of each?" |
| **Density / length** | Affects rhythm and mobile | "Compact band or a taller, breathing section?" |
| **Reference** | Fast calibration | "Any site whose version of this section you like?" |

## Default if the user is terse

If the user says "just build it," default to: **3 variants spanning different layout
families** (e.g. split / centered / full-bleed), same feel as the locked sections
above, honest content pulled from the codebase. Then let the lock + iterate loop
converge.

## After variants are shown

One closing question only: **"which locks, or what changes?"** Then act - lock and
move on, or iterate the same target with a changed *approach* (not pixel nudges).
Loading