plutonium 0.50.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 (201) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/plutonium/SKILL.md +85 -102
  3. data/.claude/skills/plutonium-app/SKILL.md +574 -0
  4. data/.claude/skills/plutonium-auth/SKILL.md +167 -302
  5. data/.claude/skills/plutonium-behavior/SKILL.md +838 -0
  6. data/.claude/skills/plutonium-resource/SKILL.md +1176 -0
  7. data/.claude/skills/plutonium-tenancy/SKILL.md +674 -0
  8. data/.claude/skills/plutonium-testing/SKILL.md +9 -6
  9. data/.claude/skills/plutonium-ui/SKILL.md +900 -0
  10. data/CHANGELOG.md +44 -2
  11. data/Rakefile +2 -1
  12. data/app/assets/plutonium.css +1 -11
  13. data/app/assets/plutonium.js +1010 -1214
  14. data/app/assets/plutonium.js.map +3 -3
  15. data/app/assets/plutonium.min.js +52 -51
  16. data/app/assets/plutonium.min.js.map +3 -3
  17. data/docs/.vitepress/config.ts +38 -29
  18. data/docs/.vitepress/theme/components/HomeAudienceSplit.vue +53 -0
  19. data/docs/.vitepress/theme/components/HomeCta.vue +108 -0
  20. data/docs/.vitepress/theme/components/HomeHero.vue +70 -0
  21. data/docs/.vitepress/theme/components/HomeInTheBox.vue +74 -0
  22. data/docs/.vitepress/theme/components/HomePillars.vue +42 -0
  23. data/docs/.vitepress/theme/components/HomeStopWriting.vue +49 -0
  24. data/docs/.vitepress/theme/components/HomeWalkthrough.vue +111 -0
  25. data/docs/.vitepress/theme/components/SectionLanding.vue +115 -0
  26. data/docs/.vitepress/theme/custom.css +144 -0
  27. data/docs/.vitepress/theme/index.ts +58 -1
  28. data/docs/getting-started/index.md +33 -57
  29. data/docs/getting-started/installation.md +37 -80
  30. data/docs/getting-started/tutorial/02-first-resource.md +17 -8
  31. data/docs/getting-started/tutorial/03-authentication.md +31 -23
  32. data/docs/getting-started/tutorial/05-custom-actions.md +9 -4
  33. data/docs/getting-started/tutorial/06-nested-resources.md +7 -1
  34. data/docs/getting-started/tutorial/07-author-portal.md +8 -0
  35. data/docs/getting-started/tutorial/08-customizing-ui.md +4 -0
  36. data/docs/getting-started/tutorial/index.md +4 -5
  37. data/docs/guides/adding-resources.md +66 -377
  38. data/docs/guides/authentication.md +98 -462
  39. data/docs/guides/authorization.md +124 -370
  40. data/docs/guides/creating-packages.md +93 -298
  41. data/docs/guides/custom-actions.md +126 -441
  42. data/docs/guides/customizing-ui.md +258 -0
  43. data/docs/guides/index.md +49 -52
  44. data/docs/guides/multi-tenancy.md +123 -186
  45. data/docs/guides/nested-resources.md +137 -396
  46. data/docs/guides/search-filtering.md +127 -238
  47. data/docs/guides/testing.md +10 -5
  48. data/docs/guides/theming.md +168 -405
  49. data/docs/guides/troubleshooting.md +5 -3
  50. data/docs/guides/user-invites.md +112 -425
  51. data/docs/guides/user-profile.md +82 -241
  52. data/docs/index.md +10 -219
  53. data/docs/public/asciinema/home-scaffold.cast +305 -0
  54. data/docs/public/images/guides/custom-actions-bulk.png +0 -0
  55. data/docs/public/images/guides/multi-tenancy-dashboard.png +0 -0
  56. data/docs/public/images/guides/multi-tenancy-welcome.png +0 -0
  57. data/docs/public/images/guides/nested-inputs.png +0 -0
  58. data/docs/public/images/guides/nested-resources-tab.png +0 -0
  59. data/docs/public/images/guides/search-filtering-index.png +0 -0
  60. data/docs/public/images/guides/search-filtering-panel.png +0 -0
  61. data/docs/public/images/guides/theming-after.png +0 -0
  62. data/docs/public/images/guides/theming-before.png +0 -0
  63. data/docs/public/images/guides/user-invites-landing.png +0 -0
  64. data/docs/public/images/guides/user-profile-edit.png +0 -0
  65. data/docs/public/images/guides/user-profile-show.png +0 -0
  66. data/docs/public/images/home-index.png +0 -0
  67. data/docs/public/images/home-new.png +0 -0
  68. data/docs/public/images/home-show.png +0 -0
  69. data/docs/public/images/tutorial/02-empty-index.png +0 -0
  70. data/docs/public/images/tutorial/02-index-with-posts.png +0 -0
  71. data/docs/public/images/tutorial/02-new-form-modal.png +0 -0
  72. data/docs/public/images/tutorial/02-new-form.png +0 -0
  73. data/docs/public/images/tutorial/03-create-account.png +0 -0
  74. data/docs/public/images/tutorial/03-login.png +0 -0
  75. data/docs/public/images/tutorial/04-admin-index.png +0 -0
  76. data/docs/public/images/tutorial/05-actions-menu.png +0 -0
  77. data/docs/public/images/tutorial/05-row-actions.png +0 -0
  78. data/docs/public/images/tutorial/06-comments-tab.png +0 -0
  79. data/docs/public/images/tutorial/06-post-with-comments.png +0 -0
  80. data/docs/public/images/tutorial/07-author-dashboard.png +0 -0
  81. data/docs/public/images/tutorial/07-author-portal.png +0 -0
  82. data/docs/public/images/tutorial/08-customized-index.png +0 -0
  83. data/docs/reference/app/generators.md +517 -0
  84. data/docs/reference/app/index.md +158 -0
  85. data/docs/reference/app/packages.md +146 -0
  86. data/docs/reference/app/portals.md +377 -0
  87. data/docs/reference/auth/accounts.md +229 -0
  88. data/docs/reference/auth/index.md +88 -0
  89. data/docs/reference/auth/profile.md +185 -0
  90. data/docs/reference/behavior/controllers.md +395 -0
  91. data/docs/reference/behavior/index.md +22 -0
  92. data/docs/reference/behavior/interactions.md +341 -0
  93. data/docs/reference/behavior/policies.md +417 -0
  94. data/docs/reference/index.md +67 -48
  95. data/docs/reference/resource/actions.md +423 -0
  96. data/docs/reference/resource/definition.md +508 -0
  97. data/docs/reference/resource/index.md +50 -0
  98. data/docs/reference/resource/model.md +348 -0
  99. data/docs/reference/resource/query.md +305 -0
  100. data/docs/reference/tenancy/entity-scoping.md +368 -0
  101. data/docs/reference/tenancy/index.md +36 -0
  102. data/docs/reference/tenancy/invites.md +400 -0
  103. data/docs/reference/tenancy/nested-resources.md +267 -0
  104. data/docs/reference/testing/index.md +287 -0
  105. data/docs/reference/ui/assets.md +400 -0
  106. data/docs/reference/ui/components.md +165 -0
  107. data/docs/reference/ui/displays.md +104 -0
  108. data/docs/reference/ui/forms.md +284 -0
  109. data/docs/reference/ui/index.md +30 -0
  110. data/docs/reference/ui/layouts.md +106 -0
  111. data/docs/reference/ui/pages.md +189 -0
  112. data/docs/reference/ui/tables.md +121 -0
  113. data/docs/superpowers/plans/2026-05-15-public-pages-overhaul.md +1648 -0
  114. data/docs/superpowers/plans/2026-05-15-public-pages-overhaul.md.tasks.json +109 -0
  115. data/docs/superpowers/specs/2026-05-09-typeahead-endpoint-design.md +203 -0
  116. data/docs/superpowers/specs/2026-05-12-skill-compaction-design.md +99 -0
  117. data/docs/superpowers/specs/2026-05-13-docs-restructure-design.md +186 -0
  118. data/docs/superpowers/specs/2026-05-15-public-pages-overhaul-design.md +263 -0
  119. data/gemfiles/rails_7.gemfile.lock +1 -1
  120. data/gemfiles/rails_8.0.gemfile.lock +1 -1
  121. data/gemfiles/rails_8.1.gemfile.lock +1 -1
  122. data/lib/generators/pu/core/assets/assets_generator.rb +10 -0
  123. data/lib/generators/pu/core/update/update_generator.rb +0 -20
  124. data/lib/generators/pu/invites/install_generator.rb +45 -0
  125. data/lib/generators/pu/invites/templates/packages/invites/app/controllers/invites/user_invitations_controller.rb.tt +1 -0
  126. data/lib/generators/pu/profile/conn_generator.rb +2 -2
  127. data/lib/generators/pu/res/conn/conn_generator.rb +33 -6
  128. data/lib/generators/pu/res/model/templates/model.rb.tt +4 -0
  129. data/lib/generators/pu/rodauth/account_generator.rb +2 -1
  130. data/lib/generators/pu/rodauth/admin_generator.rb +0 -2
  131. data/lib/generators/pu/rodauth/migration_generator.rb +0 -2
  132. data/lib/generators/pu/rodauth/views_generator.rb +0 -2
  133. data/lib/generators/pu/saas/membership/USAGE +4 -1
  134. data/lib/generators/pu/saas/setup_generator.rb +16 -4
  135. data/lib/generators/pu/saas/welcome/templates/app/controllers/welcome_controller.rb.tt +1 -1
  136. data/lib/plutonium/definition/base.rb +1 -1
  137. data/lib/plutonium/definition/{views.rb → index_views.rb} +21 -20
  138. data/lib/plutonium/helpers/turbo_helper.rb +30 -0
  139. data/lib/plutonium/helpers/turbo_stream_actions_helper.rb +14 -0
  140. data/lib/plutonium/resource/controller.rb +1 -0
  141. data/lib/plutonium/resource/controllers/crud_actions.rb +23 -5
  142. data/lib/plutonium/resource/controllers/interactive_actions.rb +3 -3
  143. data/lib/plutonium/resource/controllers/typeahead.rb +180 -0
  144. data/lib/plutonium/resource/policy.rb +7 -0
  145. data/lib/plutonium/routing/mapper_extensions.rb +15 -0
  146. data/lib/plutonium/ui/component/methods.rb +5 -0
  147. data/lib/plutonium/ui/form/base.rb +23 -3
  148. data/lib/plutonium/ui/form/components/json.rb +58 -0
  149. data/lib/plutonium/ui/form/components/resource_select.rb +62 -8
  150. data/lib/plutonium/ui/form/components/secure_association.rb +103 -22
  151. data/lib/plutonium/ui/form/concerns/typeahead_attributes.rb +83 -0
  152. data/lib/plutonium/ui/form/interaction.rb +1 -1
  153. data/lib/plutonium/ui/form/resource.rb +0 -4
  154. data/lib/plutonium/ui/form/theme.rb +1 -1
  155. data/lib/plutonium/ui/grid/resource.rb +1 -1
  156. data/lib/plutonium/ui/layout/base.rb +1 -0
  157. data/lib/plutonium/ui/page/base.rb +0 -7
  158. data/lib/plutonium/ui/page/edit.rb +1 -1
  159. data/lib/plutonium/ui/page/index.rb +4 -4
  160. data/lib/plutonium/ui/page/new.rb +1 -1
  161. data/lib/plutonium/ui/table/components/filter_form.rb +12 -4
  162. data/lib/plutonium/ui/table/resource.rb +1 -1
  163. data/lib/plutonium/version.rb +1 -1
  164. data/lib/plutonium.rb +8 -0
  165. data/lib/tasks/release.rake +15 -1
  166. data/package.json +13 -10
  167. data/src/css/slim_select.css +4 -0
  168. data/src/js/controllers/form_controller.js +5 -4
  169. data/src/js/controllers/slim_select_controller.js +61 -0
  170. data/src/js/turbo/turbo_actions.js +33 -0
  171. data/yarn.lock +661 -544
  172. metadata +86 -33
  173. data/.claude/skills/plutonium-assets/SKILL.md +0 -512
  174. data/.claude/skills/plutonium-controller/SKILL.md +0 -396
  175. data/.claude/skills/plutonium-create-resource/SKILL.md +0 -303
  176. data/.claude/skills/plutonium-definition/SKILL.md +0 -1223
  177. data/.claude/skills/plutonium-entity-scoping/SKILL.md +0 -317
  178. data/.claude/skills/plutonium-forms/SKILL.md +0 -465
  179. data/.claude/skills/plutonium-installation/SKILL.md +0 -331
  180. data/.claude/skills/plutonium-interaction/SKILL.md +0 -413
  181. data/.claude/skills/plutonium-invites/SKILL.md +0 -408
  182. data/.claude/skills/plutonium-model/SKILL.md +0 -440
  183. data/.claude/skills/plutonium-nested-resources/SKILL.md +0 -360
  184. data/.claude/skills/plutonium-package/SKILL.md +0 -198
  185. data/.claude/skills/plutonium-policy/SKILL.md +0 -456
  186. data/.claude/skills/plutonium-portal/SKILL.md +0 -410
  187. data/.claude/skills/plutonium-views/SKILL.md +0 -651
  188. data/docs/reference/assets/index.md +0 -496
  189. data/docs/reference/controller/index.md +0 -412
  190. data/docs/reference/definition/actions.md +0 -462
  191. data/docs/reference/definition/fields.md +0 -383
  192. data/docs/reference/definition/index.md +0 -326
  193. data/docs/reference/definition/query.md +0 -351
  194. data/docs/reference/generators/index.md +0 -648
  195. data/docs/reference/interaction/index.md +0 -449
  196. data/docs/reference/model/features.md +0 -248
  197. data/docs/reference/model/index.md +0 -218
  198. data/docs/reference/policy/index.md +0 -456
  199. data/docs/reference/portal/index.md +0 -379
  200. data/docs/reference/views/forms.md +0 -411
  201. data/docs/reference/views/index.md +0 -544
