plutonium 0.49.1 → 0.51.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 (206) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/plutonium/SKILL.md +85 -102
  3. data/.claude/skills/plutonium-app/SKILL.md +572 -0
  4. data/.claude/skills/plutonium-auth/SKILL.md +163 -300
  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 +655 -0
  8. data/.claude/skills/plutonium-testing/SKILL.md +6 -5
  9. data/.claude/skills/plutonium-ui/SKILL.md +900 -0
  10. data/CHANGELOG.md +37 -0
  11. data/Rakefile +2 -1
  12. data/app/assets/plutonium.css +1 -11
  13. data/app/assets/plutonium.js +1323 -1184
  14. data/app/assets/plutonium.js.map +4 -4
  15. data/app/assets/plutonium.min.js +50 -49
  16. data/app/assets/plutonium.min.js.map +4 -4
  17. data/app/views/plutonium/_resource_header.html.erb +4 -4
  18. data/app/views/plutonium/_resource_sidebar.html.erb +9 -9
  19. data/app/views/resource/_resource_grid.html.erb +1 -0
  20. data/config/brakeman.ignore +25 -2
  21. data/docs/.vitepress/config.ts +37 -27
  22. data/docs/getting-started/index.md +22 -29
  23. data/docs/getting-started/installation.md +37 -80
  24. data/docs/getting-started/tutorial/index.md +4 -5
  25. data/docs/guides/adding-resources.md +66 -377
  26. data/docs/guides/authentication.md +94 -463
  27. data/docs/guides/authorization.md +124 -370
  28. data/docs/guides/creating-packages.md +94 -296
  29. data/docs/guides/custom-actions.md +121 -441
  30. data/docs/guides/index.md +22 -42
  31. data/docs/guides/multi-tenancy.md +116 -187
  32. data/docs/guides/nested-resources.md +103 -431
  33. data/docs/guides/search-filtering.md +123 -240
  34. data/docs/guides/testing.md +5 -4
  35. data/docs/guides/theming.md +157 -407
  36. data/docs/guides/troubleshooting.md +5 -3
  37. data/docs/guides/user-invites.md +106 -425
  38. data/docs/guides/user-profile.md +76 -243
  39. data/docs/index.md +1 -1
  40. data/docs/reference/app/generators.md +517 -0
  41. data/docs/reference/app/index.md +158 -0
  42. data/docs/reference/app/packages.md +146 -0
  43. data/docs/reference/app/portals.md +377 -0
  44. data/docs/reference/auth/accounts.md +230 -0
  45. data/docs/reference/auth/index.md +88 -0
  46. data/docs/reference/auth/profile.md +185 -0
  47. data/docs/reference/behavior/controllers.md +395 -0
  48. data/docs/reference/behavior/index.md +22 -0
  49. data/docs/reference/behavior/interactions.md +341 -0
  50. data/docs/reference/behavior/policies.md +417 -0
  51. data/docs/reference/index.md +56 -49
  52. data/docs/reference/resource/actions.md +423 -0
  53. data/docs/reference/resource/definition.md +508 -0
  54. data/docs/reference/resource/index.md +50 -0
  55. data/docs/reference/resource/model.md +348 -0
  56. data/docs/reference/resource/query.md +305 -0
  57. data/docs/reference/tenancy/entity-scoping.md +361 -0
  58. data/docs/reference/tenancy/index.md +36 -0
  59. data/docs/reference/tenancy/invites.md +393 -0
  60. data/docs/reference/tenancy/nested-resources.md +267 -0
  61. data/docs/reference/testing/index.md +287 -0
  62. data/docs/reference/ui/assets.md +400 -0
  63. data/docs/reference/ui/components.md +165 -0
  64. data/docs/reference/ui/displays.md +104 -0
  65. data/docs/reference/ui/forms.md +284 -0
  66. data/docs/reference/ui/index.md +30 -0
  67. data/docs/reference/ui/layouts.md +106 -0
  68. data/docs/reference/ui/pages.md +189 -0
  69. data/docs/reference/ui/tables.md +117 -0
  70. data/docs/superpowers/plans/2026-05-07-ui-layout-overhaul.md +841 -0
  71. data/docs/superpowers/plans/2026-05-07-ui-layout-overhaul.md.tasks.json +103 -0
  72. data/docs/superpowers/specs/2026-05-07-ui-layout-overhaul-design.md +270 -0
  73. data/docs/superpowers/specs/2026-05-09-typeahead-endpoint-design.md +203 -0
  74. data/docs/superpowers/specs/2026-05-12-skill-compaction-design.md +99 -0
  75. data/docs/superpowers/specs/2026-05-13-docs-restructure-design.md +186 -0
  76. data/gemfiles/rails_7.gemfile.lock +1 -1
  77. data/gemfiles/rails_8.0.gemfile.lock +1 -1
  78. data/gemfiles/rails_8.1.gemfile.lock +1 -1
  79. data/lib/generators/pu/core/install/templates/config/initializers/plutonium.rb +1 -0
  80. data/lib/generators/pu/invites/install_generator.rb +1 -0
  81. data/lib/generators/pu/lite/rails_pulse/rails_pulse_generator.rb +54 -5
  82. data/lib/plutonium/action/base.rb +44 -1
  83. data/lib/plutonium/action/interactive.rb +1 -1
  84. data/lib/plutonium/configuration.rb +4 -0
  85. data/lib/plutonium/definition/actions.rb +3 -0
  86. data/lib/plutonium/definition/base.rb +8 -0
  87. data/lib/plutonium/definition/index_views.rb +95 -0
  88. data/lib/plutonium/definition/metadata.rb +40 -0
  89. data/lib/plutonium/helpers/turbo_helper.rb +12 -1
  90. data/lib/plutonium/helpers/turbo_stream_actions_helper.rb +14 -0
  91. data/lib/plutonium/interaction/response/redirect.rb +1 -1
  92. data/lib/plutonium/query/base.rb +8 -0
  93. data/lib/plutonium/query/filters/association.rb +30 -8
  94. data/lib/plutonium/query/filters/boolean.rb +5 -0
  95. data/lib/plutonium/resource/controller.rb +1 -0
  96. data/lib/plutonium/resource/controllers/crud_actions.rb +19 -1
  97. data/lib/plutonium/resource/controllers/presentable.rb +11 -2
  98. data/lib/plutonium/resource/controllers/typeahead.rb +180 -0
  99. data/lib/plutonium/resource/definition.rb +42 -0
  100. data/lib/plutonium/resource/policy.rb +7 -0
  101. data/lib/plutonium/resource/query_object.rb +64 -6
  102. data/lib/plutonium/routing/mapper_extensions.rb +15 -0
  103. data/lib/plutonium/testing/resource_definition.rb +2 -2
  104. data/lib/plutonium/ui/action_button.rb +4 -2
  105. data/lib/plutonium/ui/component/kit.rb +12 -0
  106. data/lib/plutonium/ui/component/methods.rb +4 -0
  107. data/lib/plutonium/ui/display/base.rb +3 -1
  108. data/lib/plutonium/ui/display/resource.rb +109 -25
  109. data/lib/plutonium/ui/display/theme.rb +2 -1
  110. data/lib/plutonium/ui/dyna_frame/content.rb +8 -14
  111. data/lib/plutonium/ui/empty_card.rb +1 -1
  112. data/lib/plutonium/ui/form/base.rb +35 -3
  113. data/lib/plutonium/ui/form/components/hidden_wrapper.rb +25 -0
  114. data/lib/plutonium/ui/form/components/json.rb +58 -0
  115. data/lib/plutonium/ui/form/components/resource_select.rb +133 -1
  116. data/lib/plutonium/ui/form/components/secure_association.rb +105 -24
  117. data/lib/plutonium/ui/form/components/sticky_footer.rb +17 -0
  118. data/lib/plutonium/ui/form/concerns/typeahead_attributes.rb +83 -0
  119. data/lib/plutonium/ui/form/resource.rb +45 -10
  120. data/lib/plutonium/ui/form/theme.rb +1 -1
  121. data/lib/plutonium/ui/frame_navigator_panel.rb +7 -4
  122. data/lib/plutonium/ui/grid/card.rb +235 -0
  123. data/lib/plutonium/ui/grid/resource.rb +149 -0
  124. data/lib/plutonium/ui/layout/base.rb +38 -1
  125. data/lib/plutonium/ui/layout/header.rb +1 -2
  126. data/lib/plutonium/ui/layout/icon_rail.rb +212 -0
  127. data/lib/plutonium/ui/layout/resource_layout.rb +10 -3
  128. data/lib/plutonium/ui/layout/sidebar.rb +12 -24
  129. data/lib/plutonium/ui/layout/topbar.rb +100 -0
  130. data/lib/plutonium/ui/modal/base.rb +109 -0
  131. data/lib/plutonium/ui/modal/centered.rb +21 -0
  132. data/lib/plutonium/ui/modal/slideover.rb +26 -0
  133. data/lib/plutonium/ui/page/base.rb +18 -6
  134. data/lib/plutonium/ui/page/edit.rb +13 -1
  135. data/lib/plutonium/ui/page/index.rb +40 -1
  136. data/lib/plutonium/ui/page/interactive_action.rb +8 -39
  137. data/lib/plutonium/ui/page/new.rb +13 -1
  138. data/lib/plutonium/ui/page/show.rb +8 -1
  139. data/lib/plutonium/ui/page_header.rb +8 -13
  140. data/lib/plutonium/ui/panel.rb +10 -19
  141. data/lib/plutonium/ui/sidebar_menu.rb +2 -25
  142. data/lib/plutonium/ui/tab_list.rb +29 -7
  143. data/lib/plutonium/ui/table/base.rb +106 -0
  144. data/lib/plutonium/ui/table/components/bulk_actions_toolbar.rb +12 -4
  145. data/lib/plutonium/ui/table/components/filter_form.rb +171 -0
  146. data/lib/plutonium/ui/table/components/filter_pills.rb +89 -0
  147. data/lib/plutonium/ui/table/components/row_actions_dropdown.rb +13 -12
  148. data/lib/plutonium/ui/table/components/scopes_pills.rb +67 -0
  149. data/lib/plutonium/ui/table/components/selection_column.rb +2 -11
  150. data/lib/plutonium/ui/table/components/toolbar.rb +104 -0
  151. data/lib/plutonium/ui/table/components/view_switcher.rb +81 -0
  152. data/lib/plutonium/ui/table/resource.rb +158 -89
  153. data/lib/plutonium/ui/table/theme.rb +14 -5
  154. data/lib/plutonium/version.rb +1 -1
  155. data/lib/plutonium.rb +14 -0
  156. data/lib/tasks/release.rake +15 -1
  157. data/package.json +10 -10
  158. data/src/css/components.css +304 -131
  159. data/src/css/slim_select.css +4 -0
  160. data/src/css/tokens.css +101 -85
  161. data/src/js/controllers/autosubmit_controller.js +24 -0
  162. data/src/js/controllers/bulk_actions_controller.js +15 -16
  163. data/src/js/controllers/capture_url_controller.js +14 -0
  164. data/src/js/controllers/filter_panel_controller.js +77 -19
  165. data/src/js/controllers/frame_navigator_controller.js +34 -6
  166. data/src/js/controllers/icon_rail_controller.js +22 -0
  167. data/src/js/controllers/icon_rail_flyout_controller.js +128 -0
  168. data/src/js/controllers/register_controllers.js +16 -0
  169. data/src/js/controllers/resource_tab_list_controller.js +56 -3
  170. data/src/js/controllers/row_click_controller.js +21 -0
  171. data/src/js/controllers/slim_select_controller.js +61 -0
  172. data/src/js/controllers/table_column_menu_controller.js +43 -0
  173. data/src/js/controllers/table_header_controller.js +16 -0
  174. data/src/js/controllers/view_switcher_controller.js +29 -0
  175. data/src/js/turbo/turbo_actions.js +33 -0
  176. data/yarn.lock +553 -543
  177. metadata +71 -32
  178. data/.claude/skills/plutonium-assets/SKILL.md +0 -512
  179. data/.claude/skills/plutonium-controller/SKILL.md +0 -396
  180. data/.claude/skills/plutonium-create-resource/SKILL.md +0 -303
  181. data/.claude/skills/plutonium-definition/SKILL.md +0 -1138
  182. data/.claude/skills/plutonium-entity-scoping/SKILL.md +0 -317
  183. data/.claude/skills/plutonium-forms/SKILL.md +0 -465
  184. data/.claude/skills/plutonium-installation/SKILL.md +0 -325
  185. data/.claude/skills/plutonium-interaction/SKILL.md +0 -413
  186. data/.claude/skills/plutonium-invites/SKILL.md +0 -408
  187. data/.claude/skills/plutonium-model/SKILL.md +0 -440
  188. data/.claude/skills/plutonium-nested-resources/SKILL.md +0 -360
  189. data/.claude/skills/plutonium-package/SKILL.md +0 -198
  190. data/.claude/skills/plutonium-policy/SKILL.md +0 -456
  191. data/.claude/skills/plutonium-portal/SKILL.md +0 -410
  192. data/.claude/skills/plutonium-views/SKILL.md +0 -592
  193. data/docs/reference/assets/index.md +0 -496
  194. data/docs/reference/controller/index.md +0 -412
  195. data/docs/reference/definition/actions.md +0 -449
  196. data/docs/reference/definition/fields.md +0 -383
  197. data/docs/reference/definition/index.md +0 -268
  198. data/docs/reference/definition/query.md +0 -351
  199. data/docs/reference/generators/index.md +0 -648
  200. data/docs/reference/interaction/index.md +0 -449
  201. data/docs/reference/model/features.md +0 -248
  202. data/docs/reference/model/index.md +0 -218
  203. data/docs/reference/policy/index.md +0 -456
  204. data/docs/reference/portal/index.md +0 -379
  205. data/docs/reference/views/forms.md +0 -411
  206. data/docs/reference/views/index.md +0 -501
