@autono/create-open-pages 0.1.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. package/README.md +6 -3
  2. package/dist/cli.js +1 -1
  3. package/package.json +1 -1
  4. package/template/.agents/skills/apply-comments/SKILL.md +4 -4
  5. package/template/.agents/skills/create-page/SKILL.md +26 -8
  6. package/template/.agents/skills/create-theme/SKILL.md +137 -150
  7. package/template/.agents/skills/current-page/SKILL.md +1 -1
  8. package/template/.agents/skills/page-authoring/SKILL.md +97 -42
  9. package/template/.agents/skills/page-authoring/references/interactivity.md +71 -23
  10. package/template/.agents/skills/page-authoring/references/layout-and-responsive.md +38 -17
  11. package/template/.agents/skills/page-authoring/references/typography-and-color.md +50 -32
  12. package/template/.agents/skills/shadcn/SKILL.md +277 -0
  13. package/template/.agents/skills/shadcn/assets/shadcn-small.png +0 -0
  14. package/template/.agents/skills/shadcn/assets/shadcn.png +0 -0
  15. package/template/.agents/skills/shadcn/cli.md +290 -0
  16. package/template/.agents/skills/shadcn/customization.md +209 -0
  17. package/template/.agents/skills/shadcn/mcp.md +105 -0
  18. package/template/.agents/skills/shadcn/registry.md +277 -0
  19. package/template/.agents/skills/shadcn/rules/base-vs-radix.md +306 -0
  20. package/template/.agents/skills/shadcn/rules/chat.md +224 -0
  21. package/template/.agents/skills/shadcn/rules/composition.md +213 -0
  22. package/template/.agents/skills/shadcn/rules/forms.md +192 -0
  23. package/template/.agents/skills/shadcn/rules/icons.md +101 -0
  24. package/template/.agents/skills/shadcn/rules/styling.md +185 -0
  25. package/template/AGENTS.md +7 -4
  26. package/template/README.md +21 -4
  27. package/template/components.json +9 -0
  28. package/template/hooks/use-mobile.ts +19 -0
  29. package/template/lib/utils.ts +6 -0
  30. package/template/package.json +27 -5
  31. package/template/pages/getting-started/index.tsx +51 -45
  32. package/template/styles/globals.css +118 -0
  33. package/template/tsconfig.json +13 -2
  34. package/template/ui/accordion.tsx +64 -0
  35. package/template/ui/alert-dialog.tsx +196 -0
  36. package/template/ui/alert.tsx +66 -0
  37. package/template/ui/aspect-ratio.tsx +11 -0
  38. package/template/ui/attachment.tsx +204 -0
  39. package/template/ui/avatar.tsx +107 -0
  40. package/template/ui/badge.tsx +48 -0
  41. package/template/ui/breadcrumb.tsx +109 -0
  42. package/template/ui/bubble.tsx +125 -0
  43. package/template/ui/button-group.tsx +83 -0
  44. package/template/ui/button.tsx +64 -0
  45. package/template/ui/calendar.tsx +218 -0
  46. package/template/ui/card.tsx +92 -0
  47. package/template/ui/carousel.tsx +241 -0
  48. package/template/ui/chart.tsx +374 -0
  49. package/template/ui/checkbox.tsx +32 -0
  50. package/template/ui/collapsible.tsx +31 -0
  51. package/template/ui/combobox.tsx +308 -0
  52. package/template/ui/command.tsx +182 -0
  53. package/template/ui/context-menu.tsx +252 -0
  54. package/template/ui/dialog.tsx +156 -0
  55. package/template/ui/direction.tsx +22 -0
  56. package/template/ui/drawer.tsx +133 -0
  57. package/template/ui/dropdown-menu.tsx +257 -0
  58. package/template/ui/empty.tsx +104 -0
  59. package/template/ui/field.tsx +246 -0
  60. package/template/ui/form.tsx +167 -0
  61. package/template/ui/hover-card.tsx +42 -0
  62. package/template/ui/input-group.tsx +170 -0
  63. package/template/ui/input-otp.tsx +77 -0
  64. package/template/ui/input.tsx +21 -0
  65. package/template/ui/item.tsx +193 -0
  66. package/template/ui/kbd.tsx +28 -0
  67. package/template/ui/label.tsx +22 -0
  68. package/template/ui/marker.tsx +69 -0
  69. package/template/ui/menubar.tsx +276 -0
  70. package/template/ui/message-scroller.tsx +128 -0
  71. package/template/ui/message.tsx +92 -0
  72. package/template/ui/native-select.tsx +62 -0
  73. package/template/ui/navigation-menu.tsx +168 -0
  74. package/template/ui/pagination.tsx +127 -0
  75. package/template/ui/popover.tsx +87 -0
  76. package/template/ui/progress.tsx +31 -0
  77. package/template/ui/radio-group.tsx +43 -0
  78. package/template/ui/resizable.tsx +53 -0
  79. package/template/ui/scroll-area.tsx +56 -0
  80. package/template/ui/select.tsx +190 -0
  81. package/template/ui/separator.tsx +26 -0
  82. package/template/ui/sheet.tsx +143 -0
  83. package/template/ui/sidebar.tsx +726 -0
  84. package/template/ui/skeleton.tsx +13 -0
  85. package/template/ui/slider.tsx +61 -0
  86. package/template/ui/sonner.tsx +40 -0
  87. package/template/ui/spinner.tsx +16 -0
  88. package/template/ui/switch.tsx +33 -0
  89. package/template/ui/table.tsx +116 -0
  90. package/template/ui/tabs.tsx +89 -0
  91. package/template/ui/textarea.tsx +18 -0
  92. package/template/ui/toggle-group.tsx +81 -0
  93. package/template/ui/toggle.tsx +47 -0
  94. package/template/ui/tooltip.tsx +55 -0
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: page-authoring
3
- description: Technical reference for writing or editing open-pages pages — file contract, Tailwind via `className`, layout and responsive breakpoints, web type scale, interactivity with hooks and state, assets and fonts, plain `index.html` pages, and the self-review checklist. Consult this whenever you are about to write or modify any file under `pages/<id>/`, including from inside the `create-page` or `apply-comments` workflows, or for any ad-hoc page edit. Triggers on phrases like "edit the page", "fix the layout", "change the palette", "add a section", "make it responsive", "add a form", "investigate the page framework", "how do pages work here".
3
+ description: Technical reference for writing or editing open-pages pages — file contract, composing from the preinstalled shadcn/ui set under `ui/`, semantic theme tokens, layout and responsive breakpoints, web type scale, interactivity with hooks and forms, assets and fonts, plain `index.html` pages, and the self-review checklist. Consult this whenever you are about to write or modify any file under `pages/<id>/`, including from inside the `create-page` or `apply-comments` workflows, or for any ad-hoc page edit. Triggers on phrases like "edit the page", "fix the layout", "change the palette", "add a section", "make it responsive", "add a form", "use a dialog", "investigate the page framework", "how do pages work here".
4
4
  ---
