plutonium 0.51.0 → 0.52.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 (120) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/plutonium-app/SKILL.md +2 -0
  3. data/.claude/skills/plutonium-auth/SKILL.md +6 -4
  4. data/.claude/skills/plutonium-behavior/SKILL.md +1 -1
  5. data/.claude/skills/plutonium-tenancy/SKILL.md +25 -6
  6. data/.claude/skills/plutonium-testing/SKILL.md +3 -1
  7. data/.claude/skills/plutonium-ui/SKILL.md +3 -3
  8. data/CHANGELOG.md +17 -0
  9. data/app/assets/plutonium.css +1 -1
  10. data/app/assets/plutonium.js +1 -0
  11. data/app/assets/plutonium.js.map +3 -3
  12. data/app/assets/plutonium.min.js +1 -1
  13. data/app/assets/plutonium.min.js.map +3 -3
  14. data/docs/.vitepress/config.ts +1 -2
  15. data/docs/.vitepress/theme/components/HomeAudienceSplit.vue +53 -0
  16. data/docs/.vitepress/theme/components/HomeCta.vue +108 -0
  17. data/docs/.vitepress/theme/components/HomeHero.vue +70 -0
  18. data/docs/.vitepress/theme/components/HomeInTheBox.vue +74 -0
  19. data/docs/.vitepress/theme/components/HomePillars.vue +42 -0
  20. data/docs/.vitepress/theme/components/HomeStopWriting.vue +49 -0
  21. data/docs/.vitepress/theme/components/HomeWalkthrough.vue +111 -0
  22. data/docs/.vitepress/theme/components/SectionLanding.vue +115 -0
  23. data/docs/.vitepress/theme/custom.css +144 -0
  24. data/docs/.vitepress/theme/index.ts +58 -1
  25. data/docs/getting-started/index.md +33 -50
  26. data/docs/getting-started/tutorial/02-first-resource.md +17 -8
  27. data/docs/getting-started/tutorial/03-authentication.md +31 -23
  28. data/docs/getting-started/tutorial/05-custom-actions.md +9 -4
  29. data/docs/getting-started/tutorial/06-nested-resources.md +7 -1
  30. data/docs/getting-started/tutorial/07-author-portal.md +8 -0
  31. data/docs/getting-started/tutorial/08-customizing-ui.md +4 -0
  32. data/docs/guides/authentication.md +10 -5
  33. data/docs/guides/authorization.md +3 -3
  34. data/docs/guides/creating-packages.md +8 -11
  35. data/docs/guides/custom-actions.md +6 -1
  36. data/docs/guides/customizing-ui.md +258 -0
  37. data/docs/guides/index.md +49 -32
  38. data/docs/guides/multi-tenancy.md +10 -2
  39. data/docs/guides/nested-resources.md +69 -0
  40. data/docs/guides/search-filtering.md +6 -0
  41. data/docs/guides/testing.md +5 -1
  42. data/docs/guides/theming.md +13 -0
  43. data/docs/guides/user-invites.md +10 -4
  44. data/docs/guides/user-profile.md +8 -0
  45. data/docs/index.md +10 -219
  46. data/docs/public/asciinema/home-scaffold.cast +305 -0
  47. data/docs/public/images/guides/custom-actions-bulk.png +0 -0
  48. data/docs/public/images/guides/multi-tenancy-dashboard.png +0 -0
  49. data/docs/public/images/guides/multi-tenancy-welcome.png +0 -0
  50. data/docs/public/images/guides/nested-inputs.png +0 -0
  51. data/docs/public/images/guides/nested-resources-tab.png +0 -0
  52. data/docs/public/images/guides/search-filtering-index.png +0 -0
  53. data/docs/public/images/guides/search-filtering-panel.png +0 -0
  54. data/docs/public/images/guides/theming-after.png +0 -0
  55. data/docs/public/images/guides/theming-before.png +0 -0
  56. data/docs/public/images/guides/user-invites-landing.png +0 -0
  57. data/docs/public/images/guides/user-profile-edit.png +0 -0
  58. data/docs/public/images/guides/user-profile-show.png +0 -0
  59. data/docs/public/images/home-index.png +0 -0
  60. data/docs/public/images/home-new.png +0 -0
  61. data/docs/public/images/home-show.png +0 -0
  62. data/docs/public/images/tutorial/02-empty-index.png +0 -0
  63. data/docs/public/images/tutorial/02-index-with-posts.png +0 -0
  64. data/docs/public/images/tutorial/02-new-form-modal.png +0 -0
  65. data/docs/public/images/tutorial/02-new-form.png +0 -0
  66. data/docs/public/images/tutorial/03-create-account.png +0 -0
  67. data/docs/public/images/tutorial/03-login.png +0 -0
  68. data/docs/public/images/tutorial/04-admin-index.png +0 -0
  69. data/docs/public/images/tutorial/05-actions-menu.png +0 -0
  70. data/docs/public/images/tutorial/05-row-actions.png +0 -0
  71. data/docs/public/images/tutorial/06-comments-tab.png +0 -0
  72. data/docs/public/images/tutorial/06-post-with-comments.png +0 -0
  73. data/docs/public/images/tutorial/07-author-dashboard.png +0 -0
  74. data/docs/public/images/tutorial/07-author-portal.png +0 -0
  75. data/docs/public/images/tutorial/08-customized-index.png +0 -0
  76. data/docs/reference/app/generators.md +4 -4
  77. data/docs/reference/auth/accounts.md +6 -7
  78. data/docs/reference/auth/index.md +1 -1
  79. data/docs/reference/behavior/policies.md +1 -1
  80. data/docs/reference/index.md +67 -55
  81. data/docs/reference/resource/definition.md +1 -1
  82. data/docs/reference/tenancy/entity-scoping.md +8 -1
  83. data/docs/reference/tenancy/index.md +1 -1
  84. data/docs/reference/tenancy/invites.md +12 -5
  85. data/docs/reference/ui/tables.md +8 -4
  86. data/docs/superpowers/plans/2026-05-15-public-pages-overhaul.md +1648 -0
  87. data/docs/superpowers/plans/2026-05-15-public-pages-overhaul.md.tasks.json +109 -0
  88. data/docs/superpowers/specs/2026-05-15-public-pages-overhaul-design.md +263 -0
  89. data/gemfiles/rails_7.gemfile.lock +1 -1
  90. data/gemfiles/rails_8.0.gemfile.lock +1 -1
  91. data/gemfiles/rails_8.1.gemfile.lock +1 -1
  92. data/lib/generators/pu/core/assets/assets_generator.rb +10 -0
  93. data/lib/generators/pu/invites/install_generator.rb +44 -0
  94. data/lib/generators/pu/invites/templates/packages/invites/app/controllers/invites/user_invitations_controller.rb.tt +1 -0
  95. data/lib/generators/pu/profile/conn_generator.rb +2 -2
  96. data/lib/generators/pu/res/conn/conn_generator.rb +33 -6
  97. data/lib/generators/pu/res/model/templates/model.rb.tt +4 -0
  98. data/lib/generators/pu/rodauth/account_generator.rb +2 -1
  99. data/lib/generators/pu/rodauth/admin_generator.rb +0 -2
  100. data/lib/generators/pu/rodauth/migration_generator.rb +0 -2
  101. data/lib/generators/pu/rodauth/views_generator.rb +0 -2
  102. data/lib/generators/pu/saas/membership/USAGE +4 -1
  103. data/lib/generators/pu/saas/setup_generator.rb +16 -4
  104. data/lib/generators/pu/saas/welcome/templates/app/controllers/welcome_controller.rb.tt +1 -1
  105. data/lib/plutonium/helpers/turbo_helper.rb +19 -0
  106. data/lib/plutonium/resource/controllers/crud_actions.rb +4 -4
  107. data/lib/plutonium/resource/controllers/interactive_actions.rb +3 -3
  108. data/lib/plutonium/ui/component/methods.rb +1 -0
  109. data/lib/plutonium/ui/form/base.rb +17 -1
  110. data/lib/plutonium/ui/form/components/secure_association.rb +11 -6
  111. data/lib/plutonium/ui/form/interaction.rb +1 -1
  112. data/lib/plutonium/ui/form/theme.rb +1 -1
  113. data/lib/plutonium/ui/page/edit.rb +1 -1
  114. data/lib/plutonium/ui/page/new.rb +1 -1
  115. data/lib/plutonium/ui/table/components/filter_form.rb +12 -4
  116. data/lib/plutonium/version.rb +1 -1
  117. data/package.json +4 -1
  118. data/src/js/controllers/form_controller.js +5 -4
  119. data/yarn.lock +108 -1
  120. metadata +45 -3