@@ -0,0 +1,115 @@
1
+ <template>
2
+ <section class="pu-section sl-section">
3
+ <div class="pu-section-inner">
4
+ <div class="pu-eyebrow">{{ eyebrow }}</div>
5
+ <h1 class="sl-h1">{{ title }}</h1>
6
+ <p class="sl-lede">{{ lede }}</p>
7
+
8
+ <div class="sl-grid">
9
+ <div class="sl-rail">
10
+ <template v-if="mode === 'numbered'">
11
+ <a
12
+ v-for="(step, i) in rail"
13
+ :key="i"
14
+ :href="step.link"
15
+ :class="['sl-step', { 'sl-step--link': step.link }]"
16
+ >
17
+ <span class="sl-num">{{ i + 1 }}</span>
18
+ <span class="sl-step-body">
19
+ <span class="sl-step-name">{{ step.name }}</span>
20
+ <span v-if="step.desc" class="sl-step-desc">{{ step.desc }}</span>
21
+ </span>
22
+ </a>
23
+ </template>
24
+ <template v-else>
25
+ <div v-for="grp in rail" :key="grp.group" class="sl-group">
26
+ <div class="sl-group-name">{{ grp.group }}</div>
27
+ <a
28
+ v-for="item in grp.items"
29
+ :key="item.name"
30
+ :href="item.link"
31
+ class="sl-step sl-step--link sl-step--cat"
32
+ >
33
+ <span class="sl-step-body">
34
+ <span class="sl-step-name">{{ item.name }}</span>
35
+ <span v-if="item.desc" class="sl-step-desc">{{ item.desc }}</span>
36
+ </span>
37
+ <IconArrowRight class="sl-step-arrow" :size="16" :stroke-width="2" />
38
+ </a>
39
+ </div>
40
+ </template>
41
+ </div>
42
+
43
+ <aside class="sl-aside">
44
+ <div v-for="block in sidebar" :key="block.heading" class="sl-aside-block">
45
+ <h4 class="sl-aside-heading">{{ block.heading }}</h4>
46
+ <ul>
47
+ <li v-for="item in block.items" :key="item.label">
48
+ <a :href="item.href">{{ item.label }}</a>
49
+ <span v-if="item.note" class="sl-aside-note"> — {{ item.note }}</span>
50
+ </li>
51
+ </ul>
52
+ </div>
53
+ </aside>
54
+ </div>
55
+ </div>
56
+ </section>
57
+ </template>
58
+
59
+ <script setup>
60
+ import { IconArrowRight } from "@tabler/icons-vue"
61
+
62
+ defineProps({
63
+ eyebrow: { type: String, required: true },
64
+ title: { type: String, required: true },
65
+ lede: { type: String, required: true },
66
+ rail: { type: Array, required: true },
67
+ mode: { type: String, default: "numbered", validator: v => ["numbered", "categorized"].includes(v) },
68
+ sidebar: { type: Array, default: () => [] },
69
+ })
70
+ </script>
71
+
72
+ <style scoped>
73
+ .sl-section { padding: 64px 24px 96px; }
74
+ .sl-h1 { font-size: 36px; letter-spacing: -0.025em; margin: 0 0 12px; color: var(--pu-text); }
75
+ .sl-lede { font-size: 16px; color: var(--pu-text-muted); max-width: 640px; margin: 0 0 40px; line-height: 1.55; }
76
+ .sl-grid { display: grid; grid-template-columns: 1.4fr 1fr; gap: 48px; }
77
+ .sl-rail { border-left: 2px solid var(--pu-accent); padding-left: 24px; }
78
+ .sl-group + .sl-group { margin-top: 22px; }
79
+ .sl-group-name {
80
+ font-size: 11px; text-transform: uppercase; letter-spacing: 0.1em;
81
+ color: var(--pu-accent); font-weight: 600; margin-bottom: 8px;
82
+ }
83
+ .sl-step {
84
+ display: flex; gap: 12px; align-items: flex-start;
85
+ padding: 12px 0; border-bottom: 1px solid var(--pu-border-soft);
86
+ color: var(--pu-text); text-decoration: none;
87
+ }
88
+ .sl-step:last-child { border-bottom: none; }
89
+ .sl-step--cat { justify-content: space-between; align-items: center; }
90
+ .sl-step--link:hover .sl-step-name { color: var(--pu-accent); }
91
+ .sl-step--link:hover .sl-step-arrow { transform: translateX(2px); color: var(--pu-accent); }
92
+ .sl-num {
93
+ flex-shrink: 0; width: 24px; height: 24px; line-height: 24px; text-align: center;
94
+ background: var(--pu-accent); color: #fff; border-radius: 50%;
95
+ font-size: 11px; font-weight: 600;
96
+ }
97
+ .sl-step-body { display: flex; flex-direction: column; gap: 2px; flex: 1; }
98
+ .sl-step-name { font-weight: 600; font-size: 14px; }
99
+ .sl-step-desc { font-size: 12.5px; color: var(--pu-text-muted); }
100
+ .sl-step-arrow { color: var(--pu-text-faint); transition: transform 0.15s ease, color 0.15s ease; flex-shrink: 0; }
101
+
102
+ .sl-aside-block + .sl-aside-block { margin-top: 28px; }
103
+ .sl-aside-heading {
104
+ font-size: 11px; text-transform: uppercase; letter-spacing: 0.1em;
105
+ color: var(--pu-text-faint); margin: 0 0 10px; font-weight: 600;
106
+ }
107
+ .sl-aside ul { list-style: none; padding: 0; margin: 0; font-size: 14px; line-height: 1.85; }
108
+ .sl-aside a { color: var(--pu-accent); text-decoration: none; font-weight: 500; }
109
+ .sl-aside a:hover { text-decoration: underline; }
110
+ .sl-aside-note { color: var(--pu-text-muted); font-weight: 400; }
111
+
112
+ @media (max-width: 900px) {
113
+ .sl-grid { grid-template-columns: 1fr; gap: 32px; }
114
+ }
115
+ </style>
@@ -418,3 +418,147 @@ Brand Colors:
418
418
  line-height: 1.5;