5
5
 
6
6
  # Authoring open-pages pages
@@ -10,19 +10,37 @@ This skill is the **technical reference** for everything that happens inside `pa
10
10
  - `create-page` owns "build a new page" — it asks the user scoping questions, then delegates the *how* to this skill.
11
11
  - `apply-comments` owns "process inspector markers" — it finds markers and applies edits, but the edits themselves follow the rules here.
12
12
  - `current-page` resolves deictic references ("this page", "this element") to a concrete `pageId` + selection. Consult it **first** when the user references the current page without naming it, then come back here for how to edit it.
13
+ - `shadcn` (the vendored official skill) covers the shadcn CLI, registries, presets, and component-level docs. Consult it for "how does `<Combobox>` work" or "add a block from a registry"; this skill covers how pages in *this* workspace use those components.
13
14
  - Any ad-hoc page edit (manual tweak, one-off fix) should also consult this skill before touching the file.
14
15
 
15
16
  A page here is a **real web page**: one React component rendered into a real browser document. The workspace previews it live in an iframe, the inspector maps clicks back to source lines, and `open-pages export` turns it into a static folder you can deploy anywhere. Your job is a good web page; the runtime's job is preview, comments, and export.
16
17
 
18
+ ## What the workspace already gives you
19
+
20
+ Every workspace is a shadcn/ui project out of the box. Nothing needs installing:
21
+
22
+ | Path | What it is |
23
+ | --- | --- |
24
+ | `ui/*.tsx` | The full shadcn/ui set (61 components: accordion, alert, alert-dialog, aspect-ratio, attachment, avatar, badge, breadcrumb, bubble, button, button-group, calendar, card, carousel, chart, checkbox, collapsible, combobox, command, context-menu, dialog, direction, drawer, dropdown-menu, empty, field, form, hover-card, input, input-group, input-otp, item, kbd, label, marker, menubar, message, message-scroller, native-select, navigation-menu, pagination, popover, progress, radio-group, resizable, scroll-area, select, separator, sheet, sidebar, skeleton, slider, sonner, spinner, switch, table, tabs, textarea, toggle, toggle-group, tooltip). Import as `@/ui/<name>`. |
25
+ | `lib/utils.ts` | `cn()` for merging class names. Import as `@/lib/utils`. |
26
+ | `hooks/use-mobile.ts` | `useIsMobile()` breakpoint hook. Import as `@/hooks/use-mobile`. |
27
+ | `styles/globals.css` | Tailwind v4 entry: `@source` for `pages/ ui/ lib/ hooks/ themes/`, the `dark` variant, `:root`/`.dark` OKLCH tokens, `@theme inline`, base layer. Loaded into every page automatically. |
28
+ | `components.json` | shadcn config (style `new-york`, base `radix`, aliases `@/ui`, `@/lib/utils`, `@/hooks`, `@/components`). Activates the `shadcn` skill and CLI lookups. |
29
+ | `themes/<id>.css` | Optional token overrides a page opts into with `meta.theme` (see Themes). |
30
+
31
+ Installed deps you may import directly: `lucide-react` (icons), `radix-ui`, `class-variance-authority`, `clsx`, `tailwind-merge`, `cmdk`, `sonner`, `vaul`, `recharts`, `react-day-picker`, `date-fns`, `react-hook-form`, `zod`, `@hookform/resolvers`, `input-otp`, `embla-carousel-react`, `react-resizable-panels`, `next-themes`. Nothing else.
32
+
33
+ `npx shadcn@latest add <item>` is only for **blocks** (`dashboard-01`, `login-03`, …) or third-party registries; every core component is already under `ui/`. For lookups use `npx shadcn@latest docs <component> --json`, `view`, `search` — details in the `shadcn` skill.
34
+
17
35
  ## Topic references
18
36
 
19
37
  Details live under `references/` in this skill. **Read the relevant file before using the feature**:
20
38
 
21
39
  | Topic | Read before | File |
22
40
  | --- | --- | --- |
23
- | Layout + responsive | any multi-column layout, nav, hero, grid; anything that must work on mobile | `references/layout-and-responsive.md` |
24
- | Typography + color | picking a type scale or palette, dark backgrounds, contrast | `references/typography-and-color.md` |
25
- | Interactivity | state, forms, tabs, toggles, anything with an event handler | `references/interactivity.md` |
41
+ | Layout + responsive | any multi-column layout, nav, app shell, sidebar, grid; anything that must work on mobile | `references/layout-and-responsive.md` |
42
+ | Typography + color | picking a type scale, the token system, dark sections, contrast | `references/typography-and-color.md` |
43
+ | Interactivity | state, forms, dialogs, toasts, tabs, anything with an event handler | `references/interactivity.md` |
26
44
  | Assets + fonts | images, icons, custom fonts, Google Fonts | `references/assets-and-fonts.md` |
27
45
  | Plain HTML pages | a page authored as `index.html` instead of React | `references/html-pages.md` |
28
46
 
@@ -30,10 +48,11 @@ Details live under `references/` in this skill. **Read the relevant file before
30
48
 
31
49
  - Put the page under `pages/<kebab-case-id>/`.
32
50
  - Entry is `pages/<id>/index.tsx` (or `pages/<id>/index.html` for a plain HTML page — see `references/html-pages.md`).
33
- - Do **not** touch `package.json`, `open-pages.config.ts`, or other pages.
34
- - Do not add dependencies. Only `react`, `react-dom`, and `@autono/open-pages` (types) are available, plus browser APIs.
35
- - A page is `index.tsx` plus, optionally, `components/*.tsx`, `styles.css`, and `assets/` for its images and fonts — all inside `pages/<id>/`. Shared assets live in the root `assets/` folder and import via `@assets/...`. No `README.md`, no config files.
36
- - Style with Tailwind utilities on `className`. Tailwind is preconfigured and scans `pages/` and `themes/`; there is nothing to set up.
51
+ - A page may contain only `index.tsx`, `components/*.tsx`, `styles.css`, and `assets/`. Shared assets live in the root `assets/` folder and import via `@assets/...`. No `README.md`, no config files.
52
+ - `ui/`, `lib/`, `hooks/`, `styles/globals.css`, and `components.json` are **workspace-level and shared by every page**. Do not edit a file under `ui/` to suit one page — wrap or extend it in `pages/<id>/components/` instead (`<Button className={cn('rounded-full', …)}>`, or a `HeroButton` that renders `<Button>`). Do not edit `styles/globals.css` from a page task; token changes belong to a theme (`create-theme`).
53
+ - Do **not** touch `package.json`, `open-pages.config.ts`, or other pages. Do not add dependencies beyond the installed list above.
54
+ - **Use `@/ui/*` first.** Hand-roll an element only when no shadcn component covers it (a hero, a marketing feature grid, a footer). Buttons, inputs, cards, dialogs, tabs, tables, menus, tooltips, forms: always the `ui/` component.
55
+ - **Style with semantic tokens**, not raw palette classes: `bg-background text-foreground`, `bg-card`, `bg-primary text-primary-foreground`, `text-muted-foreground`, `border-border`, `bg-muted`, `bg-accent`, `text-destructive`, `ring-ring`, `rounded-lg`. Themes restyle every token at once; a page written in `bg-slate-900` ignores them. Raw palette utilities are for deliberate one-off brand moments only.
37
56
 
38
57
  ## File contract
39
58
 
@@ -41,6 +60,9 @@ Details live under `references/` in this skill. **Read the relevant file before
41
60
  // pages/<id>/index.tsx
42
61
  import type { PageMeta } from '@autono/open-pages';
43
62
  import { useState } from 'react';
63
+ import { Button } from '@/ui/button';
64
+ import { Card, CardContent, CardHeader, CardTitle } from '@/ui/card';
65
+ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/ui/tabs';
44
66
 
45
67
  export const meta: PageMeta = {
46
68
  title: 'Meridian — Launch',
@@ -49,100 +71,133 @@ export const meta: PageMeta = {
49
71
  };
50
72
 
51
73
  export default function Launch() {
52
- const [open, setOpen] = useState(false);
74
+ const [yearly, setYearly] = useState(false);
53
75
  return (
54
- <main className="min-h-screen bg-white text-slate-900 antialiased">
55
- {/* sections */}
76
+ <main className="min-h-screen bg-background text-foreground antialiased">
77
+ <section className="mx-auto max-w-5xl px-6 py-20">
78
+ <h1 className="text-5xl font-bold tracking-tight">Your analytics, turned into decisions</h1>
79
+ <div className="mt-8 flex gap-3">
80
+ <Button size="lg">Start free</Button>
81
+ <Button size="lg" variant="outline">See how it works</Button>
82
+ </div>
83
+ </section>
84
+ <section className="mx-auto max-w-5xl px-6 pb-20">
85
+ <Tabs defaultValue={yearly ? 'yearly' : 'monthly'} onValueChange={(v) => setYearly(v === 'yearly')}>
86
+ <TabsList>
87
+ <TabsTrigger value="monthly">Monthly</TabsTrigger>
88
+ <TabsTrigger value="yearly">Yearly</TabsTrigger>
89
+ </TabsList>
90
+ <TabsContent value="monthly">
91
+ <Card>
92
+ <CardHeader><CardTitle>Growth</CardTitle></CardHeader>
93
+ <CardContent className="text-muted-foreground">$49 / month</CardContent>
94
+ </Card>
95
+ </TabsContent>
96
+ </Tabs>
97
+ </section>
56
98
  </main>
