For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: A dev-only /styleguide route in builder-ui that renders every real lowkeyui component with source pointers, plus a script that exports it as a self-contained HTML snapshot.
Architecture: A React page (app/src/pages-builder/styleguide/) imports real components / real kp-* markup — never copied CSS — registered behind import.meta.env.DEV via route.lazy. A Node script drives headless Chrome to snapshot the rendered page with its compiled CSS into __drops/lowkey-components.html. Components are added section-by-section with per-section user approval.
Tech Stack: React 18 + react-router v7 (route.lazy), Tailwind (compiled via app.css), vitest + renderPage from @testing/setup, playwright-core (channel: chrome) for export.
Environment notes (read first):
~/.config/af/agents/a1/workspace/builder-ui (bind-mounted into container a1 at ~/workspace/builder-ui).node_modules contains linux native binaries — run pnpm/vitest inside the container: ssh -p 2223 agent@localhost 'cd ~/workspace/builder-ui && <cmd>'.https://local.kualibuild.com:4001 (self-signed cert). Vite hot-reloads file changes — no restarts needed.styleguide-lowkey (already created; spec committed)..lowkey/.lowkey-forms are normally added to <html> by feature flags (app/src/index.jsx:82-86); the styleguide page adds its own wrapper div so it renders correctly regardless of flag state.Files:
Create: app/src/pages-builder/styleguide/index.jsx
Create: app/src/pages-builder/styleguide/section.jsx
Create: app/src/pages-builder/styleguide/__tests__/index.test.jsx
Modify: app/src/routes.jsx (children of RootLayout, next to the debug/:id/* entry at ~line 501)
Step 1: Write the failing smoke test
/* Copyright © 2017-2026 Kuali, Inc. - All Rights Reserved */import { screen } from '@testing-library/react'import { renderPage } from '@testing/setup'import { Component as Styleguide } from '../index'describe('<Styleguide />', () => {test('renders banner and warning', async () => {await renderPage(<Styleguide />)screen.getByText('LowkeyUI Styleguide')screen.getByText(/do not copy styles from this page/i)})})
Run: ssh -p 2223 agent@localhost 'cd ~/workspace/builder-ui && pnpm test:no-coverage app/src/pages-builder/styleguide'
Expected: FAIL — cannot resolve ../index
section.jsx (the per-component frame; its own chrome uses plain stone Tailwind classes — deliberately distinct from the components it displays)/* Copyright © 2017-2026 Kuali, Inc. - All Rights Reserved */import React from 'react'export function Section ({ num, title, source, code, children }) {return (<section className='mt-10'><h2 className='flex items-center gap-2 text-xl font-bold text-stone-900'><span className='rounded-md bg-stone-900 px-2 py-0.5 text-sm font-semibold text-stone-50'>{num}</span>{title}</h2><div className='mt-1 text-xs text-stone-500'>Source of truth: <code>{source}</code></div>{code && (<pre className='mt-2 overflow-x-auto rounded-md bg-stone-200 p-2 text-xs text-stone-800'>{code}</pre>)}<div className='mt-3 flex flex-wrap items-center gap-3 rounded-lg border border-stone-300 bg-white p-5'>{children}</div></section>)}export function Swatch ({ label, children }) {return (<div className='flex flex-col items-start gap-1'><span className='text-[11px] uppercase tracking-wide text-stone-400'>{label}</span>{children}</div>)}
index.jsx/* Copyright © 2017-2026 Kuali, Inc. - All Rights Reserved */import React from 'react'export function Component () {return (<div className='lowkey lowkey-forms min-h-screen bg-stone-100 p-8'><div className='mx-auto max-w-5xl' data-styleguide-ready><h1 className='text-3xl font-bold text-stone-900'>LowkeyUI Styleguide</h1><div className='mt-4 rounded-md border border-amber-300 bg-amber-50 p-4 text-sm text-amber-900'><strong>For AI sessions and devs:</strong> do not copy styles from thispage. Import these components or use these class names — every sectionlists its source of truth. Lowkey styles live in{' '}<code>app/src/app.css</code> (the <code>.lowkey</code> /{' '}<code>.lowkey-forms</code> blocks), <code>app/src/ui/</code>,{' '}<code>app/src/ui/shadcn/</code>, and <code>tailwind.config.js</code>.</div>{/* Component sections are appended here, one per inventory item. */}</div></div>)}export default Component
In app/src/routes.jsx, inside the RootLayout children, directly after { path: 'debug/:id/*', element: <Debug /> }, add:
...(import.meta.env.DEV? [{ path: 'styleguide', lazy: () => import('./pages-builder/styleguide') }]: []),
(route.lazy loads the module's Component export on demand; in prod builds Vite replaces import.meta.env.DEV with false and the route — and its chunk — is eliminated. This mirrors the existing showDevRulePresentation conditional-spread pattern at routes.jsx:502-504.)
Run: ssh -p 2223 agent@localhost 'cd ~/workspace/builder-ui && pnpm test:no-coverage app/src/pages-builder/styleguide'
Expected: PASS (1 test)
Open https://local.kualibuild.com:4001/styleguide in a browser (Playwright MCP or the user's browser). Expected: title + amber banner render.
Contingency: if RootLayout forces a login redirect, that's acceptable for the live page but matters for the export script — note it, and in Task 4 pass a logged-in storageState to Playwright (or move the route next to { path: 'health' } which serves unauthenticated). Record which path was taken in the commit message.
cd ~/.config/af/agents/a1/workspace/builder-uigit add app/src/pages-builder/styleguide app/src/routes.jsxgit commit -m "feat(styleguide): dev-only /styleguide route with scaffold"
Files:
Create: docs/superpowers/specs/2026-06-10-lowkey-styleguide-inventory.md
Step 1: Sweep app.css for every kp-* class styled under .lowkey/.lowkey-forms
Run: awk '/\.lowkey/,/^}/' app/src/app.css | grep -oE 'kp-[a-z-]+' | sort -u
Cross-check by reading app/src/app.css:234-720 directly (some selectors span lines).
Run: ls app/src/ui app/src/ui/shadcn
Known so far: alerts, checkbox, config-box, indeterminate-checkbox, info-box, input, lookup, pop-up, popover, radios, segmented-control, tabs, theme, toggle, tooltip; shadcn: dropdown-menu, popover, separator, sheet, sidebar, skeleton.
Run: grep -rl "lowkey:" app/src --include='*.jsx' --include='*.tsx' | sort and grep -rl "lowKeyUi\|lowKeyForms" app/src/components --include='*.jsx' | sort
These reveal components styled with the lowkey: Tailwind variant or branching on the flags — candidates the old HTML doc missed.
Read app/src/components/app-layout.jsx, app/src/components/home-layout.jsx, app/src/components/sidebar/index.jsx, and one representative screen per shape (e.g. dashboard = sidebar + content; form builder = 3-panel). Record the component/classes that produce each layout.
docs/superpowers/specs/2026-06-10-lowkey-styleguide-inventory.md: a checklist table — section number, name, kind (atom/molecule/layout), real source file(s), seed-doc status (verified / corrected / hallucinated-dropped / newly-discovered). Order: form controls → buttons-adjacent → data display → feedback → overlays → navigation → molecules → layouts. Buttons are already Task 3; number the rest after it.
git add docs/superpowers/specs/2026-06-10-lowkey-styleguide-inventory.mdgit commit -m "docs(styleguide): component inventory from discovery sweep"
Show the user the inventory summary (counts: verified/corrected/dropped/new) and get approval of the build order before continuing.
Files:
Modify: app/src/pages-builder/styleguide/index.jsx
Create: app/src/pages-builder/styleguide/sections/buttons.jsx
Step 1: Re-read the real button styles before writing markup
Read app/src/app.css:234-378. The variants that exist (everything else is hallucination): kp-button-primary, kp-button-secondary, kp-button-destructive, kp-button-outline, kp-button-ghost, kp-button-link (standard group); kp-button-with-icon, kp-button-primary-with-icon, kp-button-spinner-icon, kp-button-dropdown (icon group); kp-button-transparent (.lowkey-forms, app.css:356-378); modifiers kp-button-sm, kp-button-icon-only, kp-button-active.
sections/buttons.jsx/* Copyright © 2017-2026 Kuali, Inc. - All Rights Reserved */import React from 'react'import * as Icons from '../../../icons'import { Section, Swatch } from '../section'export function ButtonsSection ({ num }) {return (<Sectionnum={num}title='Buttons'source='app.css:234-352 (.lowkey .kp-button-*); app.css:356-378 (.lowkey-forms .kp-button-transparent)'code={"<button className='kp-button-primary'>… variants: -secondary -destructive -outline -ghost -link -with-icon -primary-with-icon -dropdown -transparent · modifiers: kp-button-sm kp-button-icon-only kp-button-active"}><Swatch label='primary'><button className='kp-button-primary'>Publish</button></Swatch><Swatch label='secondary'><button className='kp-button-secondary'>Cancel</button></Swatch><Swatch label='destructive'><button className='kp-button-destructive'>Delete</button></Swatch><Swatch label='outline'><button className='kp-button-outline'>Options</button></Swatch><Swatch label='ghost'><button className='kp-button-ghost'>Dismiss</button></Swatch><Swatch label='link'><button className='kp-button-link'>Learn more</button></Swatch><Swatch label='disabled'><button className='kp-button-primary' disabled>Publish</button></Swatch><Swatch label='sm'><button className='kp-button-secondary kp-button-sm'>Apply</button></Swatch><Swatch label='with-icon'><button className='kp-button-with-icon'><Icons.Settings />Settings</button></Swatch><Swatch label='with-icon active'><button className='kp-button-with-icon kp-button-active'><Icons.Settings />Settings</button></Swatch><Swatch label='primary-with-icon'><button className='kp-button-primary-with-icon'><Icons.Settings />Share</button></Swatch><Swatch label='icon-only'><button className='kp-button-outline kp-button-icon-only'><Icons.Settings /></button></Swatch><Swatch label='dropdown'><button className='kp-button-dropdown'>Actions<Icons.MenuDown /></button></Swatch><Swatch label='transparent'><button className='kp-button-transparent'><Icons.Settings />Tools</button></Swatch></Section>)}
(If Icons.Settings / Icons.MenuDown don't exist, pick the closest real exports — check grep -o "export const [A-Za-z]*" app/src/icons/index.tsx | head -40 — and verify the icon names against a real usage like app/src/pages-builder/form/index.jsx:379.)
index.jsximport { ButtonsSection } from './sections/buttons'// inside the wrapper div, replacing the placeholder comment:<ButtonsSection num={1} />
https://local.kualibuild.com:4001/styleguide — all swatches render, hover/disabled behave.kp-button-primary-with-icon; any modal footer uses primary/secondary) and screenshot both. Compare: height (40px / 32px sm), radius (6px), colors (primary = var(--primary, #0c4a6e) — sky-tinted dark, hover sky-950), shadow, gap.Show the user the section (URL + screenshot). Do not proceed to the next section without an explicit OK.
git add app/src/pages-builder/styleguidegit commit -m "feat(styleguide): buttons section"
Files:
Create: scripts/export-styleguide/index.mjs
Modify: package.json (devDependency)
Step 1: Add playwright-core (pure JS — safe across the linux/mac shared node_modules)
Run: ssh -p 2223 agent@localhost 'cd ~/workspace/builder-ui && pnpm add -D playwright-core'
scripts/export-styleguide/index.mjs/* Copyright © 2017-2026 Kuali, Inc. - All Rights Reserved */// Exports the /styleguide page as a self-contained HTML snapshot.// Run from the repo root ON THE HOST (uses installed Google Chrome):// node scripts/export-styleguide/index.mjs [url] [outfile]import fs from 'node:fs'import path from 'node:path'import { chromium } from 'playwright-core'const url = process.argv[2] || 'https://local.kualibuild.com:4001/styleguide'const out =process.argv[3] ||path.resolve(process.cwd(), '../__drops/lowkey-components.html')const browser = await chromium.launch({channel: process.env.PW_CHANNEL || 'chrome',executablePath: process.env.PW_EXECUTABLE_PATH || undefined})const context = await browser.newContext({ignoreHTTPSErrors: true,viewport: { width: 1280, height: 900 },storageState: process.env.PW_STORAGE_STATE || undefined})const page = await context.newPage()await page.goto(url, { waitUntil: 'networkidle' })await page.waitForSelector('[data-styleguide-ready]')const html = await page.evaluate(() => {let css = ''for (const sheet of document.styleSheets) {try {for (const rule of sheet.cssRules) css += rule.cssText + '\n'} catch {/* cross-origin sheet — skip */}}const doc = document.documentElement.cloneNode(true)doc.querySelectorAll('script, link[rel="stylesheet"], style').forEach(n => n.remove())const style = document.createElement('style')style.textContent = cssdoc.querySelector('head').appendChild(style)return '<!doctype html>\n' + doc.outerHTML})const banner = `<!-- GENERATED SNAPSHOT (${new Date().toISOString()}) — do not edit, do not copy styles.Live page: ${url}Regenerate: node scripts/export-styleguide/index.mjsSource of truth: builder-ui app/src/app.css (.lowkey blocks), app/src/ui/, tailwind.config.js -->\n`fs.writeFileSync(out, banner + html)console.log(`wrote ${out} (${Math.round(fs.statSync(out).size / 1024)} KB)`)await browser.close()
Known limitation (acceptable, noted in the file banner): web fonts and images keep their origin URLs, so offline viewing falls back to system fonts; all component styles (the point of the doc) are inlined.
Run on the host: cd ~/.config/af/agents/a1/workspace/builder-ui && node scripts/export-styleguide/index.mjs
Expected: wrote …/__drops/lowkey-components.html (… KB). If Task 1 Step 7 found a login redirect, export a logged-in storage state first (PW_STORAGE_STATE).
Then open ~/.config/af/agents/a1/workspace/__drops/lowkey-components.html — banner + buttons section visually identical to the live page; hover a button to confirm CSS interactivity survived.
git add scripts/export-styleguide package.json pnpm-lock.yamlgit commit -m "feat(styleguide): HTML snapshot export script"
Files:
Modify: CLAUDE.md and/or AGENTS.md (check ls -la CLAUDE.md AGENTS.md — if one symlinks the other, edit the real file once)
Step 1: Add this section
## LowkeyUI styleguide- Live styleguide (dev only): `https://local.kualibuild.com:4001/styleguide`(port = 4000 + af container index). Renders the real lowkey components withper-section source pointers.- Source of truth for lowkey styles: `app/src/app.css` (the `.lowkey` /`.lowkey-forms` blocks), `app/src/ui/`, `app/src/ui/shadcn/`,`tailwind.config.js`. Never copy styles from reference docs or snapshots —import the components or use the `kp-*` classes.- HTML snapshot for visual verification outside the dev env:`node scripts/export-styleguide/index.mjs` (writes the workspace`__drops/lowkey-components.html`).
git add CLAUDE.md AGENTS.mdgit commit -m "docs: point AI sessions at the lowkey styleguide and style sources"
This task repeats for every remaining item in the approved inventory (Task 2), in inventory order — atoms, then molecules (modal/dialog with field form + footer, card/section with labeled fields, table with mixed gadget cells, search/filter bar), then layouts (1/2/3-panel, rendered scaled-down inside a fixed-height bordered frame). The buttons section (Task 3) is the concrete template; per-section code is written at build time because it depends on discovery results and per-section user feedback.
For each inventory item:
app/src/pages-builder/styleguide/sections/<name>.jsx exporting <NameSection num={N} /> built from real imports (app/src/ui/..., ui/shadcn/...) or real kp-* markup. Wrap in formbot-gadget / formbot-config divs when the cascade requires it (e.g. .lowkey-forms .formbot-gadget .kp-input). Render interactive-only states statically too (dropdown open, modal visible, datepicker expanded) so the snapshot captures them. Components needing data get minimal inline mock props — no API calls.index.jsx with the next section number.git commit -m "feat(styleguide): <name> section".After the final section: re-run the export script (Task 4 Step 3), verify the snapshot end-to-end, and update the smoke test to assert one element from the first and last sections.
ssh -p 2223 agent@localhost 'cd ~/workspace/builder-ui && pnpm test:no-coverage' — expected: green (pre-existing failures, if any, noted to the user).__drops/lowkey-components.html opens standalone with every section visible.fix/lowkey-rollout-bugfixes).