@@ -0,0 +1,1648 @@
1
+ # Public Pages Overhaul — Implementation Plan
2
+
3
+ > **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers-extended-cc:subagent-driven-development (recommended) or superpowers-extended-cc:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
4
+
5
+ **Goal:** Overhaul the four public landing pages of the Plutonium docs site (home + Getting Started + Guides + Reference) per the locked design, and produce the supporting demo-app screenshots and asciinema asset.
6
+
7
+ **Architecture:** VitePress site with custom Vue components and shared CSS tokens. Markdown landing pages composed of small, focused Vue components (one per home section, one shared section-landing component) registered through `docs/.vitepress/theme/index.ts`. CSS variables live in `docs/.vitepress/theme/custom.css`. Visual assets (screenshots + asciinema) captured from a fresh scaffolded Rails demo app and stored under `docs/public/`.
8
+
9
+ **Tech Stack:** VitePress 1.x · Vue 3 (SFCs) · TailwindCSS-style design tokens via plain CSS variables · asciinema-player (CDN) · Plutonium gem (for demo-app scaffolding) · macOS native screenshot tools.
10
+
11
+ **User Verification:** YES — the user must visually approve the rendered home page and each section landing in a local `yarn docs:dev` browser session before the work is considered complete. A dedicated verification task at the end of the plan captures this.
12
+
13
+ ---
14
+
15
+ ## Spec Reference
16
+
17
+ `docs/superpowers/specs/2026-05-15-public-pages-overhaul-design.md` — re-read it before starting any task.
18
+
19
+ ## File Structure
20
+
21
+ Each home section becomes one Vue SFC. Each component is small (~40–120 lines) so it's easy to read, edit, and visually iterate. Shared visual primitives (button, terminal frame, eyebrow) live in CSS — not Vue — so they're reusable in markdown without import overhead.
22
+
23
+ ```
24
+ docs/
25
+ ├── .vitepress/
26
+ │ └── theme/
27
+ │ ├── index.ts # MODIFY — register new components
28
+ │ ├── custom.css # MODIFY — append shared design tokens & primitives
29
+ │ └── components/ # CREATE this dir
30
+ │ ├── HomeHero.vue
31
+ │ ├── HomeStopWriting.vue
32
+ │ ├── HomePillars.vue
33
+ │ ├── HomeWalkthrough.vue
34
+ │ ├── HomeAudienceSplit.vue
35
+ │ ├── HomeInTheBox.vue
36
+ │ ├── HomeCta.vue # owns the template-toggle pill state
37
+ │ └── SectionLanding.vue # shared rail+sidebar layout for the 3 section landings
38
+ ├── index.md # REWRITE
39
+ ├── getting-started/index.md # REWRITE
40
+ ├── guides/index.md # REWRITE
41
+ ├── reference/index.md # REWRITE
42
+ └── public/
43
+ ├── images/
44
+ │ ├── home-portal.png # asset
45
+ │ ├── home-index.png # asset
46
+ │ └── home-form.png # asset
47
+ └── asciinema/
48
+ └── home-scaffold.cast # asset
49
+ ```
50
+
51
+ The Vue components are imported and registered globally in `theme/index.ts` so markdown files can drop `<HomeHero />` etc. without per-file `<script setup>` blocks.
52
+
53
+ ---
54
+
55
+ ## Task 0: Shared CSS tokens and primitives
56
+
57
+ **Goal:** Add the design-system tokens and small CSS primitives that every component reuses, so later tasks can compose with confidence.
58
+
59
+ **Files:**
60
+ - Modify: `docs/.vitepress/theme/custom.css` (append)
61
+
62
+ **Acceptance Criteria:**
63
+ - [ ] CSS variables defined for the spec's color palette under `:root` and dark-mode variants under `.dark`.
64
+ - [ ] `.pu-eyebrow`, `.pu-btn`, `.pu-btn-primary`, `.pu-btn-ghost`, `.pu-term`, `.pu-term-cursor`, `.pu-section`, `.pu-section--dark`, `.pu-section--band` classes exist and render per the design.
65
+ - [ ] Cursor blink animation is defined and not jittery.
66
+ - [ ] No regressions on existing pages — `yarn docs:dev` still loads, default VitePress theme still works.
67
+
68
+ **Verify:** `yarn docs:dev` → open `http://localhost:5173/plutonium-core/` → existing landing still renders without console errors.
69
+
70
+ **Steps:**
71
+
72
+ - [ ] **Step 1: Read the existing custom.css** so we know what tokens/classes already exist and don't collide.
73
+
74
+ ```bash
75
+ cat docs/.vitepress/theme/custom.css | head -200
76
+ ```
77
+
78
+ - [ ] **Step 2: Append the shared token block.** Add at the bottom of `docs/.vitepress/theme/custom.css`:
79
+
80
+ ```css
81
+ /* ===== Public-pages design tokens (2026-05) ===== */
82
+ :root {
83
+ --pu-bg-dark: #0d1117;
84
+ --pu-bg-dark-2: #161b22;
85
+ --pu-bg-light: #ffffff;
86
+ --pu-bg-band: #fafafa;
87
+ --pu-border: #e0e0e0;
88
+ --pu-border-soft: #ececec;
89
+ --pu-text: #1a1a1a;
90
+ --pu-text-muted: #666666;
91
+ --pu-text-faint: #888888;
92
+ --pu-accent: #d33;
93
+ --pu-accent-soft: #fff7f7;
94
+ --pu-success-bg: #ecf9f1;
95
+ --pu-success-fg: #0a7c3f;
96
+ --pu-warn-bg: #fdf3e6;
97
+ --pu-warn-fg: #a86b00;
98
+ --pu-term-prompt: #7ee787;
99
+ --pu-term-cursor: #58a6ff;
100
+ --pu-term-text: #e6edf3;
101
+ }
102
+
103
+ .dark {
104
+ --pu-bg-light: #0d1117;
105
+ --pu-bg-band: #161b22;
106
+ --pu-text: #e6edf3;
107
+ --pu-text-muted: #9da7b1;
108
+ --pu-text-faint: #6e7681;
109
+ --pu-border: #30363d;
110
+ --pu-border-soft: #21262d;
111
+ }
112
+
113
+ /* Eyebrow */
114
+ .pu-eyebrow {
115
+ font-size: 11px;
116
+ text-transform: uppercase;
117
+ letter-spacing: 0.1em;
118
+ color: var(--pu-accent);
119
+ font-weight: 600;
120
+ margin-bottom: 8px;
121
+ }
122
+ .pu-eyebrow--muted { color: var(--pu-text-faint); }
123
+
124
+ /* Buttons */
125
+ .pu-btn {
126
+ display: inline-block;
127
+ padding: 10px 18px;
128
+ border-radius: 6px;
129
+ font-size: 14px;
130
+ font-weight: 500;
131
+ text-decoration: none;
132
+ transition: opacity 0.15s ease;
133
+ }
134
+ .pu-btn:hover { opacity: 0.85; }
135
+ .pu-btn-primary { background: var(--pu-accent); color: #ffffff; }
136
+ .pu-btn-ghost {
137
+ border: 1px solid currentColor;
138
+ color: var(--pu-text);
139
+ opacity: 0.85;
140
+ }
141
+ .pu-btn-ghost.on-dark { color: #e6edf3; border-color: rgba(255,255,255,0.25); }
142
+
143
+ /* Terminal block */
144
+ .pu-term {
145
+ background: var(--pu-bg-dark);
146
+ color: var(--pu-term-text);
147
+ border-radius: 8px;
148
+ padding: 16px 18px;
149
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
150
+ font-size: 13px;
151
+ line-height: 1.7;
152
+ overflow-x: auto;
153
+ }
154
+ .pu-term--inline { padding: 12px 14px; font-size: 12.5px; }
155
+ .pu-term .prompt { color: var(--pu-term-prompt); }
156
+ .pu-term .dim { opacity: 0.55; }
157
+ .pu-term-cursor {
158
+ background: var(--pu-term-cursor);
159
+ display: inline-block;
160
+ width: 7px;
161
+ height: 13px;
162
+ vertical-align: text-bottom;
163
+ animation: pu-blink 1s steps(2) infinite;
164
+ }
165
+ @keyframes pu-blink { 50% { opacity: 0; } }
166
+
167
+ /* Section frames */
168
+ .pu-section {
169
+ padding: 64px 24px;
170
+ }
171
+ .pu-section--dark {
172
+ background: var(--pu-bg-dark);
173
+ color: var(--pu-term-text);
174
+ }
175
+ .pu-section--band {
176
+ background: var(--pu-bg-band);
177
+ }
178
+ .pu-section .pu-section-inner {
179
+ max-width: 1100px;
180
+ margin: 0 auto;
181
+ }
182
+ .pu-section-title {
183
+ font-size: 28px;
184
+ letter-spacing: -0.02em;
185
+ margin: 0 0 24px;
186
+ color: var(--pu-text);
187
+ }
188
+ .pu-section--dark .pu-section-title { color: var(--pu-term-text); }
189
+ ```
190
+
191
+ - [ ] **Step 3: Boot the dev server and confirm no regressions.**
192
+
193
+ ```bash
194
+ yarn docs:dev
195
+ ```
196
+ Open `http://localhost:5173/plutonium-core/` in a browser. The current landing should still render — we haven't touched it yet. No console errors.
197
+
198
+ - [ ] **Step 4: Commit.**
199
+
200
+ ```bash
201
+ git add docs/.vitepress/theme/custom.css
202
+ git commit -m "feat(docs): add public-pages design tokens and primitives"
203
+ ```
204
+
205
+ ---
206
+
207
+ ## Task 1: HomeHero component
208
+
209
+ **Goal:** Render the hero exactly as locked in the spec — dark, two-column, eyebrow + headline + lede + pillar list + CTAs on the left, animated terminal on the right.
210
+
211
+ **Files:**
212
+ - Create: `docs/.vitepress/theme/components/HomeHero.vue`
213
+ - Modify: `docs/.vitepress/theme/index.ts` (register component)
214
+ - Modify: `docs/index.md` (replace VitePress home layout with our composed page — see Step 4)
215
+
216
+ **Acceptance Criteria:**
217
+ - [ ] `<HomeHero />` renders the spec's hero on the home page.
218
+ - [ ] Animated terminal cursor blinks; terminal text matches the spec verbatim.
219
+ - [ ] CTAs link to `/getting-started/` (primary) and `/getting-started/tutorial/` (ghost).
220
+ - [ ] Layout collapses to a single column at narrow widths (< 768px) with terminal below text.
221
+ - [ ] No layout shift when fonts load.
222
+
223
+ **Verify:** `yarn docs:dev` → home page → hero is visually correct, cursor blinks, CTAs are clickable.
224
+
225
+ **Steps:**
226
+
227
+ - [ ] **Step 1: Create `docs/.vitepress/theme/components/HomeHero.vue`.**
228
+
229
+ ```vue
230
+ <template>
231
+ <section class="pu-section pu-section--dark home-hero">
232
+ <div class="pu-section-inner home-hero-grid">
233
+ <div class="home-hero-text">
234
+ <div class="pu-eyebrow">Plutonium · The Rails RAD framework</div>
235
+ <h1 class="home-hero-headline">
236
+ The Rails framework for things you should never write again.
237
+ </h1>
238
+ <p class="home-hero-lede">
239
+ Convention over configuration, extended to everything you keep rebuilding.
240
+ </p>
241
+ <p class="home-hero-pillars">
242
+ <b>CRUD.</b> <b>Auth.</b> <b>Authorization.</b> <b>Multi-tenancy.</b>
243
+ <b>Admin portals.</b> <b>Search, filters, bulk actions.</b>
244
+ All generated. All customizable. All Rails.
245
+ </p>
246
+ <div class="home-hero-ctas">
247
+ <a class="pu-btn pu-btn-primary" href="/plutonium-core/getting-started/">Get started →</a>
248
+ <a class="pu-btn pu-btn-ghost on-dark" href="/plutonium-core/getting-started/tutorial/">15-min tutorial</a>
249
+ </div>
250
+ </div>
251
+ <pre class="pu-term home-hero-term"><span class="prompt">$</span> rails g pu:res:scaffold Post title:string body:text
252
+ <span class="dim"> create app/models/post.rb</span>
253
+ <span class="dim"> create app/resource_registries/post_definition.rb</span>
254
+ <span class="dim"> create db/migrate/...create_posts.rb</span>
255
+ <span class="prompt">$</span> rails g pu:res:conn Post --dest=admin_portal
256
+ <span class="dim"> route resource :posts</span>
257
+ <span class="dim"> ✓ Connected Post to AdminPortal</span>
258
+ <span class="prompt">$</span> <span class="pu-term-cursor"></span></pre>
259
+ </div>
260
+ </section>
261
+ </template>
262
+
263
+ <style scoped>
264
+ .home-hero { padding: 96px 24px; }
265
+ .home-hero-grid {
266
+ display: grid;
267
+ grid-template-columns: 1.05fr 1fr;
268
+ gap: 48px;
269
+ align-items: center;
270
+ }
271
+ .home-hero-headline {
272
+ font-size: 48px;
273
+ line-height: 1.05;
274
+ letter-spacing: -0.025em;
275
+ margin: 0 0 18px;
276
+ font-weight: 700;
277
+ }
278
+ .home-hero-lede {
279
+ font-size: 18px;
280
+ line-height: 1.5;
281
+ opacity: 0.78;
282
+ margin: 0 0 14px;
283
+ max-width: 540px;
284
+ }
285
+ .home-hero-pillars {
286
+ font-size: 14.5px;
287
+ line-height: 1.6;
288
+ opacity: 0.65;
289
+ margin: 0 0 28px;
290
+ max-width: 540px;
291
+ }
292
+ .home-hero-pillars b { color: var(--pu-term-text); font-weight: 600; opacity: 1; }
293
+ .home-hero-ctas { display: flex; gap: 12px; flex-wrap: wrap; }
294
+ .home-hero-term { margin: 0; white-space: pre; }
295
+ @media (max-width: 768px) {
296
+ .home-hero-grid { grid-template-columns: 1fr; gap: 28px; }
297
+ .home-hero-headline { font-size: 36px; }
298
+ }
299
+ </style>
300
+ ```
301
+
302
+ - [ ] **Step 2: Register the component globally in `docs/.vitepress/theme/index.ts`.**
303
+
304
+ Replace the file with:
305
+
306
+ ```ts
307
+ import DefaultTheme from "vitepress/theme"
308
+ import "./custom.css"
309
+
310
+ import HomeHero from "./components/HomeHero.vue"
311
+
312
+ export default {
313
+ extends: DefaultTheme,
314
+ enhanceApp({ app }) {
315
+ app.component("HomeHero", HomeHero)
316
+ }
317
+ }
318
+ ```
319
+
320
+ - [ ] **Step 3: Strip the existing landing content** from `docs/index.md` and replace it with a single `<HomeHero />` for now. Keep the file's existing frontmatter `layout: home` removed — we're using a custom composition, so set `layout: page` and clear the body.
321
+
322
+ ```markdown
323
+ ---
324
+ layout: page
325
+ sidebar: false
326
+ aside: false
327
+ ---
328
+
329
+ <HomeHero />
330
+ ```
331
+
332
+ (Subsequent home tasks will add the next sections below `<HomeHero />`.)
333
+
334
+ - [ ] **Step 4: Visually verify.**
335
+
336
+ ```bash
337
+ yarn docs:dev
338
+ ```
339
+ Open `http://localhost:5173/plutonium-core/`. Confirm hero matches spec mockup, cursor blinks, CTAs work, layout collapses on narrow viewport.
340
+
341
+ - [ ] **Step 5: Commit.**
342
+
343
+ ```bash
344
+ git add docs/.vitepress/theme/components/HomeHero.vue docs/.vitepress/theme/index.ts docs/index.md
345
+ git commit -m "feat(docs): hero for public-pages overhaul"
346
+ ```
347
+
348
+ ---
349
+
350
+ ## Task 2: HomeStopWriting component (Section 1)
351
+
352
+ **Goal:** Render Section 1 — surface-area before/after — exactly per the locked spec (stat-set 2: capability comparison, no LOC numbers).
353
+
354
+ **Files:**
355
+ - Create: `docs/.vitepress/theme/components/HomeStopWriting.vue`
356
+ - Modify: `docs/.vitepress/theme/index.ts` (register)
357
+ - Modify: `docs/index.md` (add `<HomeStopWriting />` below hero)
358
+
359
+ **Acceptance Criteria:**
360
+ - [ ] Two-column section with light background and section title "What you stop writing." per spec.
361
+ - [ ] Left column: file-tree placeholder with `Hand-rolled` red-pill label and capability stat row "Just CRUD · No auth · No search".
362
+ - [ ] Right column: terminal block with two `pu:*` commands and stat row "Full CRUD · + Auth · + Search · + Filters · + Bulk actions".
363
+ - [ ] Section subtitle: "A blog with posts, comments, an admin panel, and authorization. Same feature, two paths."
364
+
365
+ **Verify:** `yarn docs:dev` → home page → section renders below hero, both columns aligned, pills correct colors.
366
+
367
+ **Steps:**
368
+
369
+ - [ ] **Step 1: Create the component.**
370
+
371
+ Create `docs/.vitepress/theme/components/HomeStopWriting.vue`:
372
+
373
+ ```vue
374
+ <template>
375
+ <section class="pu-section home-stop-writing">
376
+ <div class="pu-section-inner">
377
+ <h2 class="pu-section-title">What you stop writing.</h2>
378
+ <p class="hsw-sub">A blog with posts, comments, an admin panel, and authorization. Same feature, two paths.</p>
379
+
380
+ <div class="hsw-grid">
381
+ <div>
382
+ <span class="hsw-label hsw-label--bad">Hand-rolled</span>
383
+ <div class="hsw-filetree">
384
+ app/controllers/posts_controller.rb<br>
385
+ app/views/posts/*.html.erb<br>
386
+ app/policies/post_policy.rb<br>
387
+ <span class="dim">…before search, filters, bulk actions, auth…</span>
388
+ </div>
389
+ <div class="hsw-stats">
390
+ <span>Just <b>CRUD</b></span>
391
+ <span>No <b>auth</b></span>
392
+ <span>No <b>search</b></span>
393
+ </div>
394
+ </div>
395
+ <div>
396
+ <span class="hsw-label hsw-label--good">Plutonium</span>
397
+ <pre class="pu-term pu-term--inline hsw-term"><span class="prompt">$</span> rails g pu:res:scaffold Post title:string body:text
398
+ <span class="prompt">$</span> rails g pu:res:conn Post --dest=admin_portal</pre>
399
+ <div class="hsw-stats hsw-stats--win">
400
+ <span><b>Full CRUD</b></span>
401
+ <span><b>+ Auth</b></span>
402
+ <span><b>+ Search</b></span>
403
+ <span><b>+ Filters</b></span>
404
+ <span><b>+ Bulk actions</b></span>
405
+ </div>
406
+ </div>
407
+ </div>
408
+ </div>
409
+ </section>
410
+ </template>
411
+
412
+ <style scoped>
413
+ .hsw-sub { color: var(--pu-text-muted); font-size: 15px; margin: -16px 0 32px; }
414
+ .hsw-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 24px; }
415
+ .hsw-label {
416
+ display: inline-block; font-size: 11px; padding: 3px 8px; border-radius: 4px;
417
+ text-transform: uppercase; letter-spacing: 0.08em; font-weight: 600;
418
+ }
419
+ .hsw-label--bad { background: #fff0f0; color: var(--pu-accent); }
420
+ .hsw-label--good { background: var(--pu-success-bg); color: var(--pu-success-fg); }
421
+ .hsw-filetree {
422
+ margin-top: 10px;
423
+ background: var(--pu-bg-band); border: 1px solid var(--pu-border-soft);
424
+ border-radius: 8px; padding: 14px;
425
+ font-family: ui-monospace, monospace; font-size: 12px; line-height: 1.75; color: var(--pu-text-muted);
426
+ }
427
+ .hsw-filetree .dim { color: var(--pu-text-faint); }
428
+ .hsw-term { margin-top: 10px; }
429
+ .hsw-stats {
430
+ margin-top: 14px; display: flex; gap: 14px; flex-wrap: wrap;
431
+ font-size: 11.5px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--pu-text-faint);
432
+ }
433
+ .hsw-stats b { color: var(--pu-text); font-weight: 600; }
434
+ .hsw-stats--win b { color: var(--pu-success-fg); }
435
+ @media (max-width: 768px) { .hsw-grid { grid-template-columns: 1fr; } }
436
+ </style>
437
+ ```
438
+
439
+ - [ ] **Step 2: Register in `docs/.vitepress/theme/index.ts`.** Add the import and `app.component` line:
440
+
441
+ ```ts
442
+ import HomeStopWriting from "./components/HomeStopWriting.vue"
443
+ // ... in enhanceApp:
444
+ app.component("HomeStopWriting", HomeStopWriting)
445
+ ```
446
+
447
+ - [ ] **Step 3: Add to `docs/index.md`** below `<HomeHero />`:
448
+
449
+ ```markdown
450
+ <HomeHero />
451
+
452
+ <HomeStopWriting />
453
+ ```
454
+
455
+ - [ ] **Step 4: Visually verify.** `yarn docs:dev` → confirm section renders correctly below hero.
456
+
457
+ - [ ] **Step 5: Commit.**
458
+
459
+ ```bash
460
+ git add docs/.vitepress/theme/components/HomeStopWriting.vue docs/.vitepress/theme/index.ts docs/index.md
461
+ git commit -m "feat(docs): home section 1 — what you stop writing"
462
+ ```
463
+
464
+ ---
465
+
466
+ ## Task 3: HomePillars component (Section 2)
467
+
468
+ **Goal:** Render Section 2 — four equal pillars in a 4-column grid on a light band.
469
+
470
+ **Files:**
471
+ - Create: `docs/.vitepress/theme/components/HomePillars.vue`
472
+ - Modify: `docs/.vitepress/theme/index.ts` (register)
473
+ - Modify: `docs/index.md`
474
+
475
+ **Acceptance Criteria:**
476
+ - [ ] Section title: "Built on principles, not magic."
477
+ - [ ] Four cards with names "Convention over configuration", "It's just Rails", "Multi-tenant ready", "AI-readable".
478
+ - [ ] Card descriptions match the spec verbatim, including the corrected "It's just Rails" copy mentioning regular Ruby mixins.
479
+ - [ ] On a light band background.
480
+
481
+ **Verify:** `yarn docs:dev` → home page → pillars render as a 4-column grid, copy matches spec.
482
+
483
+ **Steps:**
484
+
485
+ - [ ] **Step 1: Create the component.**
486
+
487
+ ```vue
488
+ <template>
489
+ <section class="pu-section pu-section--band">
490
+ <div class="pu-section-inner">
491
+ <div class="pu-eyebrow pu-eyebrow--muted">Four pillars</div>
492
+ <h2 class="pu-section-title">Built on principles, not magic.</h2>
493
+ <div class="hp-grid">
494
+ <div class="hp-card" v-for="p in pillars" :key="p.name">
495
+ <div class="hp-icon">{{ p.icon }}</div>
496
+ <div class="hp-name">{{ p.name }}</div>
497
+ <div class="hp-desc">{{ p.desc }}</div>
498
+ </div>
499
+ </div>
500
+ </div>
501
+ </section>
502
+ </template>
503
+
504
+ <script setup>
505
+ const pillars = [
506
+ { icon: "⚙", name: "Convention over configuration",
507
+ desc: "Extended to resources, policies, portals, and tenancy — not just routes and views." },
508
+ { icon: "💎", name: "It's just Rails",
509
+ desc: "Generated code lives in your repo. Edit it, override it, delete it. The “magic” is regular Ruby mixins you can read." },
510
+ { icon: "🏢", name: "Multi-tenant ready",
511
+ desc: "Path or domain tenancy. Scoped relations. Invites and memberships out of the box." },
512
+ { icon: "🤖", name: "AI-readable",
513
+ desc: "Predictable file layout and naming. Built-in skills teach AI assistants the patterns." },
514
+ ]
515
+ </script>
516
+
517
+ <style scoped>
518
+ .hp-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; }
519
+ .hp-card {
520
+ padding: 18px; border: 1px solid var(--pu-border-soft); border-radius: 8px;
521
+ background: var(--pu-bg-light);
522
+ }
523
+ .hp-icon { font-size: 20px; margin-bottom: 8px; opacity: 0.75; }
524
+ .hp-name { font-weight: 600; font-size: 15px; color: var(--pu-text); margin-bottom: 6px; line-height: 1.25; }
525
+ .hp-desc { font-size: 13px; color: var(--pu-text-muted); line-height: 1.5; }
526
+ @media (max-width: 900px) { .hp-grid { grid-template-columns: repeat(2, 1fr); } }
527
+ @media (max-width: 480px) { .hp-grid { grid-template-columns: 1fr; } }
528
+ </style>
529
+ ```
530
+
531
+ - [ ] **Step 2: Register and add to `docs/index.md`.**
532
+
533
+ In `theme/index.ts`:
534
+ ```ts
535
+ import HomePillars from "./components/HomePillars.vue"
536
+ app.component("HomePillars", HomePillars)
537
+ ```
538
+
539
+ In `docs/index.md`, append below `<HomeStopWriting />`:
540
+ ```markdown
541
+ <HomePillars />
542
+ ```
543
+
544
+ - [ ] **Step 3: Visually verify** with `yarn docs:dev`.
545
+
546
+ - [ ] **Step 4: Commit.**
547
+
548
+ ```bash
549
+ git add docs/.vitepress/theme/components/HomePillars.vue docs/.vitepress/theme/index.ts docs/index.md
550
+ git commit -m "feat(docs): home section 2 — four pillars"
551
+ ```
552
+
553
+ ---
554
+
555
+ ## Task 4: HomeWalkthrough component (Section 3) — placeholders
556
+
557
+ **Goal:** Build the layout for the walkthrough section: wide hero shot up top, then a 3-column strip of asciinema + index + form. Use placeholder boxes — actual assets get wired in by Task 10.
558
+
559
+ **Files:**
560
+ - Create: `docs/.vitepress/theme/components/HomeWalkthrough.vue`
561
+ - Modify: `docs/.vitepress/theme/index.ts` (register)
562
+ - Modify: `docs/index.md`
563
+
564
+ **Acceptance Criteria:**
565
+ - [ ] Section title: "Two commands. A whole portal."
566
+ - [ ] Wide placeholder ribbon at top labeled "[ Wide portal screenshot — `home-portal.png` pending ]".
567
+ - [ ] Below: three equal columns — asciinema placeholder, index placeholder, form placeholder — each labeled with the eventual asset filename.
568
+ - [ ] Each placeholder has fixed aspect ratio so layout doesn't shift when assets land.
569
+
570
+ **Verify:** `yarn docs:dev` → section renders with all four placeholder slots labeled with their asset filenames.
571
+
572
+ **Steps:**
573
+
574
+ - [ ] **Step 1: Create the component.**
575
+
576
+ ```vue
577
+ <template>
578
+ <section class="pu-section">
579
+ <div class="pu-section-inner">
580
+ <div class="pu-eyebrow">A real example</div>
581
+ <h2 class="pu-section-title">Two commands. A whole portal.</h2>
582
+
583
+ <div class="hw-hero-shot">
584
+ <div class="hw-browser-bar"><span></span><span></span><span></span><code>localhost:3000/admin</code></div>
585
+ <div class="hw-placeholder hw-placeholder--portal">
586
+ [ Wide portal screenshot — <code>home-portal.png</code> pending ]
587
+ </div>
588
+ </div>
589
+
590
+ <div class="hw-strip">
591
+ <div>
592
+ <div class="hw-label">1 — You run</div>
593
+ <div class="hw-placeholder hw-placeholder--term">
594
+ [ Asciinema — <code>home-scaffold.cast</code> pending ]
595
+ </div>
596
+ </div>
597
+ <div>
598
+ <div class="hw-label">2 — Plutonium serves</div>
599
+ <div class="hw-browser hw-browser--small">
600
+ <div class="hw-browser-bar"><span></span><span></span><code>/admin/posts</code></div>
601
+ <div class="hw-placeholder">[ <code>home-index.png</code> pending ]</div>
602
+ </div>
603
+ </div>
604
+ <div>
605
+ <div class="hw-label">3 — Forms, free</div>
606
+ <div class="hw-browser hw-browser--small">
607
+ <div class="hw-browser-bar"><span></span><span></span><code>/admin/posts/new</code></div>
608
+ <div class="hw-placeholder">[ <code>home-form.png</code> pending ]</div>
609
+ </div>
610
+ </div>
611
+ </div>
612
+ </div>
613
+ </section>
614
+ </template>
615
+
616
+ <style scoped>
617
+ .hw-hero-shot {
618
+ border: 1px solid var(--pu-border); border-radius: 10px; overflow: hidden;
619
+ background: var(--pu-bg-light); margin-bottom: 18px;
620
+ }
621
+ .hw-browser-bar {
622
+ background: var(--pu-bg-band); padding: 8px 12px; display: flex; align-items: center; gap: 5px;
623
+ border-bottom: 1px solid var(--pu-border-soft);
624
+ }
625
+ .hw-browser-bar span {
626
+ width: 10px; height: 10px; border-radius: 50%; background: var(--pu-border);
627
+ }
628
+ .hw-browser-bar code {
629
+ margin-left: 12px; background: var(--pu-bg-light); padding: 3px 8px; border-radius: 4px;
630
+ font-size: 11px; color: var(--pu-text-faint);
631
+ }
632
+ .hw-placeholder {
633
+ aspect-ratio: 16/9;
634
+ display: flex; align-items: center; justify-content: center;
635
+ background: linear-gradient(135deg, var(--pu-bg-band), #f0f0f0);
636
+ color: var(--pu-text-faint); font-size: 13px; font-family: ui-monospace, monospace;
637
+ }
638
+ .hw-placeholder--portal { aspect-ratio: 21/8; }
639
+ .hw-placeholder--term { aspect-ratio: 4/3; background: linear-gradient(135deg, #1a1f29, #0d1117); color: #6e7681; }
640
+ .hw-strip { display: grid; grid-template-columns: 1.1fr 1fr 1fr; gap: 16px; align-items: stretch; }
641
+ .hw-label { font-size: 11px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--pu-text-faint); margin-bottom: 8px; }
642
+ .hw-browser--small { border: 1px solid var(--pu-border-soft); border-radius: 8px; overflow: hidden; }
643
+ .hw-browser--small .hw-browser-bar { padding: 5px 8px; }
644
+ .hw-browser--small .hw-browser-bar span { width: 8px; height: 8px; }
645
+ .hw-browser--small .hw-browser-bar code { margin-left: 6px; font-size: 10px; }
646
+ @media (max-width: 768px) { .hw-strip { grid-template-columns: 1fr; } }
647
+ </style>
648
+ ```
649
+
650
+ - [ ] **Step 2: Register and add to `docs/index.md`.**
651
+
652
+ ```ts
653
+ // theme/index.ts
654
+ import HomeWalkthrough from "./components/HomeWalkthrough.vue"
655
+ app.component("HomeWalkthrough", HomeWalkthrough)
656
+ ```
657
+
658
+ ```markdown
659
+ <!-- docs/index.md, below HomePillars -->
660
+ <HomeWalkthrough />
661
+ ```
662
+
663
+ - [ ] **Step 3: Visually verify** layout reserves space for assets without shifting.
664
+
665
+ - [ ] **Step 4: Commit.**
666
+
667
+ ```bash
668
+ git add docs/.vitepress/theme/components/HomeWalkthrough.vue docs/.vitepress/theme/index.ts docs/index.md
669
+ git commit -m "feat(docs): home section 3 — walkthrough layout (asset placeholders)"
670
+ ```
671
+
672
+ ---
673
+
674
+ ## Task 5: HomeAudienceSplit component (Section 4)
675
+
676
+ **Goal:** Render the side-by-side audience split with the locked headlines.
677
+
678
+ **Files:**
679
+ - Create: `docs/.vitepress/theme/components/HomeAudienceSplit.vue`
680
+ - Modify: `docs/.vitepress/theme/index.ts` (register)
681
+ - Modify: `docs/index.md`
682
+
683
+ **Acceptance Criteria:**
684
+ - [ ] Section title: "Plutonium fits two kinds of teams."
685
+ - [ ] Two equal columns separated by a thin divider.
686
+ - [ ] Left lede verbatim: "The missing layer between Rails and the apps you keep building."
687
+ - [ ] Right lede verbatim: "Skip the SaaS template debate. Plutonium turns Rails into a SaaS toolkit."
688
+ - [ ] Each column has 4 bullet items per spec, with the red `→` glyph.
689
+
690
+ **Verify:** `yarn docs:dev` → home page → section renders with two columns, divider visible, copy matches spec verbatim.
691
+
692
+ **Steps:**
693
+
694
+ - [ ] **Step 1: Create the component.**
695
+
696
+ ```vue
697
+ <template>
698
+ <section class="pu-section pu-section--band">
699
+ <div class="pu-section-inner">
700
+ <div class="pu-eyebrow pu-eyebrow--muted">For two audiences</div>
701
+ <h2 class="pu-section-title">Plutonium fits two kinds of teams.</h2>
702
+ <div class="ha-grid">
703
+ <div class="ha-col">
704
+ <div class="ha-head">For Rails developers</div>
705
+ <p class="ha-lede">The missing layer between Rails and the apps you keep building.</p>
706
+ <ul class="ha-list">
707
+ <li><span class="ha-arr">→</span> Convention extended to CRUD, policies, and portals</li>
708
+ <li><span class="ha-arr">→</span> Generated code lives in your repo — edit anything</li>
709
+ <li><span class="ha-arr">→</span> Mountable Rails engines for packages and portals</li>
710
+ <li><span class="ha-arr">→</span> ActionPolicy authorization, baked in</li>
711
+ </ul>
712
+ </div>
713
+ <div class="ha-col ha-col--right">
714
+ <div class="ha-head">For founders &amp; teams</div>
715
+ <p class="ha-lede">Skip the SaaS template debate. Plutonium turns Rails into a SaaS toolkit.</p>
716
+ <ul class="ha-list">
717
+ <li><span class="ha-arr">→</span> Admin panel, signup, and invites on day one</li>
718
+ <li><span class="ha-arr">→</span> Multi-tenant scoping when you need it</li>
719
+ <li><span class="ha-arr">→</span> No template lock-in — it's just your Rails app</li>
720
+ <li><span class="ha-arr">→</span> Ship faster with AI tools that understand your code</li>
721
+ </ul>
722
+ </div>
723
+ </div>
724
+ </div>
725
+ </section>
726
+ </template>
727
+
728
+ <style scoped>
729
+ .ha-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 32px; }
730
+ .ha-col--right { border-left: 1px solid var(--pu-border); padding-left: 32px; }
731
+ .ha-head {
732
+ font-size: 11px; text-transform: uppercase; letter-spacing: 0.1em;
733
+ color: var(--pu-accent); font-weight: 600; margin-bottom: 8px;
734
+ }
735
+ .ha-lede {
736
+ font-size: 17px; line-height: 1.35; font-weight: 500; color: var(--pu-text);
737
+ margin: 0 0 14px; letter-spacing: -0.01em;
738
+ }
739
+ .ha-list { list-style: none; padding: 0; margin: 0; font-size: 14px; line-height: 1.85; color: var(--pu-text-muted); }
740
+ .ha-list li { display: flex; gap: 8px; align-items: flex-start; }
741
+ .ha-arr { color: var(--pu-accent); font-weight: 700; flex-shrink: 0; }
742
+ @media (max-width: 768px) {
743
+ .ha-grid { grid-template-columns: 1fr; }
744
+ .ha-col--right { border-left: none; padding-left: 0; border-top: 1px solid var(--pu-border); padding-top: 24px; }
745
+ }
746
+ </style>
747
+ ```
748
+
749
+ - [ ] **Step 2: Register and add to `docs/index.md`.**
750
+
751
+ ```ts
752
+ import HomeAudienceSplit from "./components/HomeAudienceSplit.vue"
753
+ app.component("HomeAudienceSplit", HomeAudienceSplit)
754
+ ```
755
+
756
+ ```markdown
757
+ <HomeAudienceSplit />
758
+ ```
759
+
760
+ - [ ] **Step 3: Visually verify.**
761
+
762
+ - [ ] **Step 4: Commit.**
763
+
764
+ ```bash
765
+ git add docs/.vitepress/theme/components/HomeAudienceSplit.vue docs/.vitepress/theme/index.ts docs/index.md
766
+ git commit -m "feat(docs): home section 4 — audience split"
767
+ ```
768
+
769
+ ---
770
+
771
+ ## Task 6: HomeInTheBox component (Section 5)
772
+
773
+ **Goal:** Render Section 5 — categorized "in the box" — three category rows, each with a 3-column grid of capabilities.
774
+
775
+ **Files:**
776
+ - Create: `docs/.vitepress/theme/components/HomeInTheBox.vue`
777
+ - Modify: `docs/.vitepress/theme/index.ts` (register)
778
+ - Modify: `docs/index.md`
779
+
780
+ **Acceptance Criteria:**
781
+ - [ ] Section title: "Organized the way you'll use it."
782
+ - [ ] Three category headers in red uppercase: Resources / App structure / People & access.
783
+ - [ ] Each row has 3 cells with bold name + one-line description, per spec.
784
+
785
+ **Verify:** `yarn docs:dev` → section renders 3 rows × 3 cells, category headers in red.
786
+
787
+ **Steps:**
788
+
789
+ - [ ] **Step 1: Create the component.**
790
+
791
+ ```vue
792
+ <template>
793
+ <section class="pu-section">
794
+ <div class="pu-section-inner">
795
+ <div class="pu-eyebrow pu-eyebrow--muted">What's in the box</div>
796
+ <h2 class="pu-section-title">Organized the way you'll use it.</h2>
797
+ <div v-for="cat in cats" :key="cat.name" class="hb-cat">
798
+ <div class="hb-cat-name">{{ cat.name }}</div>
799
+ <div class="hb-row">
800
+ <div v-for="item in cat.items" :key="item.name" class="hb-item">
801
+ <b>{{ item.name }}</b>
802
+ <small>{{ item.desc }}</small>
803
+ </div>
804
+ </div>
805
+ </div>
806
+ </div>
807
+ </section>
808
+ </template>
809
+
810
+ <script setup>
811
+ const cats = [
812
+ { name: "Resources", items: [
813
+ { name: "Scaffolds", desc: "Model, definition, policy, routes" },
814
+ { name: "Search & filters", desc: "Declarative on the definition" },
815
+ { name: "Custom & bulk actions", desc: "Resource-scoped interactions" },
816
+ ]},
817
+ { name: "App structure", items: [
818
+ { name: "Portals", desc: "Themed, mountable engines" },
819
+ { name: "Packages", desc: "Feature engines under your app" },
820
+ { name: "Multi-tenancy", desc: "Path or domain scoping" },
821
+ ]},
822
+ { name: "People & access", items: [
823
+ { name: "Auth (Rodauth)", desc: "Login, signup, password reset" },
824
+ { name: "Authorization", desc: "ActionPolicy per resource" },
825
+ { name: "Invites & memberships", desc: "Token lifecycle, mailers, onboarding" },
826
+ ]},
827
+ ]
828
+ </script>
829
+
830
+ <style scoped>
831
+ .hb-cat { margin-bottom: 28px; }
832
+ .hb-cat:last-child { margin-bottom: 0; }
833
+ .hb-cat-name {
834
+ font-size: 11px; text-transform: uppercase; letter-spacing: 0.1em;
835
+ color: var(--pu-accent); font-weight: 600; margin-bottom: 12px;
836
+ }
837
+ .hb-row { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
838
+ .hb-item { font-size: 13px; color: var(--pu-text-muted); }
839
+ .hb-item b { display: block; color: var(--pu-text); font-weight: 600; margin-bottom: 2px; font-size: 14px; }
840
+ .hb-item small { font-size: 12px; color: var(--pu-text-faint); }
841
+ @media (max-width: 768px) { .hb-row { grid-template-columns: 1fr; } }
842
+ </style>
843
+ ```
844
+
845
+ - [ ] **Step 2: Register and add to `docs/index.md`.**
846
+
847
+ ```ts
848
+ import HomeInTheBox from "./components/HomeInTheBox.vue"
849
+ app.component("HomeInTheBox", HomeInTheBox)
850
+ ```
851
+
852
+ ```markdown
853
+ <HomeInTheBox />
854
+ ```
855
+
856
+ - [ ] **Step 3: Visually verify.**
857
+
858
+ - [ ] **Step 4: Commit.**
859
+
860
+ ```bash
861
+ git add docs/.vitepress/theme/components/HomeInTheBox.vue docs/.vitepress/theme/index.ts docs/index.md
862
+ git commit -m "feat(docs): home section 5 — what's in the box"
863
+ ```
864
+
865
+ ---
866
+
867
+ ## Task 7: HomeCta component (Section 6) with template-toggle pills
868
+
869
+ **Goal:** Render the manifesto CTA with reactive Vue toggle pills swapping between `plutonium.rb` and `pluton8.rb` install commands.
870
+
871
+ **Files:**
872
+ - Create: `docs/.vitepress/theme/components/HomeCta.vue`
873
+ - Modify: `docs/.vitepress/theme/index.ts` (register)
874
+ - Modify: `docs/index.md`
875
+
876
+ **Acceptance Criteria:**
877
+ - [ ] Centered manifesto block on a subtle gradient.
878
+ - [ ] Manifesto line: *"Stop writing the parts of every Rails app you've already written. Plutonium is what should have been there all along."*
879
+ - [ ] Pill toggle with two options: `plutonium` (core + portals) and `pluton8` (+ SaaS lite stack). `plutonium` selected by default.
880
+ - [ ] Install command line updates reactively when the user clicks a pill.
881
+ - [ ] Both URLs are correct:
882
+ - `https://radioactive-labs.github.io/plutonium-core/templates/plutonium.rb`
883
+ - `https://radioactive-labs.github.io/plutonium-core/templates/pluton8.rb`
884
+ - [ ] Primary CTA "Get started →" → `/getting-started/`; ghost "GitHub" → `https://github.com/radioactive-labs/plutonium-core`.
885
+
886
+ **Verify:** `yarn docs:dev` → home page → click each pill → command updates without page reload.
887
+
888
+ **Steps:**
889
+
890
+ - [ ] **Step 1: Create the component with reactive state.**
891
+
892
+ ```vue
893
+ <template>
894
+ <section class="pu-section pu-section--band hc-section">
895
+ <div class="hc-inner">
896
+ <p class="hc-quote">
897
+ “Stop writing the parts of every Rails app you've already written.
898
+ Plutonium is what should have been there all along.”
899
+ </p>
900
+
901
+ <div class="hc-pills" role="tablist">
902
+ <button
903
+ v-for="opt in options"
904
+ :key="opt.id"
905
+ :class="['hc-pill', { 'hc-pill--active': selected === opt.id }]"
906
+ role="tab"
907
+ :aria-selected="selected === opt.id"
908
+ @click="selected = opt.id"
909
+ >
910
+ <span class="hc-pill-name">{{ opt.name }}</span>
911
+ <small class="hc-pill-sub">{{ opt.sub }}</small>
912
+ </button>
913
+ </div>
914
+
915
+ <pre class="pu-term hc-term"><span class="prompt">$</span> rails new my_app -m {{ activeUrl }}<span class="pu-term-cursor"></span></pre>
916
+
917
+ <div class="hc-ctas">
918
+ <a class="pu-btn pu-btn-primary" href="/plutonium-core/getting-started/">Get started →</a>
919
+ <a class="pu-btn pu-btn-ghost" href="https://github.com/radioactive-labs/plutonium-core" target="_blank" rel="noopener">GitHub</a>
920
+ </div>
921
+ </div>
922
+ </section>
923
+ </template>
924
+
925
+ <script setup>
926
+ import { ref, computed } from "vue"
927
+
928
+ const options = [
929
+ { id: "plutonium", name: "plutonium", sub: "core + portals",
930
+ url: "https://radioactive-labs.github.io/plutonium-core/templates/plutonium.rb" },
931
+ { id: "pluton8", name: "pluton8", sub: "+ SaaS lite stack",
932
+ url: "https://radioactive-labs.github.io/plutonium-core/templates/pluton8.rb" },
933
+ ]
934
+ const selected = ref("plutonium")
935
+ const activeUrl = computed(() => options.find(o => o.id === selected.value).url)
936
+ </script>
937
+
938
+ <style scoped>
939
+ .hc-section { padding: 96px 24px; }
940
+ .hc-inner {
941
+ max-width: 760px; margin: 0 auto; text-align: center;
942
+ background: linear-gradient(180deg, var(--pu-bg-band), var(--pu-bg-light));
943
+ border: 1px solid var(--pu-border); border-radius: 12px; padding: 56px 32px;
944
+ }
945
+ .hc-quote {
946
+ font-size: 28px; letter-spacing: -0.02em; line-height: 1.25;
947
+ color: var(--pu-text); font-weight: 500;
948
+ margin: 0 auto 28px; max-width: 600px;
949
+ }
950
+ .hc-pills {
951
+ display: inline-flex; background: rgba(0,0,0,0.05); border-radius: 999px;
952
+ padding: 4px; gap: 2px; margin-bottom: 14px;
953
+ }
954
+ .hc-pill {
955
+ background: transparent; border: 0; padding: 8px 16px; border-radius: 999px;
956
+ font-size: 12.5px; color: var(--pu-text-muted); cursor: pointer;
957
+ display: flex; flex-direction: column; align-items: center; line-height: 1.1;
958
+ }
959
+ .hc-pill-sub { font-size: 9.5px; text-transform: uppercase; letter-spacing: 0.08em; color: var(--pu-text-faint); margin-top: 2px; }
960
+ .hc-pill--active {
961
+ background: var(--pu-bg-light); color: var(--pu-text); font-weight: 600;
962
+ box-shadow: 0 1px 3px rgba(0,0,0,0.1);
963
+ }
964
+ .hc-term { max-width: 640px; margin: 0 auto 24px; text-align: left; white-space: pre-wrap; word-break: break-all; }
965
+ .hc-ctas { display: flex; gap: 10px; justify-content: center; flex-wrap: wrap; }
966
+ @media (max-width: 600px) { .hc-quote { font-size: 22px; } }
967
+ </style>
968
+ ```
969
+
970
+ - [ ] **Step 2: Register and add to `docs/index.md` as the final block.**
971
+
972
+ ```ts
973
+ import HomeCta from "./components/HomeCta.vue"
974
+ app.component("HomeCta", HomeCta)
975
+ ```
976
+
977
+ ```markdown
978
+ <HomeCta />
979
+ ```
980
+
981
+ - [ ] **Step 3: Visually verify both pill states.** Click `pluton8` → command should change to the pluton8 URL. Click back → reverts.
982
+
983
+ - [ ] **Step 4: Commit.**
984
+
985
+ ```bash
986
+ git add docs/.vitepress/theme/components/HomeCta.vue docs/.vitepress/theme/index.ts docs/index.md
987
+ git commit -m "feat(docs): home section 6 — manifesto CTA with template pills"
988
+ ```
989
+
990
+ ---
991
+
992
+ ## Task 8: SectionLanding shared component
993
+
994
+ **Goal:** Build a shared Vue component that renders the spec's Pattern B layout (eyebrow + h1 + lede + numbered/categorized rail + sidebar). The three section landings will use this component with different props.
995
+
996
+ **Files:**
997
+ - Create: `docs/.vitepress/theme/components/SectionLanding.vue`
998
+ - Modify: `docs/.vitepress/theme/index.ts` (register)
999
+
1000
+ **Acceptance Criteria:**
1001
+ - [ ] Component accepts props: `eyebrow` (string), `title` (string), `lede` (string), `rail` (array of either step objects `{name, desc}` for numbered mode OR group objects `{group, items: [{name, desc, link}]}` for categorized mode), `mode` ("numbered" | "categorized"), `sidebar` (array of `{heading, items: [{label, href, note?}]}`).
1002
+ - [ ] Renders left rail with red vertical bar; numbered mode shows red circle badges with step numbers; categorized mode shows category headers between groups.
1003
+ - [ ] Right sidebar renders heading groups with anchor links.
1004
+ - [ ] Each rail item with a `link` prop is clickable.
1005
+ - [ ] Collapses to single column < 768px (sidebar moves below rail).
1006
+
1007
+ **Verify:** Smoke-test by temporarily mounting it in a scratch markdown file with sample props; confirm both `numbered` and `categorized` modes render correctly.
1008
+
1009
+ **Steps:**
1010
+
1011
+ - [ ] **Step 1: Create the component.**
1012
+
1013
+ ```vue
1014
+ <template>
1015
+ <section class="pu-section sl-section">
1016
+ <div class="pu-section-inner">
1017
+ <div class="pu-eyebrow">{{ eyebrow }}</div>
1018
+ <h1 class="sl-h1">{{ title }}</h1>
1019
+ <p class="sl-lede">{{ lede }}</p>
1020
+
1021
+ <div class="sl-grid">
1022
+ <div class="sl-rail">
1023
+ <template v-if="mode === 'numbered'">
1024
+ <a
1025
+ v-for="(step, i) in rail"
1026
+ :key="i"
1027
+ :href="step.link"
1028
+ :class="['sl-step', { 'sl-step--link': step.link }]"
1029
+ >
1030
+ <span class="sl-num">{{ i + 1 }}</span>
1031
+ <span class="sl-step-body">
1032
+ <span class="sl-step-name">{{ step.name }}</span>
1033
+ <span v-if="step.desc" class="sl-step-desc">{{ step.desc }}</span>
1034
+ </span>
1035
+ </a>
1036
+ </template>
1037
+ <template v-else>
1038
+ <div v-for="grp in rail" :key="grp.group" class="sl-group">
1039
+ <div class="sl-group-name">{{ grp.group }}</div>
1040
+ <a
1041
+ v-for="item in grp.items"
1042
+ :key="item.name"
1043
+ :href="item.link"
1044
+ class="sl-step sl-step--link"
1045
+ >
1046
+ <span class="sl-step-body">
1047
+ <span class="sl-step-name">{{ item.name }}</span>
1048
+ <span v-if="item.desc" class="sl-step-desc">{{ item.desc }}</span>
1049
+ </span>
1050
+ </a>
1051
+ </div>
1052
+ </template>
1053
+ </div>
1054
+
1055
+ <aside class="sl-aside">
1056
+ <div v-for="block in sidebar" :key="block.heading" class="sl-aside-block">
1057
+ <h4 class="sl-aside-heading">{{ block.heading }}</h4>
1058
+ <ul>
1059
+ <li v-for="item in block.items" :key="item.label">
1060
+ <a :href="item.href">{{ item.label }}</a>
1061
+ <span v-if="item.note" class="sl-aside-note"> — {{ item.note }}</span>
1062
+ </li>
1063
+ </ul>
1064
+ </div>
1065
+ </aside>
1066
+ </div>
1067
+ </div>
1068
+ </section>
1069
+ </template>
1070
+
1071
+ <script setup>
1072
+ defineProps({
1073
+ eyebrow: { type: String, required: true },
1074
+ title: { type: String, required: true },
1075
+ lede: { type: String, required: true },
1076
+ rail: { type: Array, required: true },
1077
+ mode: { type: String, default: "numbered", validator: v => ["numbered", "categorized"].includes(v) },
1078
+ sidebar: { type: Array, default: () => [] },
1079
+ })
1080
+ </script>
1081
+
1082
+ <style scoped>
1083
+ .sl-section { padding: 64px 24px 96px; }
1084
+ .sl-h1 { font-size: 36px; letter-spacing: -0.025em; margin: 0 0 12px; color: var(--pu-text); }
1085
+ .sl-lede { font-size: 16px; color: var(--pu-text-muted); max-width: 640px; margin: 0 0 40px; line-height: 1.55; }
1086
+ .sl-grid { display: grid; grid-template-columns: 1.4fr 1fr; gap: 48px; }
1087
+ .sl-rail { border-left: 2px solid var(--pu-accent); padding-left: 24px; }
1088
+ .sl-group + .sl-group { margin-top: 22px; }
1089
+ .sl-group-name {
1090
+ font-size: 11px; text-transform: uppercase; letter-spacing: 0.1em;
1091
+ color: var(--pu-accent); font-weight: 600; margin-bottom: 8px;
1092
+ }
1093
+ .sl-step {
1094
+ display: flex; gap: 12px; align-items: flex-start;
1095
+ padding: 12px 0; border-bottom: 1px solid var(--pu-border-soft);
1096
+ color: var(--pu-text); text-decoration: none;
1097
+ }
1098
+ .sl-step:last-child { border-bottom: none; }
1099
+ .sl-step--link:hover .sl-step-name { color: var(--pu-accent); }
1100
+ .sl-num {
1101
+ flex-shrink: 0; width: 24px; height: 24px; line-height: 24px; text-align: center;
1102
+ background: var(--pu-accent); color: #fff; border-radius: 50%;
1103
+ font-size: 11px; font-weight: 600;
1104
+ }
1105
+ .sl-step-body { display: flex; flex-direction: column; gap: 2px; }
1106
+ .sl-step-name { font-weight: 600; font-size: 14px; }
1107
+ .sl-step-desc { font-size: 12.5px; color: var(--pu-text-muted); }
1108
+
1109
+ .sl-aside-block + .sl-aside-block { margin-top: 28px; }
1110
+ .sl-aside-heading {
1111
+ font-size: 11px; text-transform: uppercase; letter-spacing: 0.1em;
1112
+ color: var(--pu-text-faint); margin: 0 0 10px; font-weight: 600;
1113
+ }
1114
+ .sl-aside ul { list-style: none; padding: 0; margin: 0; font-size: 14px; line-height: 1.85; }
1115
+ .sl-aside a { color: var(--pu-accent); text-decoration: none; font-weight: 500; }
1116
+ .sl-aside a:hover { text-decoration: underline; }
1117
+ .sl-aside-note { color: var(--pu-text-muted); font-weight: 400; }
1118
+
1119
+ @media (max-width: 900px) {
1120
+ .sl-grid { grid-template-columns: 1fr; gap: 32px; }
1121
+ }
1122
+ </style>
1123
+ ```
1124
+
1125
+ - [ ] **Step 2: Register globally** in `docs/.vitepress/theme/index.ts`:
1126
+
1127
+ ```ts
1128
+ import SectionLanding from "./components/SectionLanding.vue"
1129
+ app.component("SectionLanding", SectionLanding)
1130
+ ```
1131
+
1132
+ - [ ] **Step 3: Smoke-test.** Temporarily add a `<SectionLanding>` to a scratch file (or directly try in `docs/index.md` at the bottom) with both `mode="numbered"` and `mode="categorized"` to confirm both branches render. Remove the scratch usage after confirming.
1133
+
1134
+ - [ ] **Step 4: Commit.**
1135
+
1136
+ ```bash
1137
+ git add docs/.vitepress/theme/components/SectionLanding.vue docs/.vitepress/theme/index.ts
1138
+ git commit -m "feat(docs): SectionLanding shared component"
1139
+ ```
1140
+
1141
+ ---
1142
+
1143
+ ## Task 9: Getting Started landing page
1144
+
1145
+ **Goal:** Rewrite `docs/getting-started/index.md` using `<SectionLanding>` in numbered mode for the 8 tutorial chapters.
1146
+
1147
+ **Files:**
1148
+ - Modify (rewrite): `docs/getting-started/index.md`
1149
+
1150
+ **Acceptance Criteria:**
1151
+ - [ ] Page uses `<SectionLanding>` with `mode="numbered"`.
1152
+ - [ ] Eyebrow: "GETTING STARTED". H1: "Get a working Plutonium app in 15 minutes."
1153
+ - [ ] Lede: "Walk the path top to bottom, or skip to the part you need."
1154
+ - [ ] 8 numbered steps mirror the existing tutorial sidebar (Project Setup → Customizing UI), each linking to its tutorial page.
1155
+ - [ ] Sidebar has two blocks: "Already know your way around?" (Installation, Concepts, Generators reference) and "Need help?" (Discussions, Issues).
1156
+ - [ ] Existing prerequisites/installation/template content is NOT lost — preserve it elsewhere if not surfaced here. Move "Prerequisites" section from the old page to the bottom of `docs/getting-started/installation.md` if it isn't already there, or to a `Prerequisites` section above the rail in this page.
1157
+
1158
+ **Verify:** `yarn docs:dev` → `/getting-started/` → page renders, all 8 step links work, sidebar links work.
1159
+
1160
+ **Steps:**
1161
+
1162
+ - [ ] **Step 1: Read the current page** to capture all content (already done in plan-prep), and check `docs/getting-started/installation.md` to see if Prerequisites already lives there.
1163
+
1164
+ ```bash
1165
+ cat docs/getting-started/installation.md | head -40
1166
+ ```
1167
+
1168
+ If Prerequisites is missing from installation.md, append it.
1169
+
1170
+ - [ ] **Step 2: Rewrite `docs/getting-started/index.md`.**
1171
+
1172
+ ```markdown
1173
+ ---
1174
+ layout: page
1175
+ sidebar: false
1176
+ aside: false
1177
+ ---
1178
+
1179
+ <SectionLanding
1180
+ eyebrow="Getting Started"
1181
+ title="Get a working Plutonium app in 15 minutes."
1182
+ lede="Walk the path top to bottom, or skip to the part you need."
1183
+ mode="numbered"
1184
+ :rail="[
1185
+ { name: 'Project setup', desc: 'Bootstrap a Rails app with the Plutonium template.', link: '/plutonium-core/getting-started/tutorial/01-setup' },
1186
+ { name: 'First resource', desc: 'Model, definition, scaffold, connect to a portal.', link: '/plutonium-core/getting-started/tutorial/02-first-resource' },
1187
+ { name: 'Authentication', desc: 'Add Rodauth with login + signup.', link: '/plutonium-core/getting-started/tutorial/03-authentication' },
1188
+ { name: 'Authorization', desc: 'ActionPolicy-scoped resource access.', link: '/plutonium-core/getting-started/tutorial/04-authorization' },
1189
+ { name: 'Custom actions', desc: 'Add a domain-specific action to a resource.', link: '/plutonium-core/getting-started/tutorial/05-custom-actions' },
1190
+ { name: 'Nested resources', desc: 'Posts → Comments, scoped through routing.', link: '/plutonium-core/getting-started/tutorial/06-nested-resources' },
1191
+ { name: 'Author portal', desc: 'A second portal with its own auth and pages.', link: '/plutonium-core/getting-started/tutorial/07-author-portal' },
1192
+ { name: 'Customizing UI', desc: 'Theme tokens, custom Phlex components, layouts.', link: '/plutonium-core/getting-started/tutorial/08-customizing-ui' },
1193
+ ]"
1194
+ :sidebar="[
1195
+ { heading: 'Already know your way around?', items: [
1196
+ { label: 'Installation', href: '/plutonium-core/getting-started/installation', note: 'bootstrap a new app' },
1197
+ { label: 'Concepts overview', href: '/plutonium-core/reference/' },
1198
+ { label: 'Generators reference', href: '/plutonium-core/reference/app/generators' },
1199
+ ]},
1200
+ { heading: 'Need help?', items: [
1201
+ { label: 'GitHub Discussions', href: 'https://github.com/radioactive-labs/plutonium-core/discussions' },
1202
+ { label: 'Open an issue', href: 'https://github.com/radioactive-labs/plutonium-core/issues' },
1203
+ ]},
1204
+ ]"
1205
+ />
1206
+ ```
1207
+
1208
+ - [ ] **Step 3: Visually verify.** Click each step link; confirm it routes to the tutorial pages.
1209
+
1210
+ - [ ] **Step 4: Commit.**
1211
+
1212
+ ```bash
1213
+ git add docs/getting-started/index.md docs/getting-started/installation.md
1214
+ git commit -m "feat(docs): getting started landing — Pattern B"
1215
+ ```
1216
+
1217
+ ---
1218
+
1219
+ ## Task 10: Guides landing page
1220
+
1221
+ **Goal:** Rewrite `docs/guides/index.md` using `<SectionLanding>` in categorized mode.
1222
+
1223
+ **Files:**
1224
+ - Modify (rewrite): `docs/guides/index.md`
1225
+
1226
+ **Acceptance Criteria:**
1227
+ - [ ] Page uses `<SectionLanding>` with `mode="categorized"`.
1228
+ - [ ] Eyebrow: "GUIDES". H1: "How to do the things Plutonium apps do."
1229
+ - [ ] Lede: "Task-oriented walkthroughs for the parts of the framework you reach for most."
1230
+ - [ ] Categories: Setup & Resources, Auth, Features, Customization, Quality. Each category lists existing guide links from current `docs/guides/index.md`.
1231
+ - [ ] Sidebar: "New to Plutonium?" (tutorial link), "Looking for APIs?" (reference link), "Need help?" (Discussions).
1232
+ - [ ] Existing "I want to..." quick-task table is preserved — move it below the `<SectionLanding>` block, or drop it (the rail itself fulfils the same orientation purpose). Preserve it for now to avoid losing scannability.
1233
+
1234
+ **Verify:** `yarn docs:dev` → `/guides/` → categorized list renders, all guide links work.
1235
+
1236
+ **Steps:**
1237
+
1238
+ - [ ] **Step 1: Rewrite `docs/guides/index.md`.**
1239
+
1240
+ ```markdown
1241
+ ---
1242
+ layout: page
1243
+ sidebar: false
1244
+ aside: false
1245
+ ---
1246
+
1247
+ <SectionLanding
1248
+ eyebrow="Guides"
1249
+ title="How to do the things Plutonium apps do."
1250
+ lede="Task-oriented walkthroughs for the parts of the framework you reach for most."
1251
+ mode="categorized"
1252
+ :rail="[
1253
+ { group: 'Setup & Resources', items: [
1254
+ { name: 'Adding resources', link: '/plutonium-core/guides/adding-resources' },
1255
+ { name: 'Creating packages', link: '/plutonium-core/guides/creating-packages' },
1256
+ ]},
1257
+ { group: 'Auth', items: [
1258
+ { name: 'Authentication', link: '/plutonium-core/guides/authentication' },
1259
+ { name: 'Authorization', link: '/plutonium-core/guides/authorization' },
1260
+ ]},
1261
+ { group: 'Features', items: [
1262
+ { name: 'Custom actions', link: '/plutonium-core/guides/custom-actions' },
1263
+ { name: 'Nested resources', link: '/plutonium-core/guides/nested-resources' },
1264
+ { name: 'Multi-tenancy', link: '/plutonium-core/guides/multi-tenancy' },
1265
+ { name: 'Search & filtering', link: '/plutonium-core/guides/search-filtering' },
1266
+ { name: 'User invites', link: '/plutonium-core/guides/user-invites' },
1267
+ ]},
1268
+ { group: 'Customization', items: [
1269
+ { name: 'Theming', link: '/plutonium-core/guides/theming' },
1270
+ ]},
1271
+ { group: 'Quality', items: [
1272
+ { name: 'Testing', link: '/plutonium-core/guides/testing' },
1273
+ ]},
1274
+ ]"
1275
+ :sidebar="[
1276
+ { heading: 'New to Plutonium?', items: [
1277
+ { label: 'Start with the tutorial', href: '/plutonium-core/getting-started/tutorial/' },
1278
+ ]},
1279
+ { heading: 'Looking for APIs?', items: [
1280
+ { label: 'Browse the reference', href: '/plutonium-core/reference/' },
1281
+ ]},
1282
+ { heading: 'Need help?', items: [
1283
+ { label: 'GitHub Discussions', href: 'https://github.com/radioactive-labs/plutonium-core/discussions' },
1284
+ ]},
1285
+ ]"
1286
+ />
1287
+ ```
1288
+
1289
+ - [ ] **Step 2: Visually verify.** Click each guide link.
1290
+
1291
+ - [ ] **Step 3: Commit.**
1292
+
1293
+ ```bash
1294
+ git add docs/guides/index.md
1295
+ git commit -m "feat(docs): guides landing — Pattern B"
1296
+ ```
1297
+
1298
+ ---
1299
+
1300
+ ## Task 11: Reference landing page
1301
+
1302
+ **Goal:** Rewrite `docs/reference/index.md` using `<SectionLanding>` in categorized mode.
1303
+
1304
+ **Files:**
1305
+ - Modify (rewrite): `docs/reference/index.md`
1306
+
1307
+ **Acceptance Criteria:**
1308
+ - [ ] Page uses `<SectionLanding>` with `mode="categorized"`.
1309
+ - [ ] Eyebrow: "REFERENCE". H1: "Every API, in one place." Lede per spec.
1310
+ - [ ] Categories per spec: App / Resource / UI / Tooling — but using the actual reference structure already in the codebase. Verify against `docs/.vitepress/config.ts` reference sidebar.
1311
+ - [ ] Sidebar: "Learning?" (Tutorial, Concepts), "Solving a problem?" (Guides), "Need help?" (Discussions).
1312
+
1313
+ **Verify:** `yarn docs:dev` → `/reference/` → categorized list renders, all links resolve.
1314
+
1315
+ **Steps:**
1316
+
1317
+ - [ ] **Step 1: Inspect the reference sidebar config** to use the actual categories that exist:
1318
+
1319
+ ```bash
1320
+ grep -A 200 "'/reference/'" docs/.vitepress/config.ts | head -250
1321
+ ```
1322
+
1323
+ Match the categories in the new landing to the actual sidebar structure (App, Resource, Behavior, UI, Auth, Tenancy, Testing — note the spec mentions four but the existing sidebar has seven; use the seven actual ones since they're the real navigational structure).
1324
+
1325
+ - [ ] **Step 2: Rewrite `docs/reference/index.md`.**
1326
+
1327
+ ```markdown
1328
+ ---
1329
+ layout: page
1330
+ sidebar: false
1331
+ aside: false
1332
+ ---
1333
+
1334
+ <SectionLanding
1335
+ eyebrow="Reference"
1336
+ title="Every API, in one place."
1337
+ lede="The full surface area of Plutonium — controllers, policies, definitions, fields, interactions, generators."
1338
+ mode="categorized"
1339
+ :rail="[
1340
+ { group: 'App', items: [
1341
+ { name: 'Overview', link: '/plutonium-core/reference/app/' },
1342
+ { name: 'Packages', link: '/plutonium-core/reference/app/packages' },
1343
+ { name: 'Portals', link: '/plutonium-core/reference/app/portals' },
1344
+ { name: 'Generators', link: '/plutonium-core/reference/app/generators' },
1345
+ ]},
1346
+ { group: 'Resource', items: [
1347
+ { name: 'Definition', link: '/plutonium-core/reference/resource/definition' },
1348
+ { name: 'Query', link: '/plutonium-core/reference/resource/query' },
1349
+ { name: 'Actions', link: '/plutonium-core/reference/resource/actions' },
1350
+ ]},
1351
+ { group: 'Behavior', items: [
1352
+ { name: 'Controllers', link: '/plutonium-core/reference/behavior/controllers' },
1353
+ { name: 'Policies', link: '/plutonium-core/reference/behavior/policies' },
1354
+ { name: 'Interactions', link: '/plutonium-core/reference/behavior/interactions' },
1355
+ ]},
1356
+ { group: 'UI', items: [
1357
+ { name: 'Pages', link: '/plutonium-core/reference/ui/pages' },
1358
+ { name: 'Forms', link: '/plutonium-core/reference/ui/forms' },
1359
+ { name: 'Tables & displays', link: '/plutonium-core/reference/ui/tables' },
1360
+ { name: 'Assets & theming', link: '/plutonium-core/reference/ui/assets' },
1361
+ ]},
1362
+ { group: 'Auth', items: [
1363
+ { name: 'Accounts', link: '/plutonium-core/reference/auth/accounts' },
1364
+ { name: 'Profile', link: '/plutonium-core/reference/auth/profile' },
1365
+ ]},
1366
+ { group: 'Tenancy', items: [
1367
+ { name: 'Entity scoping', link: '/plutonium-core/reference/tenancy/entity-scoping' },
1368
+ { name: 'Invites', link: '/plutonium-core/reference/tenancy/invites' },
1369
+ ]},
1370
+ { group: 'Testing', items: [
1371
+ { name: 'Testing helpers', link: '/plutonium-core/reference/testing/' },
1372
+ ]},
1373
+ ]"
1374
+ :sidebar="[
1375
+ { heading: 'Learning?', items: [
1376
+ { label: 'Tutorial', href: '/plutonium-core/getting-started/tutorial/' },
1377
+ ]},
1378
+ { heading: 'Solving a problem?', items: [
1379
+ { label: 'Guides', href: '/plutonium-core/guides/' },
1380
+ ]},
1381
+ { heading: 'Need help?', items: [
1382
+ { label: 'GitHub Discussions', href: 'https://github.com/radioactive-labs/plutonium-core/discussions' },
1383
+ ]},
1384
+ ]"
1385
+ />
1386
+ ```
1387
+
1388
+ **IMPORTANT:** verify each `link:` resolves against the actual `docs/.vitepress/config.ts` reference sidebar. If a link in the example above (e.g., `tables-displays`) doesn't match the real path, fix it before committing. Don't link to nonexistent pages.
1389
+
1390
+ - [ ] **Step 3: Visually verify.** Click each link, confirm no 404s.
1391
+
1392
+ - [ ] **Step 4: Commit.**
1393
+
1394
+ ```bash
1395
+ git add docs/reference/index.md
1396
+ git commit -m "feat(docs): reference landing — Pattern B"
1397
+ ```
1398
+
1399
+ ---
1400
+
1401
+ ## Task 12: Demo app, asciinema recording, screenshots
1402
+
1403
+ **Goal:** Produce the three screenshots and one asciinema recording referenced by `HomeWalkthrough`.
1404
+
1405
+ **Files:**
1406
+ - Create: `docs/public/images/home-portal.png`
1407
+ - Create: `docs/public/images/home-index.png`
1408
+ - Create: `docs/public/images/home-form.png`
1409
+ - Create: `docs/public/asciinema/home-scaffold.cast`
1410
+
1411
+ **Acceptance Criteria:**
1412
+ - [ ] All four asset files exist at the paths above.
1413
+ - [ ] Screenshots are PNG, captured at 1280×800, light mode, with seeded data: at least 3 posts including a draft.
1414
+ - [ ] Asciinema cast covers the scaffold sequence: `rails new` (skipped or trimmed) → `pu:res:scaffold Post` → `pu:res:scaffold Comment` → `pu:res:conn` → `rails s`.
1415
+ - [ ] Cast trims to roughly 30 seconds of useful output (use `asciinema rec` then post-trim, or re-record with paced typing).
1416
+
1417
+ **Verify:** `ls -la docs/public/images/home-*.png docs/public/asciinema/home-scaffold.cast` → all four files present, non-empty.
1418
+
1419
+ **Steps:**
1420
+
1421
+ - [ ] **Step 1: Create a fresh demo app outside the project tree.**
1422
+
1423
+ ```bash
1424
+ mkdir -p /tmp/plutonium-demo && cd /tmp/plutonium-demo
1425
+ asciinema rec /tmp/home-scaffold-raw.cast
1426
+ ```
1427
+
1428
+ Inside the recording session, run:
1429
+ ```bash
1430
+ rails new blog -a propshaft -j esbuild -c tailwind \
1431
+ -m https://radioactive-labs.github.io/plutonium-core/templates/plutonium.rb
1432
+ cd blog
1433
+ rails g pu:res:scaffold Post title:string body:text published:boolean
1434
+ rails g pu:res:scaffold Comment 'post:references' body:text
1435
+ rails g pu:res:conn Post Comment --dest=admin_portal
1436
+ rails db:migrate
1437
+ ```
1438
+ Then exit the recording.
1439
+
1440
+ *(Note: `rails new` is verbose. Either record only the `pu:*` commands by starting asciinema after `rails new`, or use `asciinema-trim` / a re-recorded clean version for the final asset.)*
1441
+
1442
+ - [ ] **Step 2: Trim the cast** to a focused ~30s sequence showing only the `pu:*` commands and `rails s`. Save to `docs/public/asciinema/home-scaffold.cast`.
1443
+
1444
+ ```bash
1445
+ mkdir -p docs/public/asciinema
1446
+ cp /tmp/home-scaffold-trimmed.cast docs/public/asciinema/home-scaffold.cast
1447
+ ```
1448
+
1449
+ - [ ] **Step 3: Boot the demo app and seed data.**
1450
+
1451
+ ```bash
1452
+ cd /tmp/plutonium-demo/blog
1453
+ rails runner '
1454
+ 3.times { |i| Post.create!(title: "Hello world #{i+1}", body: "Lorem ipsum…", published: true) }
1455
+ Post.create!(title: "Draft post", body: "Consectetur…", published: false)
1456
+ '
1457
+ rails s
1458
+ ```
1459
+
1460
+ - [ ] **Step 4: Capture screenshots** with the macOS screenshot tool (`Cmd+Shift+4` then space → click window) at 1280×800 viewport.
1461
+
1462
+ - `home-portal.png` — wide shot of the admin portal with sidebar, nav, and posts table visible.
1463
+ - `home-index.png` — focused shot of `/admin/posts` table with the 4 seeded posts.
1464
+ - `home-form.png` — focused shot of `/admin/posts/new` form.
1465
+
1466
+ Save all three to `docs/public/images/`.
1467
+
1468
+ ```bash
1469
+ mkdir -p docs/public/images
1470
+ # move screenshots into place
1471
+ mv ~/Desktop/home-portal.png docs/public/images/
1472
+ mv ~/Desktop/home-index.png docs/public/images/
1473
+ mv ~/Desktop/home-form.png docs/public/images/
1474
+ ```
1475
+
1476
+ - [ ] **Step 5: Confirm assets exist and are non-empty.**
1477
+
1478
+ ```bash
1479
+ ls -la docs/public/images/home-*.png docs/public/asciinema/home-scaffold.cast
1480
+ ```
1481
+
1482
+ - [ ] **Step 6: Commit.**
1483
+
1484
+ ```bash
1485
+ git add docs/public/images/home-portal.png docs/public/images/home-index.png docs/public/images/home-form.png docs/public/asciinema/home-scaffold.cast
1486
+ git commit -m "feat(docs): walkthrough assets — screenshots and asciinema"
1487
+ ```
1488
+
1489
+ ---
1490
+
1491
+ ## Task 13: Wire assets into HomeWalkthrough
1492
+
1493
+ **Goal:** Replace the placeholder slots in `HomeWalkthrough.vue` with the real screenshots and asciinema embed.
1494
+
1495
+ **Files:**
1496
+ - Modify: `docs/.vitepress/theme/components/HomeWalkthrough.vue`
1497
+ - Modify: `docs/.vitepress/theme/index.ts` (add asciinema-player CSS+JS injection if needed)
1498
+
1499
+ **Acceptance Criteria:**
1500
+ - [ ] Wide portal screenshot renders in the hero ribbon.
1501
+ - [ ] Index and form screenshots render in the strip.
1502
+ - [ ] Asciinema cast plays in the strip via `asciinema-player`. Auto-loops, no controls overlay distracting in the small frame.
1503
+ - [ ] No console errors. No layout shift compared to placeholder version.
1504
+
1505
+ **Verify:** `yarn docs:dev` → home page → walkthrough section shows three real screenshots and a playing asciinema clip.
1506
+
1507
+ **Steps:**
1508
+
1509
+ - [ ] **Step 1: Add asciinema-player to the page.** In `docs/.vitepress/theme/index.ts`, inject the CDN-hosted player CSS and JS once on app mount:
1510
+
1511
+ ```ts
1512
+ import DefaultTheme from "vitepress/theme"
1513
+ import "./custom.css"
1514
+
1515
+ // ... existing component imports ...
1516
+
1517
+ export default {
1518
+ extends: DefaultTheme,
1519
+ enhanceApp({ app }) {
1520
+ // ... existing app.component(...) calls ...
1521
+
1522
+ if (typeof window !== "undefined") {
1523
+ const css = document.createElement("link")
1524
+ css.rel = "stylesheet"
1525
+ css.href = "https://cdn.jsdelivr.net/npm/asciinema-player@3.7.1/dist/bundle/asciinema-player.css"
1526
+ document.head.appendChild(css)
1527
+ }
1528
+ }
1529
+ }
1530
+ ```
1531
+
1532
+ The player itself will be loaded lazily inside the component (Step 2) so SSR doesn't crash.
1533
+
1534
+ - [ ] **Step 2: Update `HomeWalkthrough.vue`** to use real assets and lazy-load the asciinema player:
1535
+
1536
+ Replace the three placeholder slots:
1537
+ - Wide portal: `<img src="/plutonium-core/images/home-portal.png" alt="Plutonium admin portal" />`
1538
+ - Index: `<img src="/plutonium-core/images/home-index.png" alt="Posts index" />`
1539
+ - Form: `<img src="/plutonium-core/images/home-form.png" alt="New post form" />`
1540
+
1541
+ Replace the asciinema placeholder with a `<div ref="castEl" />` and add a `<script setup>`:
1542
+
1543
+ ```vue
1544
+ <script setup>
1545
+ import { ref, onMounted } from "vue"
1546
+
1547
+ const castEl = ref(null)
1548
+
1549
+ onMounted(async () => {
1550
+ if (!castEl.value) return
1551
+ const mod = await import("https://cdn.jsdelivr.net/npm/asciinema-player@3.7.1/dist/bundle/asciinema-player.min.js")
1552
+ mod.create("/plutonium-core/asciinema/home-scaffold.cast", castEl.value, {
1553
+ autoPlay: true,
1554
+ loop: true,
1555
+ controls: false,
1556
+ fit: "width",
1557
+ terminalFontSize: "small",
1558
+ })
1559
+ })
1560
+ </script>
1561
+ ```
1562
+
1563
+ The image slots replace `.hw-placeholder` with `<img>` styled to fit the same aspect ratios:
1564
+
1565
+ ```css
1566
+ .hw-shot { width: 100%; aspect-ratio: 21/8; object-fit: cover; display: block; }
1567
+ .hw-shot--small { width: 100%; aspect-ratio: 4/3; object-fit: cover; display: block; }
1568
+ .hw-cast { aspect-ratio: 4/3; }
1569
+ ```
1570
+
1571
+ - [ ] **Step 3: Visually verify.** Hard-refresh; cast should auto-play and loop; screenshots should render sharply.
1572
+
1573
+ - [ ] **Step 4: Commit.**
1574
+
1575
+ ```bash
1576
+ git add docs/.vitepress/theme/components/HomeWalkthrough.vue docs/.vitepress/theme/index.ts
1577
+ git commit -m "feat(docs): wire walkthrough assets — screenshots + asciinema"
1578
+ ```
1579
+
1580
+ ---
1581
+
1582
+ ## Task 14: Visual sweep + user verification
1583
+
1584
+ **Goal:** Catch any cross-cutting issues (broken links, dark-mode bugs, mobile layout issues) and get the user's visual sign-off on all four pages.
1585
+
1586
+ **Files:** None (review only).
1587
+
1588
+ **Acceptance Criteria:**
1589
+ - [ ] All four pages render in light and dark modes.
1590
+ - [ ] All internal links resolve (no 404s when clicking through).
1591
+ - [ ] No console errors on any page.
1592
+ - [ ] User has visually approved all four pages via AskUserQuestion.
1593
+
1594
+ **Verify:** `yarn docs:dev` running; manual click-through; AskUserQuestion answered.
1595
+
1596
+ **User Verification Required:**
1597
+ Before marking this task complete, you MUST call AskUserQuestion:
1598
+ ```yaml
1599
+ AskUserQuestion:
1600
+ question: "All four public pages are live in `yarn docs:dev`. Have you reviewed Home, Getting Started, Guides, and Reference and are they ready to ship?"
1601
+ header: "Verification"
1602
+ options:
1603
+ - label: "Approved"
1604
+ description: "Pages look right — ready to merge"
1605
+ - label: "Needs rework"
1606
+ description: "Something's off — I'll describe what to fix"
1607
+ ```
1608
+
1609
+ **If the user selects "Needs rework":** Apply the fixes they describe, then re-verify with AskUserQuestion before marking complete.
1610
+
1611
+ **Steps:**
1612
+
1613
+ - [ ] **Step 1: Run the visual sweep.**
1614
+
1615
+ ```bash
1616
+ yarn docs:dev
1617
+ ```
1618
+
1619
+ Click through each page, toggle dark mode, narrow the viewport to ~400px and confirm responsive breakpoints. Open DevTools and watch for console errors.
1620
+
1621
+ - [ ] **Step 2: Fix any issues found** in the sweep with targeted edits. Commit each fix with a focused message.
1622
+
1623
+ - [ ] **Step 3: Build the production bundle** to catch build-time errors that don't surface in dev:
1624
+
1625
+ ```bash
1626
+ yarn docs:build
1627
+ ```
1628
+
1629
+ Should complete without errors. If it errors, fix and re-run before proceeding.
1630
+
1631
+ - [ ] **Step 4: Call AskUserQuestion (the one in the User Verification block above).** Wait for the answer.
1632
+
1633
+ - [ ] **Step 5: Handle the response.**
1634
+ - If "Approved": mark this task complete.
1635
+ - If "Needs rework": apply fixes, then call AskUserQuestion again. Do not mark complete until "Approved".
1636
+
1637
+ ```json:metadata
1638
+ {"files": [], "verifyCommand": "yarn docs:build", "acceptanceCriteria": ["all 4 pages render in both modes", "no console errors", "user approved"], "requiresUserVerification": true, "userVerificationPrompt": "All four public pages are live in `yarn docs:dev`. Have you reviewed Home, Getting Started, Guides, and Reference and are they ready to ship?"}
1639
+ ```
1640
+
1641
+ ---
1642
+
1643
+ ## Self-Review Notes (inline)
1644
+
1645
+ - **Spec coverage:** Every section in the spec maps to a task. Hero → T1. Sec 1 → T2. Sec 2 → T3. Sec 3 → T4 (layout) + T13 (assets). Sec 4 → T5. Sec 5 → T6. Sec 6 → T7. Section landings (3) → T8 (shared) + T9, T10, T11. Assets → T12. Visual system → T0. Out-of-scope items confirmed not added.
1646
+ - **Placeholders:** None — all code blocks contain actual content. Asset captures in T12 are concrete commands, not "TBD".
1647
+ - **Type consistency:** `SectionLanding` props (`rail`, `mode`, `sidebar`) are used identically in T9/T10/T11. Component names (`HomeHero` etc.) match between create/register/use steps.
1648
+ - **Verification scan:** YES → T14 includes a `requiresUserVerification: true` task with the standard verification block.