57
99
  );
58
100
  }
59
101
  ```
60
102
 
61
- - `export default` is **one zero-prop React component** — the whole page. It owns the viewport: set the page background and text color on the root element (`min-h-screen bg-… text-…`).
103
+ - `export default` is **one zero-prop React component** — the whole page. Its root is `min-h-screen bg-background text-foreground` (add `dark` to the root's `className` for a dark page — the variant is `&:is(.dark *)`, so every token flips underneath it).
62
104
  - `meta.title` (optional) becomes the browser tab title and the workspace card label. Default is the folder name.
63
105
  - `meta.description` (optional) becomes `<meta name="description">` in the exported HTML.
64
- - `meta.theme` (optional) marks the page as built from a theme under `themes/`. The id must match a `<id>.md` basename. Omit if not derived from a registered theme.
106
+ - `meta.theme` (optional) opts the page into `themes/<id>.css` — the runtime injects that stylesheet into the preview frame and the export. The id must match a `themes/<id>.md` basename. Omit for the default neutral tokens.
65
107
  - `meta.createdAt` is an **ISO 8601 string literal** set once when the page is scaffolded — **immediately before writing the file, run `node -e "console.log(new Date().toISOString())"` via Bash and paste the exact output**. Must stay a plain string literal (the framework reads it via regex, never by evaluating the module).
66
108
  - Hooks, state, event handlers, `fetch`, `window`, client-side routing: all allowed. This is a browser. See `references/interactivity.md` for the constraints that still apply (no `window` at module top level, StrictMode double-invokes effects).
67
109
 
68
- ## Styling: Tailwind on `className`
110
+ ## Styling: tokens first, then shadcn, then utilities
69
111
 
70
- Write the HTML you already know — `main`, `header`, `nav`, `section`, `h1`–`h3`, `p`, `ul`/`li`, `button`, `a`, `form`, `table` — styled with **Tailwind v4 utilities via `className`**:
112
+ 1. **Tokens.** Surfaces and text use the semantic classes from `styles/globals.css`. Pairs go together: `bg-primary text-primary-foreground`, `bg-card text-card-foreground`, `bg-muted text-muted-foreground`. Borders are `border-border`; focus rings `ring-ring`; radius `rounded-md`/`rounded-lg`/`rounded-xl` (all scale from `--radius`).
113
+ 2. **shadcn components** carry their own token styling and variants (`<Button variant="outline" size="sm">`, `<Badge variant="secondary">`). Reach for a variant before adding classes; add classes via `className` (merged with `cn`) for layout — width, margin, grid placement — not to repaint the component.
114
+ 3. **Tailwind utilities** on `className` for everything structural: containers, grids, spacing, type sizes, responsive variants. Arbitrary values are fine (`max-w-[72ch]`). Mobile-first: unprefixed utilities are the phone layout, `sm:`/`md:`/`lg:` layer on top.
71
115
 
72
116
  ```tsx
