diff --git a/skills/INDEX.md b/skills/INDEX.md index f4974a5..e2aa616 100644 --- a/skills/INDEX.md +++ b/skills/INDEX.md @@ -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 | diff --git a/skills/lab/SKILL.md b/skills/lab/SKILL.md new file mode 100644 index 0000000..667556b --- /dev/null +++ b/skills/lab/SKILL.md @@ -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 `` | 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. diff --git a/skills/lab/assets/lab-build.png b/skills/lab/assets/lab-build.png new file mode 100644 index 0000000..66c8ea3 Binary files /dev/null and b/skills/lab/assets/lab-build.png differ diff --git a/skills/lab/assets/lab-view.png b/skills/lab/assets/lab-view.png new file mode 100644 index 0000000..0b2fc52 Binary files /dev/null and b/skills/lab/assets/lab-view.png differ diff --git a/skills/lab/references/harness-blueprint.md b/skills/lab/references/harness-blueprint.md new file mode 100644 index 0000000..62962f8 --- /dev/null +++ b/skills/lab/references/harness-blueprint.md @@ -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/ +
.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/
.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 : +{IS_DEV && } />} +{IS_DEV && } />} +``` + +Keep the real production routes untouched. Optionally alias the production landing +route to `` *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 ( +
+ {/* lab navbar: plain, very low height, two controls top-right */} +
+ {title} +
+ + +
+
+ +
+ {/* MAIN preview - desktop width */} +
+ {/* shared real site navbar, inside the preview */} + {children} +
+ + {/* mobile sidebar - toggled from the navbar, mirrors the same content at ~390px */} + {showMobile && ( + + )} +
+
+ ); +} +``` + +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 ( + + {variants.map(v => ( +
+
{v.id} {v.label} - {v.note}
+ +
+ ))} +
+ ); +} +``` + +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 ( + + {LOCKED.map(({ id, C }) => )} + + ); +} +``` + +## 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. diff --git a/skills/lab/references/scope-questions.md b/skills/lab/references/scope-questions.md new file mode 100644 index 0000000..a31ee73 --- /dev/null +++ b/skills/lab/references/scope-questions.md @@ -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).