419
419
  padding-bottom: 4px;
420
420
  }
421
+
422
+ /* ===== Public-pages design tokens (2026-05) ===== */
423
+ :root {
424
+ --pu-bg-dark: #0d1117;
425
+ --pu-bg-dark-2: #161b22;
426
+ --pu-bg-light: #ffffff;
427
+ --pu-bg-band: #fafafa;
428
+ --pu-border: #e0e0e0;
429
+ --pu-border-soft: #ececec;
430
+ --pu-text: #1a1a1a;
431
+ --pu-text-muted: #666666;
432
+ --pu-text-faint: #888888;
433
+ --pu-accent: #d33;
434
+ --pu-accent-soft: #fff7f7;
435
+ --pu-success-bg: #ecf9f1;
436
+ --pu-success-fg: #0a7c3f;
437
+ --pu-warn-bg: #fdf3e6;
438
+ --pu-warn-fg: #a86b00;
439
+ --pu-term-prompt: #7ee787;
440
+ --pu-term-cursor: #58a6ff;
441
+ --pu-term-text: #e6edf3;
442
+ }
443
+
444
+ .dark {
445
+ --pu-bg-light: #0d1117;
446
+ --pu-bg-band: #161b22;
447
+ --pu-text: #e6edf3;
448
+ --pu-text-muted: #9da7b1;
449
+ --pu-text-faint: #6e7681;
450
+ --pu-border: #30363d;
451
+ --pu-border-soft: #21262d;
452
+ }
453
+
454
+ /* Eyebrow */
455
+ .pu-eyebrow {
456
+ font-size: 11px;
457
+ text-transform: uppercase;
458
+ letter-spacing: 0.1em;
459
+ color: var(--pu-accent);
460
+ font-weight: 600;
461
+ margin-bottom: 8px;
462
+ }
463
+ .pu-eyebrow--muted { color: var(--pu-text-faint); }
464
+
465
+ /* Buttons */
466
+ .pu-btn {
467
+ display: inline-block;
468
+ padding: 10px 18px;
469
+ border-radius: 6px;
470
+ font-size: 14px;
471
+ font-weight: 500;
472
+ text-decoration: none;
473
+ transition: opacity 0.15s ease;
474
+ }
475
+ .pu-btn:hover { opacity: 0.85; }
476
+ .pu-btn:focus-visible {
477
+ outline: 2px solid var(--pu-accent);
478
+ outline-offset: 2px;
479
+ }
480
+ .pu-btn-primary { background: var(--pu-accent); color: #ffffff; }
481
+ .pu-btn-ghost {
482
+ border: 1px solid currentColor;
483
+ color: var(--pu-text);
484
+ opacity: 0.85;
485
+ }
486
+ .pu-btn-ghost.on-dark { color: #e6edf3; border-color: rgba(255,255,255,0.25); }
487
+
488
+ /* Terminal block */
489
+ .pu-term {
490
+ background: var(--pu-bg-dark);
491
+ color: var(--pu-term-text);
492
+ border-radius: 8px;
493
+ padding: 16px 18px;
494
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
495
+ font-size: 13px;
496
+ line-height: 1.7;
497
+ overflow-x: auto;
498
+ }
499
+ .pu-term--inline { padding: 12px 14px; font-size: 12.5px; }
500
+ .pu-term .prompt { color: var(--pu-term-prompt); }
501
+ .pu-term .dim { opacity: 0.55; }
502
+ .pu-term-cursor {
503
+ background: var(--pu-term-cursor);
504
+ display: inline-block;
505
+ width: 7px;
506
+ height: 13px;
507
+ vertical-align: text-bottom;
508
+ animation: pu-blink 1s steps(2) infinite;
509
+ }
510
+ @keyframes pu-blink { 50% { opacity: 0; } }
511
+ @media (prefers-reduced-motion: reduce) {
512
+ .pu-term-cursor { animation: none; }
513
+ }
514
+
515
+ /* Section frames */
516
+ .pu-section {
517
+ padding: 64px 24px;
518
+ }
519
+ .pu-section--dark {
520
+ background: var(--pu-bg-dark);
521
+ color: var(--pu-term-text);
522
+ }
523
+ .pu-section--band {
524
+ background: var(--pu-bg-band);
525
+ }
526
+ .pu-section .pu-section-inner {
527
+ max-width: 1100px;
528
+ margin: 0 auto;
529
+ }
530
+ .pu-section-title {
531
+ font-size: 28px;
532
+ letter-spacing: -0.02em;
533
+ margin: 0 0 24px;
534
+ color: var(--pu-text);
535
+ }
536
+ .pu-section--dark .pu-section-title { color: var(--pu-term-text); }
537
+
538
+ .vp-doc img:not(a img),
539
+ img.pu-zoomable { cursor: zoom-in; }
540
+ .medium-zoom-overlay { z-index: 100; }
541
+ .medium-zoom-image--opened { z-index: 101; }
542
+
543
+ .pu-zoom-close {
544
+ position: fixed;
545
+ top: 16px;
546
+ right: 16px;
547
+ z-index: 102;
548
+ width: 40px;
549
+ height: 40px;
550
+ border: 1px solid var(--vp-c-divider);
551
+ background: var(--vp-c-bg-soft);
552
+ color: var(--vp-c-text-1);
553
+ border-radius: 50%;
554
+ font-size: 24px;
555
+ line-height: 1;
556
+ cursor: pointer;
557
+ display: inline-flex;
558
+ align-items: center;
559
+ justify-content: center;
560
+ padding: 0;
561
+ transition: background 0.15s ease, transform 0.15s ease;
562
+ }
563
+ .pu-zoom-close:hover { background: var(--vp-c-bg-mute); transform: scale(1.05); }
564
+ .pu-zoom-close:focus-visible { outline: 2px solid var(--vp-c-brand-1); outline-offset: 2px; }
@@ -1,4 +1,61 @@
1
1
  import DefaultTheme from "vitepress/theme"
2
+ import { onMounted, watch, nextTick } from "vue"
3
+ import { useRoute } from "vitepress"
4
+ import mediumZoom from "medium-zoom"
2
5
  import "./custom.css"
3
6
 
4
- export default DefaultTheme
7
+ import HomeHero from "./components/HomeHero.vue"
8
+ import HomeStopWriting from "./components/HomeStopWriting.vue"
9
+ import HomePillars from "./components/HomePillars.vue"
10
+ import HomeWalkthrough from "./components/HomeWalkthrough.vue"
11
+ import HomeAudienceSplit from "./components/HomeAudienceSplit.vue"
12
+ import HomeInTheBox from "./components/HomeInTheBox.vue"
13
+ import HomeCta from "./components/HomeCta.vue"
14
+ import SectionLanding from "./components/SectionLanding.vue"
15
+
16
+ export default {
17
+ extends: DefaultTheme,
18
+ enhanceApp({ app }) {
19
+ app.component("HomeHero", HomeHero)
20
+ app.component("HomeStopWriting", HomeStopWriting)
21
+ app.component("HomePillars", HomePillars)
22
+ app.component("HomeWalkthrough", HomeWalkthrough)
23
+ app.component("HomeAudienceSplit", HomeAudienceSplit)
24
+ app.component("HomeInTheBox", HomeInTheBox)
25
+ app.component("HomeCta", HomeCta)
26
+ app.component("SectionLanding", SectionLanding)
27
+ },
28
+ setup() {
29
+ const route = useRoute()
30
+
31
+ let closeBtn: HTMLButtonElement | null = null
32
+
33
+ const attachCloseButton = (z: ReturnType<typeof mediumZoom>) => {
34
+ z.on("opened", () => {
35
+ const overlay = document.querySelector<HTMLElement>(".medium-zoom-overlay")
36
+ if (!overlay) return
37
+ closeBtn = document.createElement("button")
38
+ closeBtn.type = "button"
39
+ closeBtn.setAttribute("aria-label", "Close")
40
+ closeBtn.className = "pu-zoom-close"
41
+ closeBtn.innerHTML = "&times;"
42
+ closeBtn.addEventListener("click", () => z.close())
43
+ document.body.appendChild(closeBtn)
44
+ })
45
+ z.on("close", () => {
46
+ closeBtn?.remove()
47
+ closeBtn = null
48
+ })
49
+ }
50
+
51
+ const zoom = () => {
52
+ const z = mediumZoom(".vp-doc img:not(a img), img.pu-zoomable", {
53
+ background: "var(--vp-c-bg)",
54
+ margin: 16,
55
+ })
56
+ attachCloseButton(z)
57
+ }
58
+ onMounted(() => nextTick(zoom))
59
+ watch(() => route.path, () => nextTick(zoom))
60
+ }
61
+ }
@@ -1,57 +1,33 @@
1
- # Getting Started
2
-
3
- Welcome to Plutonium! This guide will help you get up and running quickly.
4
-
5
- ## What You'll Learn
6
-
7
- - How to install Plutonium in a new or existing Rails application
8
- - The basic concepts behind Plutonium's architecture
9
- - How to create your first resource and connect it to a portal
10
-
11
- ## Prerequisites
12
-
13
- Before you begin, make sure you have:
14
-
15
- - **Ruby 3.2+** installed
16
- - **Rails 7.2+** (Rails 8 recommended)
17
- - **Node.js 18+** (for asset compilation)
18
- - Basic familiarity with Ruby on Rails
19
-
20
- ## Choose Your Path
21
-
22
- ### New Application
23
-
24
- If you're starting fresh, use our application template:
25
-
26
- ```bash
27
- rails new myapp -a propshaft -j esbuild -c tailwind \
28
- -m https://radioactive-labs.github.io/plutonium-core/templates/plutonium.rb
29
- ```
30
-
31
- This creates a fully configured Plutonium application with authentication ready to go.
32
-
33
- [Continue to Installation →](./installation)
34
-
35
- ### Existing Application
36
-
37
- Adding Plutonium to an existing Rails app requires a few more steps but is fully supported.
38
-
39
- [Continue to Installation →](./installation#existing-application)
40
-
41
- ### Tutorial
42
-
43
- Want to learn by building? Follow our step-by-step tutorial to create a complete blog application.
44
-
45
- [Start the Tutorial →](./tutorial/)
46
-
47
- ## Next Steps
48
-
49
- After installation, you'll typically:
50
-
51
- 1. **Create a Feature Package** - Organize your business logic
52
- 2. **Generate Resources** - Create your models and scaffolds
53
- 3. **Create a Portal** - Set up the web interface
54
- 4. **Connect Resources** - Make resources accessible through the portal
55
- 5. **Customize** - Override defaults as needed
56
-
57
- Each of these steps is covered in detail in the [Tutorial](./tutorial/).
1
+ ---
2
+ layout: page
3
+ sidebar: false
4
+ aside: false
5
+ ---
6
+
7
+ <SectionLanding
8
+ eyebrow="Getting Started"
9
+ title="Learn Plutonium by building."
10
+ lede="Walk the path top to bottom, or skip to the part you need."
11
+ mode="numbered"
12
+ :rail="[
13
+ { name: 'Project setup', desc: 'Bootstrap a Rails app with the Plutonium template.', link: '/plutonium-core/getting-started/tutorial/01-setup' },
14
+ { name: 'First resource', desc: 'Model, definition, scaffold, connect to a portal.', link: '/plutonium-core/getting-started/tutorial/02-first-resource' },
15
+ { name: 'Authentication', desc: 'Add Rodauth with login + signup.', link: '/plutonium-core/getting-started/tutorial/03-authentication' },
16
+ { name: 'Authorization', desc: 'ActionPolicy-scoped resource access.', link: '/plutonium-core/getting-started/tutorial/04-authorization' },
17
+ { name: 'Custom actions', desc: 'Add a domain-specific action to a resource.', link: '/plutonium-core/getting-started/tutorial/05-custom-actions' },
18
+ { name: 'Nested resources', desc: 'Posts → Comments, scoped through routing.', link: '/plutonium-core/getting-started/tutorial/06-nested-resources' },
19
+ { name: 'Author portal', desc: 'A second portal with its own auth and pages.', link: '/plutonium-core/getting-started/tutorial/07-author-portal' },
20
+ { name: 'Customizing UI', desc: 'Theme tokens, custom Phlex components, layouts.', link: '/plutonium-core/getting-started/tutorial/08-customizing-ui' },
21
+ ]"
22
+ :sidebar="[
23
+ { heading: 'Already know your way around?', items: [
24
+ { label: 'Installation', href: '/plutonium-core/getting-started/installation', note: 'bootstrap a new app' },
25
+ { label: 'Concepts overview', href: '/plutonium-core/reference/' },
26
+ { label: 'Generators reference', href: '/plutonium-core/reference/app/generators' },
27
+ ]},
28
+ { heading: 'Need help?', items: [
29
+ { label: 'GitHub Discussions', href: 'https://github.com/radioactive-labs/plutonium-core/discussions' },
30
+ { label: 'Open an issue', href: 'https://github.com/radioactive-labs/plutonium-core/issues' },
31
+ ]},
32
+ ]"
33
+ />
@@ -1,22 +1,15 @@
1
1
  # Installation
2
2
 
3
- This guide covers installing Plutonium in both new and existing Rails applications.
3
+ For full installation reference (configuration options, base classes, what `pu:core:install` creates), see [Reference › App](/reference/app/). This page covers the quickest path.
4
4
 
5
- ## New Application
6
-
7
- The fastest way to get started is with our application template:
5
+ ## New application
8
6
 
9
7
  ```bash
10
8
  rails new myapp -a propshaft -j esbuild -c tailwind \
11
9
  -m https://radioactive-labs.github.io/plutonium-core/templates/plutonium.rb
12
10
  ```
13
11
 
14
- This template:
15
- - Adds the Plutonium gem
16
- - Configures TailwindCSS 4 with Plutonium's theme
17
- - Sets up Rodauth for authentication
18
- - Creates initial migrations
19
- - Configures the asset pipeline
12
+ This sets up Rails with Propshaft, esbuild, TailwindCSS, and Plutonium — plus Rodauth auth, asset pipeline, and initial migrations.
20
13
 
21
14
  After the template completes:
22
15
 
@@ -26,122 +19,86 @@ rails db:migrate
26
19
  bin/dev
27
20
  ```
28
21
 
29
- Visit `http://localhost:3000` to see your new application.
22
+ Visit `http://localhost:3000`.
30
23
 
31
- ## Existing Application
24
+ ## Existing application
32
25
 
33
- ### Step 1: Add the Gem
26
+ ::: danger Use `base.rb`, not `plutonium.rb`
27
+ The `plutonium.rb` template re-runs full app bootstrap (dotenv, annotate, solid_*, asset config) and creates generic "initial commit" commits that clobber history. For any pre-existing app, always use `base.rb`.
28
+ :::
34
29
 
35
- Add Plutonium to your Gemfile:
30
+ ### Option 1: Template
36
31
 
37
- ```ruby
38
- gem "plutonium"
32
+ ```bash
33
+ bin/rails app:template \
34
+ LOCATION=https://radioactive-labs.github.io/plutonium-core/templates/base.rb
39
35
  ```
40
36
 
41
- Then install:
37
+ ### Option 2: Manual
42
38
 
43
- ```bash
44
- bundle install
39
+ ```ruby
40
+ # Gemfile
41
+ gem "plutonium"
45
42
  ```
46
43
 
47
- ### Step 2: Run the Installer
48
-
49
44
  ```bash
45
+ bundle install
50
46
  rails generate pu:core:install
51
47
  ```
52
48
 
53
- This generator:
54
- - Creates the Plutonium initializer
55
- - Adds required configurations
56
- - Sets up the asset pipeline integration
57
-
58
- ### Step 3: Install Rodauth (Optional)
59
-
60
- If you want Plutonium's built-in authentication:
49
+ ## Optional: authentication
61
50
 
62
51
  ```bash
63
52
  rails generate pu:rodauth:install
64
- ```
65
-
66
- This creates:
67
- - Rodauth configuration files
68
- - Account model and migrations
69
- - Email templates for authentication flows
70
-
71
- ### Step 4: Run Migrations
72
-
73
- ```bash
53
+ rails generate pu:rodauth:account user
74
54
  rails db:migrate
75
55
  ```
76
56
 
77
- ### Step 5: Configure Assets
57
+ For account options and customization, see [Reference › Auth](/reference/auth/) and [Guides › Authentication](/guides/authentication).
78
58
 
79
- Run the assets generator to set up TailwindCSS and Plutonium styles:
59
+ ## Optional: assets toolchain
80
60
 
81
61
  ```bash
82
62
  rails generate pu:core:assets
83
63
  ```
84
64
 
85
- This configures PostCSS, TailwindCSS, and imports Plutonium's styles into your application.
86
-
87
- ## Verifying Installation
65
+ Installs npm packages, creates `tailwind.config.js` extending Plutonium's config, imports Plutonium CSS, registers Stimulus controllers. Required if you want to customize the theme — see [Reference › UI › Assets](/reference/ui/assets) and [Guides › Theming](/guides/theming).
88
66
 
89
- After installation, verify everything is working:
67
+ ## Verify
90
68
 
91
69
  ```bash
92
70
  rails runner "puts Plutonium::VERSION"
93
71
  ```
94
72
 
95
- You should see the installed version number.
96
-
97
73
  ## Configuration
98
74
 
99
- Plutonium is configured in `config/initializers/plutonium.rb`:
100
-
101
75
  ```ruby
76
+ # config/initializers/plutonium.rb
102
77
  Plutonium.configure do |config|
103
- # Load default settings for version 1.0
104
78
  config.load_defaults 1.0
105
79
 
106
- # Development mode (auto-detected from PLUTONIUM_DEV env var)
107
- # config.development = true
108
-
109
- # Cache discovery (defaults to true in production, false in development)
110
- # config.cache_discovery = false
80
+ # config.shell = :classic # legacy chrome (only for upgrades)
111
81
 
112
- # Hot reloading (defaults to true in development)
113
- # config.enable_hotreload = true
114
-
115
- # Asset configuration
116
- # config.assets.logo = "custom_logo.png"
117
- # config.assets.favicon = "custom_favicon.ico"
118
- # config.assets.stylesheet = "plutonium.css"
119
- # config.assets.script = "plutonium.min.js"
82
+ # Custom assets (after running pu:core:assets)
83
+ # config.assets.stylesheet = "application"
84
+ # config.assets.script = "application"
85
+ # config.assets.logo = "custom_logo.png"
86
+ # config.assets.favicon = "custom_favicon.ico"
120
87
  end
121
88
  ```
122
89
 
123
- ## Development Setup
124
-
125
- For the best development experience:
90
+ Full configuration options: [Reference › App](/reference/app/#configuration).
126
91
 
127
- ### 1. Use bin/dev
92
+ ## `bin/dev` for development
128
93
 
129
- Plutonium includes a Procfile for `foreman`:
94
+ Plutonium ships a Procfile that runs Rails and the CSS watcher together:
130
95
 
131
96
  ```bash
132
97
  bin/dev
133
98
  ```
134
99
 
135
- This starts Rails and the CSS watcher together.
136
-
137
- ### 2. Enable Reloading
138
-
139
- In development, Plutonium automatically reloads definitions and policies when files change. This is controlled by `config.enable_hotreload` (enabled by default in development).
140
-
141
- ## Next Steps
142
-
143
- Now that Plutonium is installed:
100
+ ## Next steps
144
101
 
145
- - [Create your first Feature Package](/guides/creating-packages)
146
- - [Generate a Resource](/guides/adding-resources)
147
- - [Follow the Tutorial](/getting-started/tutorial/)
102
+ - [Tutorial](./tutorial/) build a complete blog application step-by-step
103
+ - [Adding resources](/guides/adding-resources) — create your first resource
104
+ - [Creating packages](/guides/creating-packages) — organize code into feature and portal packages
@@ -118,13 +118,13 @@ rails db:migrate
118
118
 
119
119
  ## Creating a Portal
120
120
 
121
- Resources need a portal to be accessible via the web. Let's create an admin portal:
121
+ Resources need a portal to be accessible via the web. Let's create a public admin portal so we can explore the UI right away — we'll add authentication in [Chapter 3](./03-authentication).
122
122
 
123
123
  ```bash
124
- rails generate pu:pkg:portal admin
124
+ rails generate pu:pkg:portal admin --public
125
125
  ```
126
126
 
127
- This creates the AdminPortal package with authentication configured.
127
+ This creates the `AdminPortal` package mounted at `/admin`. The `--public` flag wires the portal's controller with `Plutonium::Auth::Public`, so any visitor can access it. (Other options: `--auth=ACCOUNT` to gate via a Rodauth account, or `--byo` for your own auth.)
128
128
 
129
129
  ## Connecting the Resource
130
130
 
@@ -145,12 +145,21 @@ This:
145
145
  bin/dev
146
146
  ```
147
147
 
148
- Visit `http://localhost:3000/admin/blogging/posts`. You should see:
149
- - An empty posts table
150
- - A "New Post" button
151
- - Search and filter options
148
+ Visit `http://localhost:3000/admin/blogging/posts`. You should see an empty posts table with a "New Post" button:
152
149
 
153
- Try creating a post. The form is automatically generated from your model's attributes.
150
+ ![Empty posts index](/images/tutorial/02-empty-index.png)
151
+
152
+ Click "New" — the form is automatically generated from your model's attributes. By default Plutonium opens it as a slideover (right) so you keep the index visible; visiting `/admin/blogging/posts/new` directly renders the same form as a standalone page (left):
153
+
154
+ | Default — slideover from index | Standalone page (direct URL) |
155
+ |:--:|:--:|
156
+ | ![Slideover new form](/images/tutorial/02-new-form-modal.png) | ![Standalone new form](/images/tutorial/02-new-form.png) |
157
+
158
+ To always render full-page instead, set `modal false` in the definition. To pick a different style, use `modal :centered`. See [Reference › Resource › Definition › Modal](/reference/resource/definition).
159
+
160
+ Create a few posts and the table fills in:
161
+
162
+ ![Posts index with rows](/images/tutorial/02-index-with-posts.png)
154
163
 
155
164
  ## Understanding Auto-Detection
156
165