73
117
  <section className="mx-auto max-w-5xl px-6 py-20">
74
- <h2 className="text-3xl font-bold tracking-tight">Pricing</h2>
75
- <p className="mt-3 max-w-xl text-lg text-slate-600">Simple plans that grow with you.</p>
118
+ <Badge variant="secondary">Pricing</Badge>
119
+ <h2 className="mt-4 text-3xl font-bold tracking-tight">Simple plans</h2>
120
+ <p className="mt-3 max-w-xl text-lg text-muted-foreground">Grow at your own pace.</p>
121
+ <div className="mt-8 grid gap-6 sm:grid-cols-3">
122
+ {plans.map((p) => (
123
+ <Card key={p.name} className={cn(p.featured && 'border-primary')}>…</Card>
124
+ ))}
125
+ </div>
76
126
  </section>
77
127
  ```
78
128
 
79
- - `className`, never a `tw` prop. Arbitrary values are fine (`text-[15px]`, `bg-[#0b0b10]`, `max-w-[72ch]`).
80
- - Responsive variants are the default tool: `grid sm:grid-cols-2 lg:grid-cols-3`. Mobile-first — unprefixed utilities are the mobile layout.
81
- - State variants for interactive elements: `hover:`, `focus-visible:`, `disabled:`, `aria-pressed:`.
82
- - Use inline `style={{ … }}` only for values Tailwind cannot express (computed positions, CSS variables from data).
83
- - A page may import its own stylesheet (`import './styles.css'`) for keyframes, complex selectors, or a font `@import`. Keep it small; utilities first.
84
- - Preflight (Tailwind's reset) is applied inside the page, so headings and buttons start unstyled — set sizes and weights explicitly.
129
+ - `className`, never a `tw` prop. Build class strings literally; `cn()` for conditionals — never `` `text-${size}` ``.
130
+ - Icons: `lucide-react` (`<ArrowRight className="size-4" />`), sized with `size-*`, `aria-hidden` when decorative.
131
+ - `style={{ … }}` only for values Tailwind cannot express (computed positions, CSS variables from data).
132
+ - A page may import its own stylesheet (`import './styles.css'`) for keyframes, complex selectors, or a font `@import`. Keep it small; never redefine the tokens there — that is a theme's job.
133
+ - Preflight plus the shadcn base layer are applied inside the page: headings start unstyled (set sizes and weights explicitly); `body` already carries `bg-background text-foreground`.
85
134
 
86
- ## Web type scale and color
135
+ ## Web type scale
87
136
 
88
137
  | Element | Size |
89
138
  | --- | --- |
90
139
  | Hero heading | 48–72px (`text-5xl`–`text-7xl`), tight leading + tracking |
91
140
  | Section heading | 24–36px (`text-2xl`–`text-4xl`) |
92
141
  | Body | 16–18px (`text-base`–`text-lg`), `leading-relaxed` |
93
- | Secondary / captions | 13–14px (`text-sm`), muted color |
142
+ | Secondary / captions | 13–14px (`text-sm text-muted-foreground`) |
94
143
  | Labels, eyebrows | 11–12px (`text-xs`), `uppercase tracking-widest` |
95
144
 
96
- - One page = one palette: a background, a text color, a muted text color, one accent, one border tint. Hold it for the whole page.
97
- - Contrast: body text must pass 4.5:1 against its background; muted text on dark backgrounds is `text-white/60`, not `text-white/30`.
98
- - Details in `references/typography-and-color.md`.
145
+ - Contrast: body text must pass 4.5:1. The tokens pass by construction; if you reach for `text-foreground/40`, you are below the floor.
146
+ - Details and the token → OKLCH map in `references/typography-and-color.md`.
99
147
 
100
148
  ## Data rows vs designed repeats
101
149
 
102
150
  - **Repeated data belongs in a `.map` over a data array** — pricing tiers, feature lists, testimonials pulled from a typed const at the top of the file; keep the row JSX in the map body. A comment on any row means "this row template".
103
151
  - **Designed repeats that differ (hero vs. secondary CTA, three distinct feature panels with custom art) are explicit instances** of a small helper component defined in the same file or under `components/`. The inspector targets source JSX; explicit instances give each block its own address, so "make the middle one green" is one edit, not three.
152
+ - The inspector tags components as well as host elements: shadcn components spread their props onto their root, so a click on a rendered `<Button>` resolves to the `<Button>` line in *your* page, not to `ui/button.tsx`.
104
153
 
105
154
  ## Editing an existing page
106
155
 
107
156
  Locate the section first instead of reading the whole file:
108
157
 
109
158
  ```bash
110
- grep -n '<section\|<h[12]\|<header\|<footer' pages/<id>/index.tsx
159
+ grep -n '<section\|<h[12]\|<header\|<footer\|<Card\b\|<Dialog\b' pages/<id>/index.tsx
111
160
  ```
112
161
 
113
- Landmarks and headings anchor sections; read the target range with `offset` + `limit`. Read the whole file when auditing palette or restructuring.
162
+ Landmarks, headings, and top-level shadcn wrappers anchor sections; read the target range with `offset` + `limit`. Read the whole file when auditing tokens or restructuring.
114
163
 
115
164
  ## Themes
116
165
 
117
- If `themes/<id>.md` exists and the page is meant to follow it, **the theme file overrides the defaults in this skill** — its palette, typography, and fixed components are authoritative. Read the theme before applying anything else here. Themes are produced by the `create-theme` skill.
166
+ A theme is `themes/<id>.md` (direction and component notes) + `themes/<id>.css` (token overrides) + `themes/<id>.demo.tsx` (a demo page). If the page is meant to follow one, **read `themes/<id>.md` first — its guidance overrides the defaults in this skill** — and set `meta.theme: '<id>'`; the runtime injects the CSS. Do not paste token values into the page; the whole point is that the page stays token-based and the theme repaints it. Themes are produced by the `create-theme` skill.
118
167
 
119
168
  ## Runtime behavior you get for free
120
169
 
121
170
  - Home lists every folder under `pages/`; cards show a live, scaled-down preview of the real page.
122
171
  - The page view at `http://localhost:5173/p/<id>` renders the page in an iframe with **Desktop / Tablet (820px) / Mobile (390px)** viewport toggles, a reload button, and **Open** (the page by itself in a new tab).
123
- - **Inspect mode** (toolbar button or `i`): the user clicks any element, sees its tag and source line, and can leave a comment that lands in your source as an `@page-comment` marker (see `apply-comments`). The current selection is always in `node_modules/.open-pages/current.json` (see `current-page`).
172
+ - **Inspect mode** (toolbar button or `i`): the user clicks any element or component, sees its tag and source line, and can leave a comment that lands in your source as an `@page-comment` marker (see `apply-comments`). The current selection is always in `node_modules/.open-pages/current.json` (see `current-page`).
124
173
  - Hot reload: save any file under `pages/<id>/` and the preview updates in place.
125
- - `open-pages export <id>` builds `export/<id>/` — `index.html` plus hashed assets with relative URLs. Drop the folder on Netlify, Vercel, Cloudflare Pages, GitHub Pages, or S3.
174
+ - `open-pages export <id>` builds `export/<id>/` — `index.html` plus hashed assets with relative URLs, bundling only the `ui/` components the page imports. Drop the folder on Netlify, Vercel, Cloudflare Pages, GitHub Pages, or S3.
126
175
 
127
176
  ## Self-review before finishing
128
177
 
129
- - [ ] `pages/<id>/index.tsx` default-exports **one** component; `meta` has `title` + fresh `createdAt` literal.
130
- - [ ] Root element sets `min-h-screen`, background, and text color; the page has a `<main>` landmark.
178
+ - [ ] `pages/<id>/index.tsx` default-exports **one** component; `meta` has `title` + fresh `createdAt` literal (+ `theme` if built from one).
179
+ - [ ] Root element is `min-h-screen bg-background text-foreground`; the page has a `<main>` landmark.
180
+ - [ ] Every button, input, card, dialog, tab, table, menu, tooltip, or form is the `@/ui/*` component, not a hand-rolled lookalike.
181
+ - [ ] Colors are semantic tokens; raw palette classes appear only where a one-off brand moment was intended.
182
+ - [ ] Nothing under `ui/`, `lib/`, `hooks/`, or `styles/` was edited.
131
183
  - [ ] Preview it at `http://localhost:5173/p/<id>` (or ask the user to). No error banner, no console errors.
132
184
  - [ ] Check the **Mobile** viewport: no horizontal overflow, nav collapses or wraps, grids stack, text sizes step down.
133
- - [ ] Every `className` is Tailwind v4 the scanner will see (literal strings, no runtime-concatenated utility names).
134
- - [ ] Interactive elements are real `<button>`/`<a>`/`<input>` elements with visible `focus-visible:` styles and labels; images have `alt`.
135
- - [ ] One coherent palette and type scale across the page; contrast holds on dark sections.
185
+ - [ ] Every `className` is Tailwind v4 the scanner will see (literal strings, `cn()` for conditionals).
186
+ - [ ] Interactive elements are real `<Button>`/`<a>`/`<Input>` with visible focus styles and labels; images have `alt`.
187
+ - [ ] One coherent type scale across the page; contrast holds on dark sections.
136
188
  - [ ] Designed repeats are explicit component instances; data lists are a `.map` over a typed const.
137
189
  - [ ] No `window`/`document` access at module top level; effects clean up.
138
190
  - [ ] Nothing outside `pages/<id>/` was edited.
139
191
 
140
192
  ## Anti-patterns
141
193
 
194
+ - ❌ A hand-rolled `<button className="rounded-md bg-slate-900 px-4 …">` when `<Button>` exists. Same for inputs, cards, dialogs, dropdowns, tabs, tables.
195
+ - ❌ Repainting a component with raw colors (`<Button className="bg-blue-600">`). Use a variant, or change the theme.
196
+ - ❌ Editing `ui/button.tsx` because one page wants rounder buttons. Wrap it under `pages/<id>/components/`.
142
197
  - ❌ `tw` props, `pageOptions`, fixed-size "page" `<div>`s, or any document/slide thinking. This is a scrolling web page.
143
198
  - ❌ Desktop-only layouts: absolute pixel positioning, fixed widths on the root, `grid-cols-3` with no `sm:` fallback.
144
- - ❌ Building class names at runtime (`` `text-${size}` ``) — Tailwind only generates utilities it can read literally. Map to full class strings instead.
145
- - ❌ `<div onClick>` where a `<button>` belongs; `<a>` without `href`; icon buttons without `aria-label`.
199
+ - ❌ Building class names at runtime (`` `text-${size}` ``) — Tailwind only generates utilities it can read literally.
200
+ - ❌ `<div onClick>` where a `<Button>` belongs; `<a>` without `href`; icon buttons without `aria-label`.
146
201
  - ❌ Tiny typography (11px body). Web body is 16px+.
147
- - ❌ Global CSS resets or `body {}` rules in `styles.css` that fight Preflight — style the root element instead.
148
- - ❌ Installing packages, editing `package.json`/config/other pages, adding a `README.md` to the page folder.
202
+ - ❌ Global CSS resets, `body {}` rules, or `:root { --primary: … }` in the page's `styles.css`. Tokens belong to themes.
203
+ - ❌ Installing packages, `npx shadcn add` for a component already in `ui/`, editing `package.json`/config/other pages, adding a `README.md` to the page folder.
@@ -1,50 +1,95 @@
1
1
  # Interactivity
2
2
 
3
- Pages are real React 18 components running in the browser, wrapped in
3
+ Pages are real React 19 components running in the browser, wrapped in
4
4
  `StrictMode`. Everything React can do is available; the constraints below
5
- keep pages preview-safe, export-safe, and inspectable.
5
+ keep pages preview-safe, export-safe, and inspectable. Interactive UI comes
6
+ from `ui/` first — `Tabs`, `Accordion`, `Dialog`, `Sheet`, `Drawer`,
7
+ `DropdownMenu`, `Popover`, `Tooltip`, `Switch`, `Select`, `Combobox`,
8
+ `Command`, `Slider`, `Toggle`, `Collapsible` all ship with keyboard handling
9
+ and ARIA wired. Hand-rolled state is for what they do not cover.
6
10
 
7
11
  ## State
8
12
 
9
13
  ```tsx
10
- const [tab, setTab] = useState<'monthly' | 'yearly'>('monthly');
11
-
12
- <button
13
- type="button"
14
- onClick={() => setTab('yearly')}
15
- aria-pressed={tab === 'yearly'}
16
- className={tab === 'yearly' ? 'bg-slate-900 text-white' : 'text-slate-600'}
17
- >
18
- Yearly
19
- </button>
14
+ const [billing, setBilling] = useState<'monthly' | 'yearly'>('monthly');
15
+
16
+ <Tabs value={billing} onValueChange={(v) => setBilling(v as typeof billing)}>
17
+ <TabsList>
18
+ <TabsTrigger value="monthly">Monthly</TabsTrigger>
19
+ <TabsTrigger value="yearly">Yearly</TabsTrigger>
20
+ </TabsList>
21
+ </Tabs>
20
22
  ```
21
23
 
22
24
  - Keep state local and small: tabs, toggles, accordions, filters, form values.
23
25
  - Derive, do not duplicate: compute filtered lists with `useMemo` (or inline)
24
26
  from the source array and the filter state.
25
- - Toggle/tab buttons carry `aria-pressed` or `role="tablist"` semantics;
26
- disclosure buttons carry `aria-expanded` and `aria-controls`.
27
+ - Controlled vs. uncontrolled: shadcn components accept `defaultValue` for
28
+ fire-and-forget UI and `value` + `onValueChange` (or `open` +
29
+ `onOpenChange`) when the page needs the value.
30
+ - When you must hand-roll a toggle, it is a `<Button variant="outline"
31
+ aria-pressed={on}>`; a disclosure carries `aria-expanded` and
32
+ `aria-controls` — or just use `<Collapsible>`.
27
33
 
28
34
  ## Forms
29
35
 
30
- - Real `<form>` with `onSubmit={(e) => { e.preventDefault(); … }}`.
31
- - Every input has a `<label htmlFor>` (or `aria-label`), a `name`, and a
32
- sensible `type`/`inputMode`/`autoComplete`.
36
+ Simple forms: `<form onSubmit>` with `@/ui/field`, `@/ui/input`, `@/ui/label`,
37
+ `@/ui/select`, `@/ui/checkbox`, `@/ui/textarea`, `@/ui/button`. Validated
38
+ forms: `react-hook-form` + `zod` through `@/ui/form`:
39
+
40
+ ```tsx
41
+ import { zodResolver } from '@hookform/resolvers/zod';
42
+ import { useForm } from 'react-hook-form';
43
+ import { z } from 'zod';
44
+ import { Button } from '@/ui/button';
45
+ import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/ui/form';
46
+ import { Input } from '@/ui/input';
47
+
48
+ const schema = z.object({ email: z.string().email('Enter a valid work email') });
49
+
50
+ const form = useForm<z.infer<typeof schema>>({ resolver: zodResolver(schema), defaultValues: { email: '' } });
51
+
52
+ <Form {...form}>
53
+ <form onSubmit={form.handleSubmit((values) => setSubmitted(values))} className="flex flex-col gap-4">
54
+ <FormField control={form.control} name="email" render={({ field }) => (
55
+ <FormItem>
56
+ <FormLabel>Work email</FormLabel>
57
+ <FormControl><Input type="email" autoComplete="email" {...field} /></FormControl>
58
+ <FormMessage />
59
+ </FormItem>
60
+ )} />
61
+ <Button type="submit">Join the waitlist</Button>
62
+ </form>
63
+ </Form>
64
+ ```
65
+
66
+ - Every input has a label (`<Label htmlFor>` or `<FormLabel>`), a `name`, and
67
+ a sensible `type`/`inputMode`/`autoComplete`.
33
68
  - No backend exists in the workspace or the export. Either mirror submitted
34
69
  values into local state ("Thanks, we'll be in touch"), or point the form at
35
70
  a URL the user supplies (`action="https://formspree.io/…" method="post"`).
36
71
  Never invent an endpoint.
37
72
 
73
+ ## Dialogs, sheets, toasts
74
+
75
+ - Modal confirmation: `<Dialog>` / `<AlertDialog>` with a `<DialogTrigger asChild><Button>…`.
76
+ Side panels: `<Sheet>`; mobile bottom panels: `<Drawer>`.
77
+ - Toasts: render `<Toaster />` from `@/ui/sonner` once at the root of the
78
+ page, then `toast('Saved')` / `toast.success(…)` from `sonner` in handlers.
79
+ - Menus: `<DropdownMenu>` for actions, `<Select>` for a value, `<Combobox>`
80
+ / `<Command>` for searchable lists.
81
+
38
82
  ## Network and browser APIs
39
83
 
40
84
  - `fetch`, `localStorage`, `IntersectionObserver`, `matchMedia` are fine —
41
- inside effects or handlers, with loading and error states.
85
+ inside effects or handlers, with loading (`<Skeleton>`, `<Spinner>`) and
86
+ error (`<Alert variant="destructive">`) states.
42
87
  - Assume the exported page may run from `file://` or a static host with no
43
88
  API: gate network features behind a URL the user supplied, and render
44
- something meaningful without it.
89
+ something meaningful without it (`<Empty>`).
45
90
  - Access `window`/`document` only inside `useEffect` or event handlers.
46
91
  Module-top-level `window.innerWidth` breaks the module import and the
47
- export build.
92
+ export build. For breakpoints use `useIsMobile()` from `@/hooks/use-mobile`.
48
93
 
49
94
  ## Effects and StrictMode
50
95
 
@@ -57,21 +102,24 @@ const [tab, setTab] = useState<'monthly' | 'yearly'>('monthly');
57
102
 
58
103
  - Content must be visible with JavaScript disabled or before hydration: no
59
104
  empty shells that only fill from an effect.
60
- - Hover-only affordances need a keyboard/touch equivalent.
105
+ - Hover-only affordances need a keyboard/touch equivalent (`<Tooltip>` and
106
+ `<HoverCard>` already handle focus).
61
107
  - Animations: `motion-safe:transition-*`; respect `prefers-reduced-motion`.
62
108
 
63
109
  ## Routing inside a page
64
110
 
65
111
  - Simple multi-view pages: a `view` state and conditional rendering, with
66
- `<a href="#section">` anchors for in-page navigation.
112
+ `<a href="#section">` anchors for in-page navigation and `<NavigationMenu>`
113
+ or `<Breadcrumb>` for chrome.
67
114
  - Hash routing is acceptable when the user asks for "pages" inside one export
68
115
  (`window.location.hash`, read in an effect). Path-based routing needs a
69
116
  host with rewrites — mention this to the user before choosing it.
70
117
 
71
118
  ## Anti-patterns
72
119
 
120
+ - ❌ A `useState` + two `<div>`s reimplementing `<Tabs>`, `<Accordion>`, or `<Dialog>`.
73
121
  - ❌ `useEffect` to compute something derivable from props/state.
74
122
  - ❌ Fetching from an endpoint nobody set up.
75
123
  - ❌ `document.querySelector` to mutate the DOM React owns.
76
124
  - ❌ Global listeners without cleanup; timers that outlive the component.
77
- - ❌ Interactive `<div>`s. Use `<button>`, `<a>`, `<input>`, `<select>`.
125
+ - ❌ Interactive `<div>`s. Use `<Button>`, `<a>`, `<Input>`, `<Select>`.
@@ -4,7 +4,9 @@ The viewer previews every page at three widths — **Desktop** (the full
4
4
  viewport), **Tablet** (820px), and **Mobile** (390px). A page is done when all
5
5
  three look intentional. Tailwind is mobile-first: unprefixed utilities are the
6
6
  phone layout; `sm:` (640px), `md:` (768px), `lg:` (1024px), `xl:` (1280px)
7
- layer larger layouts on top.
7
+ layer larger layouts on top. `useIsMobile()` from `@/hooks/use-mobile` is
8
+ available when JS must know (swap a `<Sheet>` for a `<Drawer>`, collapse a
9
+ table into cards).
8
10
 
9
11
  ## Containers
10
12
 
@@ -17,14 +19,19 @@ layer larger layouts on top.
17
19
  - Reading columns: `max-w-2xl` / `max-w-[65ch]` for prose. Marketing grids:
18
20
  `max-w-5xl`–`max-w-7xl`.
19
21
  - Vertical rhythm: `py-16` between sections on mobile, `sm:py-24` on desktop.
20
- Sections separate by space or a hairline (`border-t border-slate-200`), not
21
- both.
22
+ Sections separate by space or a hairline (`border-t border-border`, or a
23
+ `<Separator />`), not both.
22
24
 
23
25
  ## Grids and stacks
24
26
 
25
27
  ```tsx
26
28
  <div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
27
- {features.map((f) => <FeatureCard key={f.title} {...f} />)}
29
+ {features.map((f) => (
30
+ <Card key={f.title}>
31
+ <CardHeader><CardTitle>{f.title}</CardTitle></CardHeader>
32
+ <CardContent className="text-muted-foreground">{f.body}</CardContent>
33
+ </Card>
34
+ ))}
28
35
  </div>
29
36
  ```
30
37
 
@@ -32,35 +39,48 @@ layer larger layouts on top.
32
39
  - `flex flex-col gap-4 sm:flex-row sm:items-center` for header rows that must
33
40
  stack on phones.
34
41
  - Use `gap-*`, not margins on children, for spacing inside grids and flex rows.
42
+ - Tabular data is `<Table>` from `@/ui/table` inside an `overflow-x-auto`
43
+ wrapper; on mobile either let it scroll or render a card list.
35
44
 
36
45
  ## Navigation
37
46
 
38
- - Desktop nav links: `hidden sm:flex gap-8`. On mobile either show a compact
39
- set of links, or a `<button aria-expanded>` that toggles a stacked menu with
40
- `useState`. Never let a nav overflow horizontally.
41
- - Sticky headers: `sticky top-0 z-10 bg-white/90 backdrop-blur` — give them a
42
- background so content does not bleed through.
47
+ - Marketing header: wordmark + `<NavigationMenu>` (or plain links) with
48
+ `hidden sm:flex`, one `<Button>` CTA. On mobile, a `<Sheet side="left">`
49
+ opened by an icon `<Button variant="ghost" size="icon" aria-label="Menu">`
50
+ holding the stacked links. Never let a nav overflow horizontally.
51
+ - Sticky headers: `sticky top-0 z-10 bg-background/90 backdrop-blur border-b border-border`.
52
+ - Sub-navigation and hierarchy: `<Breadcrumb>`, `<Tabs>`, `<Pagination>`.
43
53
 
44
54
  ## Hero
45
55
 
46
56
  - Heading `text-4xl sm:text-6xl lg:text-7xl`, `leading-[1.05] tracking-tight`.
47
57
  - Constrain the heading (`max-w-3xl`) and the lede (`max-w-xl`) independently.
48
- - CTA row: `flex flex-col gap-3 sm:flex-row` so buttons stack on mobile.
58
+ - CTA row: `flex flex-col gap-3 sm:flex-row` with `<Button size="lg">` and
59
+ `<Button size="lg" variant="outline">` so buttons stack on mobile.
49
60
 
50
- ## Full-viewport pages (dashboards, app UIs)
61
+ ## App shells (dashboards, tools, docs)
51
62
 
52
63
  - Root: `min-h-screen` (not `h-screen`) so content can scroll.
53
- - Sidebars: `hidden lg:block w-64` plus a mobile alternative (top tabs or a
54
- toggle). Content area: `min-w-0 flex-1` so tables and code blocks can shrink.
55
- - Wide content (tables, charts) scrolls inside its own `overflow-x-auto`
56
- wrapper; the page body must never scroll horizontally.
64
+ - Sidebar layouts use `@/ui/sidebar`: wrap the page in `<SidebarProvider>`,
65
+ render `<Sidebar>` + `<SidebarInset>`; it collapses to a sheet on mobile by
66
+ itself and gives you `<SidebarTrigger />` for the topbar. Do not hand-roll
67
+ a `hidden lg:block w-64` aside when this exists.
68
+ - Split panes (editor + preview, list + detail): `@/ui/resizable`
69
+ (`ResizablePanelGroup` / `ResizablePanel` / `ResizableHandle`); stack them
70
+ (`direction="vertical"`) or render one pane at a time on mobile via
71
+ `useIsMobile()`.
72
+ - Content area: `min-w-0 flex-1` so tables and code blocks can shrink. Wide
73
+ content scrolls inside its own `overflow-x-auto` wrapper (or
74
+ `<ScrollArea>`); the page body must never scroll horizontally.
75
+ - Loading and empty states: `<Skeleton>` while data arrives, `<Empty>` when
76
+ there is none.
57
77
 
58
78
  ## Checklist
59
79
 
60
- - [ ] Mobile: no horizontal scrollbar, every grid stacks, headings shrink.
80
+ - [ ] Mobile: no horizontal scrollbar, every grid stacks, headings shrink, nav collapses into a sheet or wraps.
61
81
  - [ ] Tablet: two-column layouts where three would cramp.
62
82
  - [ ] Desktop: content is capped by a `max-w-*`, not stretched edge to edge.
63
- - [ ] Touch targets ≥ 40px tall on mobile (`py-2.5` on buttons and links).
83
+ - [ ] Touch targets ≥ 40px tall on mobile (`<Button size="lg">` or `py-2.5` on links).
64
84
 
65
85
  ## Anti-patterns
66
86
 
@@ -69,3 +89,4 @@ layer larger layouts on top.
69
89
  - ❌ `h-screen` on scrolling pages; `overflow-hidden` on `body`/root.
70
90
  - ❌ Grids with no mobile fallback (`grid-cols-4` alone).
71
91
  - ❌ Text sized only for desktop (`text-7xl` with no smaller base).
92
+ - ❌ A hand-built sidebar or drawer when `@/ui/sidebar`, `<Sheet>`, or `<Drawer>` fits.