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.
- checksums.yaml +4 -4
- data/.claude/skills/plutonium-app/SKILL.md +2 -0
- data/.claude/skills/plutonium-auth/SKILL.md +6 -4
- data/.claude/skills/plutonium-behavior/SKILL.md +1 -1
- data/.claude/skills/plutonium-tenancy/SKILL.md +25 -6
- data/.claude/skills/plutonium-testing/SKILL.md +3 -1
- data/.claude/skills/plutonium-ui/SKILL.md +3 -3
- data/CHANGELOG.md +17 -0
- data/app/assets/plutonium.css +1 -1
- data/app/assets/plutonium.js +1 -0
- data/app/assets/plutonium.js.map +3 -3
- data/app/assets/plutonium.min.js +1 -1
- data/app/assets/plutonium.min.js.map +3 -3
- data/docs/.vitepress/config.ts +1 -2
- data/docs/.vitepress/theme/components/HomeAudienceSplit.vue +53 -0
- data/docs/.vitepress/theme/components/HomeCta.vue +108 -0
- data/docs/.vitepress/theme/components/HomeHero.vue +70 -0
- data/docs/.vitepress/theme/components/HomeInTheBox.vue +74 -0
- data/docs/.vitepress/theme/components/HomePillars.vue +42 -0
- data/docs/.vitepress/theme/components/HomeStopWriting.vue +49 -0
- data/docs/.vitepress/theme/components/HomeWalkthrough.vue +111 -0
- data/docs/.vitepress/theme/components/SectionLanding.vue +115 -0
- data/docs/.vitepress/theme/custom.css +144 -0
- data/docs/.vitepress/theme/index.ts +58 -1
- data/docs/getting-started/index.md +33 -50
- data/docs/getting-started/tutorial/02-first-resource.md +17 -8
- data/docs/getting-started/tutorial/03-authentication.md +31 -23
- data/docs/getting-started/tutorial/05-custom-actions.md +9 -4
- data/docs/getting-started/tutorial/06-nested-resources.md +7 -1
- data/docs/getting-started/tutorial/07-author-portal.md +8 -0
- data/docs/getting-started/tutorial/08-customizing-ui.md +4 -0
- data/docs/guides/authentication.md +10 -5
- data/docs/guides/authorization.md +3 -3
- data/docs/guides/creating-packages.md +8 -11
- data/docs/guides/custom-actions.md +6 -1
- data/docs/guides/customizing-ui.md +258 -0
- data/docs/guides/index.md +49 -32
- data/docs/guides/multi-tenancy.md +10 -2
- data/docs/guides/nested-resources.md +69 -0
- data/docs/guides/search-filtering.md +6 -0
- data/docs/guides/testing.md +5 -1
- data/docs/guides/theming.md +13 -0
- data/docs/guides/user-invites.md +10 -4
- data/docs/guides/user-profile.md +8 -0
- data/docs/index.md +10 -219
- data/docs/public/asciinema/home-scaffold.cast +305 -0
- data/docs/public/images/guides/custom-actions-bulk.png +0 -0
- data/docs/public/images/guides/multi-tenancy-dashboard.png +0 -0
- data/docs/public/images/guides/multi-tenancy-welcome.png +0 -0
- data/docs/public/images/guides/nested-inputs.png +0 -0
- data/docs/public/images/guides/nested-resources-tab.png +0 -0
- data/docs/public/images/guides/search-filtering-index.png +0 -0
- data/docs/public/images/guides/search-filtering-panel.png +0 -0
- data/docs/public/images/guides/theming-after.png +0 -0
- data/docs/public/images/guides/theming-before.png +0 -0
- data/docs/public/images/guides/user-invites-landing.png +0 -0
- data/docs/public/images/guides/user-profile-edit.png +0 -0
- data/docs/public/images/guides/user-profile-show.png +0 -0
- data/docs/public/images/home-index.png +0 -0
- data/docs/public/images/home-new.png +0 -0
- data/docs/public/images/home-show.png +0 -0
- data/docs/public/images/tutorial/02-empty-index.png +0 -0
- data/docs/public/images/tutorial/02-index-with-posts.png +0 -0
- data/docs/public/images/tutorial/02-new-form-modal.png +0 -0
- data/docs/public/images/tutorial/02-new-form.png +0 -0
- data/docs/public/images/tutorial/03-create-account.png +0 -0
- data/docs/public/images/tutorial/03-login.png +0 -0
- data/docs/public/images/tutorial/04-admin-index.png +0 -0
- data/docs/public/images/tutorial/05-actions-menu.png +0 -0
- data/docs/public/images/tutorial/05-row-actions.png +0 -0
- data/docs/public/images/tutorial/06-comments-tab.png +0 -0
- data/docs/public/images/tutorial/06-post-with-comments.png +0 -0
- data/docs/public/images/tutorial/07-author-dashboard.png +0 -0
- data/docs/public/images/tutorial/07-author-portal.png +0 -0
- data/docs/public/images/tutorial/08-customized-index.png +0 -0
- data/docs/reference/app/generators.md +4 -4
- data/docs/reference/auth/accounts.md +6 -7
- data/docs/reference/auth/index.md +1 -1
- data/docs/reference/behavior/policies.md +1 -1
- data/docs/reference/index.md +67 -55
- data/docs/reference/resource/definition.md +1 -1
- data/docs/reference/tenancy/entity-scoping.md +8 -1
- data/docs/reference/tenancy/index.md +1 -1
- data/docs/reference/tenancy/invites.md +12 -5
- data/docs/reference/ui/tables.md +8 -4
- data/docs/superpowers/plans/2026-05-15-public-pages-overhaul.md +1648 -0
- data/docs/superpowers/plans/2026-05-15-public-pages-overhaul.md.tasks.json +109 -0
- data/docs/superpowers/specs/2026-05-15-public-pages-overhaul-design.md +263 -0
- data/gemfiles/rails_7.gemfile.lock +1 -1
- data/gemfiles/rails_8.0.gemfile.lock +1 -1
- data/gemfiles/rails_8.1.gemfile.lock +1 -1
- data/lib/generators/pu/core/assets/assets_generator.rb +10 -0
- data/lib/generators/pu/invites/install_generator.rb +44 -0
- data/lib/generators/pu/invites/templates/packages/invites/app/controllers/invites/user_invitations_controller.rb.tt +1 -0
- data/lib/generators/pu/profile/conn_generator.rb +2 -2
- data/lib/generators/pu/res/conn/conn_generator.rb +33 -6
- data/lib/generators/pu/res/model/templates/model.rb.tt +4 -0
- data/lib/generators/pu/rodauth/account_generator.rb +2 -1
- data/lib/generators/pu/rodauth/admin_generator.rb +0 -2
- data/lib/generators/pu/rodauth/migration_generator.rb +0 -2
- data/lib/generators/pu/rodauth/views_generator.rb +0 -2
- data/lib/generators/pu/saas/membership/USAGE +4 -1
- data/lib/generators/pu/saas/setup_generator.rb +16 -4
- data/lib/generators/pu/saas/welcome/templates/app/controllers/welcome_controller.rb.tt +1 -1
- data/lib/plutonium/helpers/turbo_helper.rb +19 -0
- data/lib/plutonium/resource/controllers/crud_actions.rb +4 -4
- data/lib/plutonium/resource/controllers/interactive_actions.rb +3 -3
- data/lib/plutonium/ui/component/methods.rb +1 -0
- data/lib/plutonium/ui/form/base.rb +17 -1
- data/lib/plutonium/ui/form/components/secure_association.rb +11 -6
- data/lib/plutonium/ui/form/interaction.rb +1 -1
- data/lib/plutonium/ui/form/theme.rb +1 -1
- data/lib/plutonium/ui/page/edit.rb +1 -1
- data/lib/plutonium/ui/page/new.rb +1 -1
- data/lib/plutonium/ui/table/components/filter_form.rb +12 -4
- data/lib/plutonium/version.rb +1 -1
- data/package.json +4 -1
- data/src/js/controllers/form_controller.js +5 -4
- data/yarn.lock +108 -1
- 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 & 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.
|