@@ -0,0 +1,99 @@
1
+ # Skill Compaction & Consolidation Design
2
+
3
+ **Date:** 2026-05-12
4
+ **Status:** Approved (pending implementation)
5
+
6
+ ## Problem
7
+
8
+ The `.claude/skills/` directory currently holds 19 Plutonium skills totaling ~7,846 lines. Several issues:
9
+
10
+ - Skills are too verbose for a stable-API framework. Plutonium rarely changes shape, so re-explaining concepts has little ROI.
11
+ - Several skills are read together for any non-trivial task (e.g. create-resource + model + definition). Loading them separately wastes context.
12
+ - Some skills duplicate content (Rails-isms, philosophy preambles, repeated DSL explanations).
13
+
14
+ Skills are written for **developers using the framework**, not for first-time Rails users. They should read like reference + decision rules, not tutorials.
15
+
16
+ ## Goals
17
+
18
+ 1. Reduce total skill volume by ~45% (target: ~4,150 lines from 7,846).
19
+ 2. Merge skills that are almost always loaded together.
20
+ 3. Keep skills self-contained with inline code examples (chosen over linking to `test/dummy`).
21
+ 4. Preserve high-value reference material (option/DSL/field tables).
22
+
23
+ ## Non-Goals
24
+
25
+ - Restructuring user-facing `docs/` site.
26
+ - Changing the framework API.
27
+ - Splitting examples into separate files outside the skill.
28
+
29
+ ## Target Skill Map
30
+
31
+ From 19 skills to 8:
32
+
33
+ | New skill | Merges | Est. lines |
34
+ |---|---|---|
35
+ | `plutonium` | (router, kept) | ~150 |
36
+ | `plutonium-app` | installation + portal + package | ~600 |
37
+ | `plutonium-resource` | create-resource + model + definition | ~800 |
38
+ | `plutonium-behavior` | controller + policy + interaction | ~700 |
39
+ | `plutonium-ui` | views + forms + assets | ~700 |
40
+ | `plutonium-auth` | (kept, compacted) | ~350 |
41
+ | `plutonium-tenancy` | entity-scoping + nested-resources + invites | ~600 |
42
+ | `plutonium-testing` | (kept, compacted) | ~250 |
43
+
44
+ **Total: ~4,150 lines.**
45
+
46
+ ### Rationale per merge
47
+
48
+ - **plutonium-app** — installation, portal creation, and package creation are the setup arc. Always done together on a new app.
49
+ - **plutonium-resource** — model declarations, scaffold options, and definition DSL are the core "build a resource" workflow.
50
+ - **plutonium-behavior** — controllers, policies, and interactions form the request/authorization/business-logic layer.
51
+ - **plutonium-ui** — views, forms, and assets all touch presentation. Assets covers the toolchain backing both views and forms.
52
+ - **plutonium-tenancy** — entity-scoping is the core mechanic; nested-resources and invites are both consumers of that mechanic.
53
+ - **plutonium-auth** stays solo at the user's request (rodauth/profile is distinct enough from tenancy/invites).
54
+ - **plutonium-testing** stays solo (orthogonal concern, loaded only for test work).
55
+
56
+ ## Compaction Rules
57
+
58
+ Applied to every skill during merge.
59
+
60
+ **Cut:**
61
+ - Rails/Ruby basics — assume reader knows Rails.
62
+ - Philosophy/motivation preambles.
63
+ - Duplicated content across merged skills (one canonical location per concept).
64
+ - Verbose prose where a 10-line snippet shows the same thing.
65
+ - Marketing copy ("Plutonium gives you...").
66
+
67
+ **Keep:**
68
+ - Decision rules ("use X when…, Y when…").
69
+ - Non-obvious gotchas and constraints.
70
+ - Short canonical inline snippets.
71
+ - **Option/field/DSL tables** — high-value reference, kept verbatim.
72
+ - Cross-references to other skills via `[[plutonium-resource]]` style links.
73
+
74
+ ## Format per merged skill
75
+
76
+ 1. **Header paragraph** — what this covers + when to load.
77
+ 2. **Sub-sections per merged topic** — each with: decision rules → minimal inline example → gotchas → tables (where applicable).
78
+ 3. **Cross-references** at bottom.
79
+
80
+ ## Rollout
81
+
82
+ 1. **Pilot:** `plutonium-resource` first (largest, hardest — biggest signal on whether the template works).
83
+ 2. **Review pilot together** — adjust template if needed.
84
+ 3. **Apply pattern** to the remaining merges. One PR per merged skill OR all-in-one (TBD with user).
85
+ 4. **Update `plutonium` router skill** last — it references the new names.
86
+ 5. **Delete old skill directories** only after the new one lands.
87
+
88
+ Skills require a gem release to take effect for users (per `CLAUDE.md`), so this ships as a single release regardless of how PRs are split.
89
+
90
+ ## Risks
91
+
92
+ - **Loss of granularity for context loading** — a single merged skill loads more tokens even when only one sub-topic is needed. Mitigated by aggressive compaction (loose budget but still much smaller than today's biggest individual skills).
93
+ - **Cross-references breaking** — the `plutonium` router skill and any external references must update at the same time as the merge.
94
+ - **Drift from `docs/`** — the user-facing docs site may still reference old skill structure; out of scope for this spec but worth noting.
95
+
96
+ ## Open Questions
97
+
98
+ - Should the merge land as one PR or eight? (Deferred to rollout time.)
99
+ - Are there external references to the old skill names (other repos, marketplace listings) that need updating?
@@ -0,0 +1,186 @@
1
+ # Docs Restructure & Compaction Design
2
+
3
+ **Date:** 2026-05-13
4
+ **Status:** Draft — awaiting approval
5
+
6
+ ## Problem
7
+
8
+ Following the skill consolidation (19 → 8 skills, ~37% volume cut), the `docs/` site is misaligned in two ways:
9
+
10
+ 1. **Reference structure** mirrors the OLD skill structure (separate `model/`, `definition/`, `policy/`, `controller/`, `interaction/`, `views/`, `assets/`, `portal/`, `generators/`). It should mirror the new 7 functional areas.
11
+ 2. **Concept/task split is muddled.** Some "guides" are really concept explanations (e.g. `guides/authorization.md` overlaps `reference/policy/`). Some concepts have NO reference home (auth, tenancy, testing — they live in `guides/` only).
12
+
13
+ ## Goals
14
+
15
+ **Primary goal: quality.** Volume reduction is incidental.
16
+
17
+ 1. Restructure `reference/` to mirror the 7 functional areas from the skill consolidation — so readers can predict where to look.
18
+ 2. Establish a clean role split: **guides = task recipes ("how do I X")**, **reference = concept lookup ("what does X do")**. Some duplication is fine when framed as different entry points; tables of options live in ONE place.
19
+ 3. Improve every page on these axes:
20
+ - **Right place** — concepts in reference, recipes in guides.
21
+ - **Right structure** — 🚨 callouts at top for "you'll regret this" rules; option/decision tables for scannability; decision rules over generic exhortations.
22
+ - **Right content** — keep WHY explanations that help reason about edge cases; cut marketing copy and empty "best practices" exhortations; verify technical accuracy as we go (the skill work caught real bugs — same energy).
23
+ 4. Light pass on the tutorial — preserve narrative flow; improve clarity where prose is unclear.
24
+
25
+ ## Non-Goals
26
+
27
+ - Restructuring `getting-started/` navigation (the tutorial arc stays).
28
+ - Changing VitePress theme, search provider, or build pipeline.
29
+ - Adding new content beyond reorganizing what exists.
30
+
31
+ ## Current state
32
+
33
+ 40 markdown files, ~13,222 lines.
34
+
35
+ | Area | Files | Notes |
36
+ |---|---|---|
37
+ | `getting-started/` | 11 | Index + installation + 8-step tutorial. Narrative learning arc. |
38
+ | `guides/` | 14 | Task-oriented but inconsistent — some concept-heavy. |
39
+ | `reference/` | 16 | Concept-by-concept, mirrors the OLD skill structure. |
40
+
41
+ ## Target reference structure (mirrors 7 skill areas)
42
+
43
+ ```
44
+ reference/
45
+ ├── index.md ← rewritten overview, links to the 7 areas
46
+ ├── app/
47
+ │ ├── index.md ← installation, configuration
48
+ │ ├── packages.md ← feature + portal packages
49
+ │ ├── portals.md ← portal engines, mounting, route registration
50
+ │ └── generators.md ← full generator catalog
51
+ ├── resource/
52
+ │ ├── index.md ← overview, the 4 layers
53
+ │ ├── model.md ← `Plutonium::Resource::Record`, has_cents, SGID, routing (merges current model/)
54
+ │ ├── definition.md ← field/input/display/column, page chrome (merges current definition/index + fields)
55
+ │ ├── query.md ← search, filters, scopes, sort
56
+ │ └── actions.md ← custom + bulk actions
57
+ ├── behavior/
58
+ │ ├── index.md ← overview, the controller/policy/interaction trio
59
+ │ ├── controllers.md ← hooks, key methods, presentation
60
+ │ ├── policies.md ← actions, permitted attributes, associations, relation_scope
61
+ │ └── interactions.md ← structure, outcomes, chaining, URL generation
62
+ ├── ui/
63
+ │ ├── index.md ← overview
64
+ │ ├── pages.md ← IndexPage/ShowPage/NewPage/EditPage, hooks
65
+ │ ├── forms.md ← field builder, layouts, theming, association inputs (current views/forms.md)
66
+ │ ├── displays.md ← Display class, custom rendering
67
+ │ ├── tables.md ← Table class, customization
68
+ │ ├── components.md ← component kit, custom Phlex components
69
+ │ ├── layouts.md ← shell, eject, ResourceLayout
70
+ │ └── assets.md ← Tailwind config, Stimulus, design tokens, .pu-* classes (current assets/)
71
+ ├── auth/ ← NEW (currently only in guides/)
72
+ │ ├── index.md ← Rodauth overview
73
+ │ ├── accounts.md ← basic, admin, SaaS account types
74
+ │ └── profile.md ← profile resource, SecuritySection
75
+ ├── tenancy/ ← NEW (currently spread across guides/)
76
+ │ ├── index.md ← overview, three pieces (portal/policy/model)
77
+ │ ├── entity-scoping.md ← associated_with, three model shapes
78
+ │ ├── nested-resources.md ← parent/child routes, scoping
79
+ │ └── invites.md ← invitation system
80
+ └── testing/ ← NEW (currently only in guides/)
81
+ ├── index.md
82
+ ├── crud.md
83
+ ├── policy.md
84
+ ├── nested.md
85
+ ├── portal-access.md
86
+ └── auth-helpers.md
87
+ ```
88
+
89
+ ### Content migrations
90
+
91
+ | Current file | Goes to |
92
+ |---|---|
93
+ | `reference/model/index.md` + `features.md` | `reference/resource/model.md` |
94
+ | `reference/definition/index.md` + `fields.md` | `reference/resource/definition.md` |
95
+ | `reference/definition/query.md` | `reference/resource/query.md` |
96
+ | `reference/definition/actions.md` | `reference/resource/actions.md` |
97
+ | `reference/controller/index.md` | `reference/behavior/controllers.md` |
98
+ | `reference/policy/index.md` | `reference/behavior/policies.md` |
99
+ | `reference/interaction/index.md` | `reference/behavior/interactions.md` |
100
+ | `reference/views/index.md` | split → `pages.md`, `displays.md`, `tables.md`, `components.md`, `layouts.md` |
101
+ | `reference/views/forms.md` | `reference/ui/forms.md` |
102
+ | `reference/assets/index.md` | `reference/ui/assets.md` |
103
+ | `reference/portal/index.md` | `reference/app/portals.md` |
104
+ | `reference/generators/index.md` | `reference/app/generators.md` |
105
+ | `getting-started/installation.md` | concept part → `reference/app/index.md`; task part stays |
106
+ | `guides/authentication.md` | concept part → `reference/auth/index.md` + `accounts.md`; recipe stays |
107
+ | `guides/user-profile.md` | concept part → `reference/auth/profile.md`; recipe stays |
108
+ | `guides/multi-tenancy.md` | concept part → `reference/tenancy/entity-scoping.md`; recipe stays |
109
+ | `guides/nested-resources.md` | concept part → `reference/tenancy/nested-resources.md`; recipe stays |
110
+ | `guides/user-invites.md` | concept part → `reference/tenancy/invites.md`; recipe stays |
111
+ | `guides/testing.md` | concept part → `reference/testing/*`; recipe stays |
112
+ | `guides/creating-packages.md` | concept part → `reference/app/packages.md`; recipe stays |
113
+
114
+ ## Guides restructure
115
+
116
+ Each guide becomes a clean **task recipe**:
117
+
118
+ - Single goal stated at the top ("Add authentication to your app")
119
+ - Numbered step-by-step
120
+ - Each step links to the relevant reference page for "why" and "what else"
121
+ - No exhaustive option tables (those live in reference)
122
+ - ~50-150 lines each, down from ~300-600
123
+
124
+ Keep the 14 guides at their current paths so external links don't break.
125
+
126
+ ## Editing principles (quality-first)
127
+
128
+ Not "cut everything"; cut what doesn't earn its keep. Specifically:
129
+
130
+ **Cut:**
131
+ - Marketing copy ("Plutonium is awesome because…").
132
+ - Empty "best practices" exhortations ("write clean code", "test your code").
133
+ - Content duplicated across pages — one canonical home + cross-link.
134
+ - Verbose prose where a 10-line snippet shows the same thing.
135
+ - Rails-101 explanations that don't set up a Plutonium twist.
136
+
137
+ **Keep — and add more of:**
138
+ - **WHY explanations** that help readers reason about edge cases.
139
+ - **Non-obvious gotchas** — the dangerous-default stuff that bites people.
140
+ - **Decision rules** ("use X when you need Y") over generic exhortations.
141
+ - **Option / field / DSL tables** — readers scan them, they don't read prose.
142
+ - **Inline code examples** that work — copy-pasteable, no `...` stand-ins.
143
+ - **🚨 callouts at top of each page** for the "you'll regret this" rules.
144
+
145
+ **Verify as we go.** The skill work caught real bugs (auto-detection rules, association input behavior, action visibility flags, `views` DSL naming). Same energy here — when prose claims X, check the source.
146
+
147
+ ## Tutorial compaction pass
148
+
149
+ Same cuts as above, but preserve:
150
+
151
+ - Step structure (1-8 stays)
152
+ - Narrative flow (one step builds on the next)
153
+ - Frequent "expected output" / "verify" callouts
154
+ - Screenshots and visuals (keep all references)
155
+
156
+ Target: 10-20% volume reduction without losing pedagogical value.
157
+
158
+ ## VitePress sidebar rewrite
159
+
160
+ `.vitepress/config.ts` sidebar rewritten for the new structure. Three sidebars:
161
+
162
+ - `/getting-started/` — unchanged
163
+ - `/guides/` — same 14 entries, reorganized into the 7 functional groups
164
+ - `/reference/` — new 7-area structure
165
+
166
+ ## Rollout
167
+
168
+ 1. **Pilot one reference area** — pick `reference/resource/` (largest, most-read). Build it from scratch using the merged skills as the template. Get user feedback on shape.
169
+ 2. **Build out the other 6 areas** — one at a time or in parallel, your call.
170
+ 3. **Move concept content** from guides into reference. Hollow guides become recipes.
171
+ 4. **Tutorial compaction pass** — minimal, last.
172
+ 5. **Rewrite VitePress sidebar** — once all paths exist.
173
+ 6. **Delete old reference directories** — `model/`, `definition/`, `policy/`, etc. — in one sweep after everything's in place.
174
+ 7. **Build the site locally** — verify no dead links, search index regenerates cleanly.
175
+
176
+ ## Risks
177
+
178
+ - **External links break.** GitHub PRs, blog posts, Stack Overflow answers may link to `reference/model/`, `reference/definition/actions`, etc. Mitigation: VitePress supports redirects, OR keep stub pages that redirect via meta refresh. Cheap to add.
179
+ - **Guides ↔ reference duplication drifts.** If a recipe and its reference page describe the same option differently, readers get confused. Mitigation: linting (no duplicate DSL/option tables across guide + reference).
180
+ - **Volume isn't the metric.** Some pages will get longer (currently underdeveloped topics: tenancy, testing, profile). Some will get shorter. The win is navigability and clarity, not line count.
181
+
182
+ ## Open questions
183
+
184
+ - Add `.vitepress` redirect configuration for old reference paths? (Recommended: yes, low cost.)
185
+ - Should `reference/app/generators.md` be the full catalog or a per-area split? (Recommended: full catalog — it's reference, scannability matters.)
186
+ - Are there guides that should be **deleted entirely** because they're 100% concept overlap with reference? (Decide after pilot.)
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- plutonium (0.49.0)
4
+ plutonium (0.50.0)
5
5
  action_policy (~> 0.7.0)
6
6
  listen (~> 3.8)
7
7
  pagy (~> 43.0)
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- plutonium (0.49.0)
4
+ plutonium (0.50.0)
5
5
  action_policy (~> 0.7.0)
6
6
  listen (~> 3.8)
7
7
  pagy (~> 43.0)
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: ..
3
3
  specs:
4
- plutonium (0.49.0)
4
+ plutonium (0.50.0)
5
5
  action_policy (~> 0.7.0)
6
6
  listen (~> 3.8)
7
7
  pagy (~> 43.0)
@@ -3,5 +3,6 @@
3
3
  Plutonium.configure do |config|
4
4
  config.load_defaults 1.0
5
5
 
6
+ config.shell = :modern
6
7
  # Configure plutonium above.
7
8
  end
@@ -70,6 +70,7 @@ module Pu
70
70
  end
71
71
 
72
72
  def create_package
73
+ return if File.exist?(File.join(destination_root, "packages/invites"))
73
74
  generate "pu:pkg:package", "invites"
74
75
  end
75
76
 
@@ -70,21 +70,70 @@ module Pu
70
70
 
71
71
  def setup_recurring_tasks
72
72
  recurring_file = "config/recurring.yml"
73
- return unless File.exist?(File.expand_path(recurring_file, destination_root))
73
+ full_path = File.expand_path(recurring_file, destination_root)
74
+ return unless File.exist?(full_path)
74
75
  return if file_includes?(recurring_file, "rails_pulse")
75
76
 
76
- recurring_tasks = <<~YAML
77
+ content = File.read(full_path)
78
+ env_keys = %w[production development staging test]
79
+ env_scoped = content.lines.any? { |l| l.match?(/^(#{env_keys.join("|")}):\s*$/) }
77
80
 
81
+ if env_scoped
82
+ create_file recurring_file, inject_rails_pulse_under_envs(content, env_keys), force: true
83
+ else
84
+ append_to_file recurring_file, "\n" + rails_pulse_tasks_yaml(0)
85
+ end
86
+ end
87
+
88
+ def rails_pulse_tasks_yaml(indent)
89
+ pad = " " * indent
90
+ <<~YAML.gsub(/^(?=.)/, pad)
78
91
  rails_pulse_summary:
79
92
  class: RailsPulse::SummaryJob
80
- schedule: "5 * * * *" # 5 minutes past every hour
93
+ queue: default
94
+ schedule: every hour at minute 5
95
+ description: "Roll up Rails Pulse raw records into summary tables"
81
96
 
82
97
  rails_pulse_cleanup:
83
98
  class: RailsPulse::CleanupJob
84
- schedule: "0 1 * * *" # Daily at 1am
99
+ queue: default
100
+ schedule: every day at 1am
101
+ description: "Archive/purge old Rails Pulse data"
85
102
  YAML
103
+ end
104
+
105
+ def inject_rails_pulse_under_envs(content, env_keys)
106
+ lines = content.lines
107
+ env_re = /^(#{env_keys.join("|")}):\s*$/
108
+
109
+ env_starts = lines.each_with_index.select { |l, _| env_re.match?(l) }.map(&:last)
110
+
111
+ env_starts.reverse_each do |start|
112
+ end_idx = lines.length
113
+ ((start + 1)...lines.length).each do |i|
114
+ if lines[i].match?(/^[^\s#]/)
115
+ end_idx = i
116
+ break
117
+ end
118
+ end
119
+
120
+ indent = 2
121
+ ((start + 1)...end_idx).each do |i|
122
+ if (m = lines[i].match(/^(\s+)\S/))
123
+ indent = m[1].length
124
+ break
125
+ end
126
+ end
127
+
128
+ insert_at = end_idx
129
+ while insert_at > start + 1 && lines[insert_at - 1].strip.empty?
130
+ insert_at -= 1
131
+ end
132
+
133
+ lines.insert(insert_at, "\n", rails_pulse_tasks_yaml(indent))
134
+ end
86
135
 
87
- append_to_file recurring_file, recurring_tasks
136
+ lines.join
88
137
  end
89
138
  end
90
139
  end
@@ -16,7 +16,7 @@ module Plutonium
16
16
  # @attr_reader [Symbol, nil] category The category of the action.
17
17
  # @attr_reader [Integer] position The position of the action within its category.
18
18
  class Base
19
- attr_reader :name, :label, :description, :icon, :route_options, :confirmation, :turbo, :turbo_frame, :color, :category, :position, :return_to
19
+ attr_reader :name, :label, :description, :icon, :route_options, :confirmation, :turbo, :turbo_frame, :color, :category, :position, :return_to, :modal
20
20
 
21
21
  # Initialize a new action.
22
22
  #
@@ -57,6 +57,8 @@ module Plutonium
57
57
  @resource_action = options[:resource_action] || false
58
58
  @category = ActiveSupport::StringInquirer.new((options[:category] || :secondary).to_s)
59
59
  @position = options[:position] || 50
60
+ @modal = options[:modal] || :centered
61
+ validate_modal!
60
62
 
61
63
  freeze
62
64
  end
@@ -85,8 +87,49 @@ module Plutonium
85
87
  policy.allowed_to?(:"#{name}?")
86
88
  end
87
89
 
90
+ # Returns a new Action with the given options merged over this one.
91
+ # Used by the resource definition to derive variants (e.g. dropping
92
+ # `turbo_frame` when `modal false` is configured) without mutating
93
+ # the frozen original.
94
+ def with(**overrides)
95
+ self.class.new(name, **to_options.merge(overrides))
96
+ end
97
+
98
+ protected
99
+
100
+ # Canonical representation for reconstruction via `with`. Every
101
+ # attribute set in `initialize` MUST appear here; otherwise
102
+ # `with(**overrides)` would silently drop it on round-trip.
103
+ # `category` is exposed as a Symbol since `initialize` re-wraps
104
+ # it in StringInquirer.
105
+ def to_options
106
+ {
107
+ label: @label,
108
+ description: @description,
109
+ icon: @icon,
110
+ color: @color,
111
+ confirmation: @confirmation,
112
+ route_options: @route_options,
113
+ turbo: @turbo,
114
+ turbo_frame: @turbo_frame,
115
+ return_to: @return_to,
116
+ bulk_action: @bulk_action,
117
+ collection_record_action: @collection_record_action,
118
+ record_action: @record_action,
119
+ resource_action: @resource_action,
120
+ category: @category.to_sym,
121
+ position: @position,
122
+ modal: @modal
123
+ }
124
+ end
125
+
88
126
  private
89
127
 
128
+ def validate_modal!
129
+ return if [:centered, :slideover].include?(@modal)
130
+ raise ArgumentError, "modal must be :centered or :slideover, got #{@modal.inspect}"
131
+ end
132
+
90
133
  # Build RouteOptions from the provided options
91
134
  #
92
135
  # @param [RouteOptions, Hash, nil] options The routing options
@@ -22,7 +22,7 @@ module Plutonium
22
22
  options[:label] ||= interaction.label
23
23
  options[:description] ||= interaction.description
24
24
  options[:icon] ||= interaction.icon
25
- options[:turbo_frame] = "remote_modal" unless options.key?(:turbo_frame)
25
+ options[:turbo_frame] = Plutonium::REMOTE_MODAL_FRAME unless options.key?(:turbo_frame)
26
26
 
27
27
  super(name, **options)
28
28
  end
@@ -27,6 +27,9 @@ module Plutonium
27
27
  # @return [Float] the current defaults version
28
28
  attr_reader :defaults_version
29
29
 
30
+ # @return [Symbol] :classic (legacy Header/Sidebar) or :modern (Topbar/IconRail)
31
+ attr_accessor :shell
32
+
30
33
  # Map of version numbers to their default configurations
31
34
  VERSION_DEFAULTS = {
32
35
  1.0 => proc do |config|
@@ -48,6 +51,7 @@ module Plutonium
48
51
  @development = parse_boolean_env("PLUTONIUM_DEV")
49
52
  @cache_discovery = !Rails.env.development?
50
53
  @enable_hotreload = Rails.env.development?
54
+ @shell = :modern
51
55
  end
52
56
 
53
57
  # Load default configuration for a specific version
@@ -32,6 +32,9 @@ module Plutonium
32
32
 
33
33
  # standard CRUD actions
34
34
 
35
+ # turbo_frame for :new and :edit is set by
36
+ # Resource::Definition.configure_crud_modal_targets! based on the
37
+ # `modal` config. Don't hard-code it here.
35
38
  action(:new, route_options: {action: :new},
36
39
  resource_action: true, category: :primary,
37
40
  icon: Phlex::TablerIcons::Plus, position: 10)
@@ -33,6 +33,8 @@ module Plutonium
33
33
  include Scoping
34
34
  include Search
35
35
  include NestedInputs
36
+ include IndexViews
37
+ include Metadata
36
38
 
37
39
  class IndexPage < Plutonium::UI::Page::Index; end
38
40
 
@@ -48,6 +50,8 @@ module Plutonium
48
50
 
49
51
  class Table < Plutonium::UI::Table::Resource; end
50
52
 
53
+ class Grid < Plutonium::UI::Grid::Resource; end
54
+
51
55
  class Display < Plutonium::UI::Display::Resource; end
52
56
 
53
57
  class QueryForm < Plutonium::UI::Form::Query; end
@@ -114,6 +118,10 @@ module Plutonium
114
118
  self.class::Table
115
119
  end
116
120
 
121
+ def grid_class
122
+ self.class::Grid
123
+ end
124
+
117
125
  def detail_class
118
126
  self.class::Display
119
127
  end
@@ -0,0 +1,95 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Plutonium
4
+ module Definition
5
+ # DSL for declaring which index views a resource supports and how
6
+ # they're configured.
7
+ #
8
+ # @example Enable both views, default to Grid
9
+ # class UserDefinition < Plutonium::Resource::Definition
10
+ # grid_fields(
11
+ # image: :avatar,
12
+ # header: :name,
13
+ # subheader: :email,
14
+ # meta: [:role, :status]
15
+ # )
16
+ # default_index_view :grid
17
+ # end
18
+ module IndexViews
19
+ extend ActiveSupport::Concern
20
+
21
+ KNOWN_VIEWS = %i[table grid].freeze
22
+ GRID_SLOTS = %i[image header subheader body meta footer].freeze
23
+ GRID_LAYOUTS = %i[compact media].freeze
24
+
25
+ included do
26
+ class_attribute :defined_index_views, default: [:table], instance_accessor: false
27
+ class_attribute :defined_default_index_view, default: nil, instance_accessor: false
28
+ class_attribute :defined_grid_fields, default: {}, instance_accessor: false
29
+ class_attribute :defined_grid_layout, default: :compact, instance_accessor: false
30
+ class_attribute :defined_grid_columns, default: nil, instance_accessor: false
31
+ end
32
+
33
+ class_methods do
34
+ # Declares the index views this resource supports.
35
+ # Usually unnecessary — declaring `grid_fields` auto-enables :grid
36
+ # alongside the default :table. Use `index_views` only to disable
37
+ # one (e.g. `index_views :grid` to drop the table view).
38
+ # @param list [Array<Symbol>] one or more of {KNOWN_VIEWS}
39
+ def index_views(*list)
40
+ list = list.flatten.map(&:to_sym)
41
+ invalid = list - KNOWN_VIEWS
42
+ raise ArgumentError, "Unknown index_views: #{invalid.inspect}. Valid: #{KNOWN_VIEWS}" if invalid.any?
43
+ self.defined_index_views = list.empty? ? [:table] : list
44
+ end
45
+
46
+ # Declares the default index view. Must be one of {.index_views}.
47
+ # Falls back to the first declared view if unset.
48
+ def default_index_view(name = nil)
49
+ if name.nil?
50
+ defined_default_index_view || defined_index_views.first
51
+ else
52
+ name = name.to_sym
53
+ unless defined_index_views.include?(name)
54
+ raise ArgumentError, "default_index_view #{name.inspect} not in index_views #{defined_index_views.inspect}"
55
+ end
56
+ self.defined_default_index_view = name
57
+ end
58
+ end
59
+
60
+ # Maps grid slots to fields. Each slot is optional. Implicitly
61
+ # adds `:grid` to {.index_views} so a resource can opt into the
62
+ # Grid view simply by declaring its slots.
63
+ # @param slots [Hash{Symbol => Symbol, Array<Symbol>}]
64
+ def grid_fields(**slots)
65
+ invalid = slots.keys - GRID_SLOTS
66
+ raise ArgumentError, "Unknown grid slots: #{invalid.inspect}. Valid: #{GRID_SLOTS}" if invalid.any?
67
+ self.defined_grid_fields = slots
68
+ self.defined_index_views = defined_index_views + [:grid] unless defined_index_views.include?(:grid)
69
+ end
70
+
71
+ # Layout shape for grid cards. :compact (default) places the image
72
+ # left of the content; :media stacks the image full-width on top.
73
+ def grid_layout(value)
74
+ value = value.to_sym
75
+ unless GRID_LAYOUTS.include?(value)
76
+ raise ArgumentError, "grid_layout must be one of #{GRID_LAYOUTS}, got #{value.inspect}"
77
+ end
78
+ self.defined_grid_layout = value
79
+ end
80
+
81
+ # Override responsive column count. Default is 1 / 2 / 3 / 4 at
82
+ # sm / md / lg / xl.
83
+ def grid_columns(value)
84
+ self.defined_grid_columns = Integer(value)
85
+ end
86
+ end
87
+
88
+ def defined_index_views = self.class.defined_index_views
89
+ def default_index_view = self.class.default_index_view
90
+ def defined_grid_fields = self.class.defined_grid_fields
91
+ def defined_grid_layout = self.class.defined_grid_layout
92
+ def defined_grid_columns = self.class.defined_grid_columns
93
+ end
94
+ end
95
+ end