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
@@ -1,322 +1,163 @@
1
1
  # User Profile
2
2
 
3
- Plutonium provides a Profile resource generator for user account settings. The Profile page allows users to manage their personal information and access Rodauth security features like password changes, two-factor authentication, and session management.
3
+ Add a profile / account-settings page where users edit personal fields and access Rodauth security features (change password, 2FA, etc.) in one place.
4
4
 
5
- ## Overview
5
+ ## Goal
6
6
 
7
- The profile system provides:
8
- - **Profile Resource**: A standard Plutonium resource linked to the User model
9
- - **Security Section**: Automatic display of enabled Rodauth security features
10
- - **Customizable Fields**: Add any fields you need (bio, avatar, preferences, etc.)
7
+ A `/profile` URL that shows the user's personal fields plus a "Security" section linking to Rodauth-managed features.
11
8
 
12
- ## Prerequisites
9
+ ## 🚨 Critical
13
10
 
14
- Before installing the profile, ensure you have:
11
+ - **Profile association is always `:profile`** regardless of the model class — `current_user.profile`, `build_profile`, `params.require(:profile)` always work.
12
+ - **Profile needs `pu:profile:conn` to be visible** — without it, no `/profile` route, no `profile_url` helper.
13
+ - **Every user needs a profile row.** Add an `after_create :create_profile!` callback to the user model. Without it, `current_user.profile` is nil.
15
14
 
16
- 1. **User Authentication**: A Rodauth user account set up
17
- 2. **Model Markers**: The User model with marker comments
15
+ The show page renders the user's fields, then the **Security Settings** block with Rodauth-backed actions:
18
16
 
19
- The easiest way to set this up is with the SaaS generator:
17
+ ![User profile show page with SecuritySection](/images/guides/user-profile-show.png)
20
18
 
21
- ```bash
22
- rails g pu:saas:user User
23
- ```
19
+ Editing produces a regular Plutonium form — same form generator the rest of your resources use:
24
20
 
25
- ## Installation
21
+ ![User profile edit form](/images/guides/user-profile-edit.png)
26
22
 
27
- ### Step 1: Install the Profile Resource
23
+ ## Quick path
28
24
 
29
25
  ```bash
30
- rails generate pu:profile:install --dest=main_app
26
+ rails g pu:profile:setup date_of_birth:date bio:text \
27
+ --dest=competition \
28
+ --portal=competition_portal
31
29
  ```
32
30
 
33
- With custom fields:
31
+ `pu:profile:setup` is a meta-generator — runs `pu:profile:install` + `pu:profile:conn` in one shot.
32
+
33
+ ## Step-by-step
34
+
35
+ ### 1. Install
34
36
 
35
37
  ```bash
36
- rails g pu:profile:install \
37
- bio:text \
38
- avatar:attachment \
39
- 'timezone:string?' \
40
- notifications_enabled:boolean \
41
- --dest=main_app
38
+ rails generate pu:profile:install bio:text avatar:attachment 'timezone:string?' \
39
+ --dest=customer
42
40
  ```
43
41
 
44
- ### Options
45
-
46
42
  | Option | Default | Description |
47
- |--------|---------|-------------|
48
- | `--dest` | (prompts) | Target destination package or main_app |
49
- | `--user-model` | User | Rodauth user model name |
43
+ |---|---|---|
44
+ | `--dest=DEST` | (prompts) | Target package or `main_app` |
45
+ | `--user-model=NAME` | `User` | Rodauth user model |
50
46
 
51
- ### Step 2: Run Migrations
47
+ Custom resource name (first positional argument):
52
48
 
53
49
  ```bash
54
- rails db:migrate
50
+ rails g pu:profile:install AccountSettings bio:text --dest=main_app
55
51
  ```
56
52
 
57
- ### Step 3: Connect to Portal
53
+ By default the model is `{UserModel}Profile` (`UserProfile`, `StaffUserProfile`, etc.).
58
54
 
59
- Connect the Profile to your portal using the profile connect generator:
55
+ ### 2. Migrate
60
56
 
61
57
  ```bash
62
- rails g pu:profile:conn --dest=customer_portal
58
+ rails db:migrate
63
59
  ```
64
60
 
65
- This:
66
- - Registers the Profile as a singular resource (`/profile` instead of `/profiles/:id`)
67
- - Configures the `profile_url` helper to enable the "Profile" link in the user menu
68
-
69
- ## Generated Files
61
+ ### 3. Connect to a portal
70
62
 
71
- The generator creates a standard Plutonium resource:
72
-
73
- ```
74
- app/models/profile.rb
75
- db/migrate/xxx_create_profiles.rb
76
- app/controllers/profiles_controller.rb
77
- app/policies/profile_policy.rb
78
- app/definitions/profile_definition.rb
63
+ ```bash
64
+ rails g pu:profile:conn --dest=customer_portal
79
65
  ```
80
66
 
81
- And modifies:
82
- - **User model**: Adds `has_one :profile, dependent: :destroy`
83
- - **Definition**: Injects custom ShowPage with security links
84
-
85
- ## Security Section
86
-
87
- The ShowPage automatically displays links to enabled Rodauth security features.
88
-
89
- Available features (only shown if enabled in Rodauth):
90
-
91
- | Feature | Link Label | Description |
92
- |---------|------------|-------------|
93
- | `change_password` | Change Password | Update account password |
94
- | `change_login` | Change Email | Update email address |
95
- | `otp` | Two-Factor Authentication | Set up TOTP authenticator |
96
- | `recovery_codes` | Recovery Codes | View or regenerate backup codes |
97
- | `webauthn` | Security Keys | Manage passkeys and hardware keys |
98
- | `active_sessions` | Active Sessions | View and revoke sessions |
99
- | `close_account` | Close Account | Permanently delete account |
100
-
101
- ## Creating Profiles for Users
67
+ This registers the profile as a **singular** resource — exposes `/profile` (no `:id`) and the `profile_url` helper.
102
68
 
103
- Users need a profile created before they can access it. Choose one approach:
104
-
105
- ### Option A: Automatic Creation on User Signup
69
+ ### 4. Add the auto-create callback
106
70
 
107
71
  ```ruby
108
- # app/models/user.rb
72
+ # app/models/user.rb (modified by pu:profile:install)
109
73
  class User < ApplicationRecord
110
- has_one :profile, dependent: :destroy
74
+ has_one :profile, class_name: "UserProfile", dependent: :destroy
111
75
 
112
76
  after_create :create_profile!
113
77
 
114
78
  private
115
-
116
- def create_profile!
117
- create_profile
118
- end
119
-
120
- # add has_one associations above.
79
+ def create_profile! = create_profile
121
80
  end
122
81
  ```
123
82
 
124
- ### Option B: Create on First Access
125
-
126
- In your portal's application controller:
83
+ For existing users at migration time:
127
84
 
128
- ```ruby
129
- class ApplicationController < PlutoniumController
130
- before_action :ensure_profile
131
-
132
- private
133
-
134
- def ensure_profile
135
- return unless current_user
136
- current_user.profile || current_user.create_profile
137
- end
138
- end
85
+ ```bash
86
+ rails runner "User.find_each(&:create_profile)"
139
87
  ```
140
88
 
141
- ### Option C: Find or Create in Profile Controller
89
+ ## What you get
142
90
 
143
- ```ruby
144
- # app/controllers/profiles_controller.rb
145
- class ProfilesController < ResourceController
146
- private
91
+ The generated definition injects a custom `ShowPage` that renders `SecuritySection` — dynamically lists Rodauth security links based on which features are enabled:
147
92
 
148
- def current_resource
149
- @current_resource ||= current_user.profile || current_user.create_profile
150
- end
151
- end
152
- ```
93
+ | Feature enabled | Link rendered |
94
+ |---|---|
95
+ | `change_password` | Change Password |
96
+ | `change_login` | Change Email |
97
+ | `otp` | Two-Factor Authentication |
98
+ | `recovery_codes` | Recovery Codes |
99
+ | `webauthn` | Security Keys |
100
+ | `active_sessions` | Active Sessions |
101
+ | `close_account` | Close Account |
153
102
 
154
- ## Customization
103
+ If a feature isn't enabled, its link doesn't render — no configuration needed.
155
104
 
156
- ### Adding Custom Fields
157
-
158
- Edit the generated definition to configure how fields appear:
105
+ ## Linking to the profile
159
106
 
160
107
  ```ruby
161
- # app/definitions/profile_definition.rb
162
- class ProfileDefinition < Plutonium::Resource::Definition
163
- form do |f|
164
- f.field :bio, as: :text
165
- f.field :avatar, as: :attachment
166
- f.field :timezone, collection: ActiveSupport::TimeZone.all.map(&:name)
167
- f.field :notifications_enabled
168
- end
169
-
170
- display do |d|
171
- d.field :bio
172
- d.field :avatar
173
- d.field :timezone
174
- d.field :notifications_enabled
175
- end
176
-
177
- class ShowPage < ShowPage
178
- private
179
-
180
- def render_after_content
181
- render Plutonium::Profile::SecuritySection.new
182
- end
183
- end
184
- end
108
+ link_to("Profile", profile_url) if respond_to?(:profile_url)
185
109
  ```
186
110
 
187
- ### Custom Security Section
111
+ The `respond_to?` guard is defensive — only portals that ran `pu:profile:conn` have the helper.
112
+
113
+ ## Customizing the definition
188
114
 
189
- Create your own security section component:
115
+ The generated profile is a normal Plutonium definition. Customize like any other:
190
116
 
191
117
  ```ruby
192
- # app/components/custom_security_section.rb
193
- class CustomSecuritySection < Plutonium::UI::Component::Base
194
- def view_template
195
- div(class: "mt-8") do
196
- h2(class: "text-lg font-semibold") { "Account Security" }
197
-
198
- if rodauth.features.include?(:change_password)
199
- a(href: rodauth.change_password_path) { "Change Password" }
200
- end
201
-
202
- # Add your custom links
203
- a(href: "/settings/notifications") { "Notification Preferences" }
204
- end
205
- end
206
- end
207
- ```
118
+ class UserProfileDefinition < Plutonium::Resource::Definition
119
+ field :bio, as: :markdown
120
+ input :avatar, as: :uppy
121
+ field :timezone, as: :select, choices: ActiveSupport::TimeZone.all.map(&:name)
208
122
 
209
- Use it in your definition:
123
+ metadata :created_at, :updated_at
210
124
 
211
- ```ruby
212
- class ProfileDefinition < Plutonium::Resource::Definition
213
125
  class ShowPage < ShowPage
214
126
  private
215
127
 
216
128
  def render_after_content
217
- render CustomSecuritySection.new
129
+ render Plutonium::Profile::SecuritySection.new
218
130
  end
219
131
  end
220
132
  end
221
133
  ```
222
134
 
223
- ### Adding Profile Actions
224
-
225
- Add custom actions to the profile:
226
-
227
- ```ruby
228
- class ProfileDefinition < Plutonium::Resource::Definition
229
- action :export_data,
230
- interaction: Profile::ExportDataInteraction,
231
- icon: Phlex::TablerIcons::Download
232
-
233
- action :verify_email,
234
- interaction: Profile::VerifyEmailInteraction,
235
- category: :secondary
236
- end
237
- ```
238
-
239
- ### Profile Link in Navigation
240
-
241
- Add a profile link to your application layout or header:
242
-
243
- ```ruby
244
- # In your header component
245
- if current_user && respond_to?(:profile_path)
246
- a(href: profile_path) { "My Profile" }
247
- end
248
- ```
249
-
250
- ## Policy Configuration
251
-
252
- The generated policy controls access:
253
-
254
- ```ruby
255
- # app/policies/profile_policy.rb
256
- class ProfilePolicy < Plutonium::Resource::Policy
257
- # Users can only access their own profile
258
- def read?
259
- resource.user == user
260
- end
261
-
262
- def update?
263
- resource.user == user
264
- end
265
-
266
- def destroy?
267
- false # Disable deletion through the UI
268
- end
269
-
270
- # Only allow editing these attributes
271
- def permitted_attributes_for_update
272
- [:bio, :avatar, :timezone, :notifications_enabled]
273
- end
274
- end
275
- ```
276
-
277
- ## Troubleshooting
278
-
279
- ### "Profile not found" Error
135
+ See [Reference Resource › Definition](/reference/resource/definition) for the full definition surface.
280
136
 
281
- Ensure the user has a profile created. Use one of the creation strategies above.
137
+ ## Multiple account types
282
138
 
283
- ### Security Links Not Showing
139
+ If your app has both `User` and `StaffUser` accounts, run `pu:profile:install` once per:
284
140
 
285
- Security links only appear for features enabled in your Rodauth configuration:
286
-
287
- ```ruby
288
- # app/rodauth/user_rodauth_plugin.rb
289
- class UserRodauthPlugin < Plutonium::Auth::RodauthPlugin
290
- configure do
291
- enable :change_password, :change_login, :otp, :active_sessions
292
- # Only these features will show in the security section
293
- end
294
- end
141
+ ```bash
142
+ rails g pu:profile:install --user-model=User --dest=main_app
143
+ rails g pu:profile:install --user-model=StaffUser --dest=main_app
295
144
  ```
296
145
 
297
- ### Profile Routes Conflicting
298
-
299
- Ensure you use `--singular` when connecting:
146
+ Each gets its own `*Profile` model with `:profile` association on the respective user. Connect each to the appropriate portal:
300
147
 
301
148
  ```bash
302
- rails g pu:res:conn Profile --dest=my_portal --singular
149
+ rails g pu:profile:conn UserProfile --dest=customer_portal
150
+ rails g pu:profile:conn StaffUserProfile --dest=admin_portal
303
151
  ```
304
152
 
305
- This creates `/profile` (singular) instead of `/profiles/:id`.
153
+ ## Common issues
306
154
 
307
- ### Changes Not Saving
308
-
309
- Check your policy's `permitted_attributes_for_update`:
310
-
311
- ```ruby
312
- def permitted_attributes_for_update
313
- [:bio, :avatar, :timezone, :notifications_enabled]
314
- end
315
- ```
155
+ - **`current_user.profile` is nil** — every user needs a profile row. Add `after_create :create_profile!` to the user model.
156
+ - **`profile_url` is undefined** — the profile isn't connected to this portal. Run `pu:profile:conn --dest=<portal>`.
157
+ - **`SecuritySection` shows nothing** — none of the relevant Rodauth features are enabled. Enable `change_password`, `otp`, etc. on the Rodauth plugin.
316
158
 
317
- ## Next Steps
159
+ ## Related
318
160
 
319
- - [Authentication](/guides/authentication) - Set up Rodauth features
320
- - [Custom Actions](/guides/custom-actions) - Add profile actions
321
- - [Views](/guides/views) - Customize the profile page
322
- - [Authorization](/guides/authorization) - Configure profile policies
161
+ - [Reference › Auth › Profile](/reference/auth/profile) full surface
162
+ - [Reference › Auth › Accounts](/reference/auth/accounts) Rodauth feature flags that gate SecuritySection
163
+ - [Authentication](./authentication) the underlying auth setup
data/docs/index.md CHANGED
@@ -1,228 +1,19 @@
1
1
  ---
2
- layout: home
3
- hero:
4
- name: Plutonium
5
- text: Ship Rails Apps 10x Faster
6
- tagline: Build production-ready Rails applications in minutes, not days. Convention-driven, fully customizable. Built for the AI era.
7
- image:
8
- src: /plutonium.png
9
- alt: Plutonium
10
- actions:
11
- - theme: brand
12
- text: Get Started →
13
- link: /getting-started/
14
- - theme: alt
15
- text: GitHub
16
- link: https://github.com/radioactive-labs/plutonium-core
2
+ layout: page
3
+ sidebar: false
4
+ aside: false
17
5
  ---
18
6
 
19
- <div class="landing-content">
7
+ <HomeHero />
20
8
 
21
- <section class="before-after">
22
- <h2>The Old Way vs The Plutonium Way</h2>
23
- <div class="comparison">
24
- <div class="before">
25
- <h3>Without Plutonium</h3>
26
- <div class="file-tree">
27
- <div class="file">app/controllers/posts_controller.rb</div>
28
- <div class="file">app/controllers/comments_controller.rb</div>
29
- <div class="file">app/views/posts/index.html.erb</div>
30
- <div class="file">app/views/posts/show.html.erb</div>
31
- <div class="file">app/views/posts/new.html.erb</div>
32
- <div class="file">app/views/posts/edit.html.erb</div>
33
- <div class="file">app/views/posts/_form.html.erb</div>
34
- <div class="file dim">...12 more files</div>
35
- </div>
36
- <div class="stats">
37
- <span>~200 lines</span>
38
- <span>30+ minutes</span>
39
- <span>No auth yet</span>
40
- </div>
41
- </div>
42
- <div class="arrow">→</div>
43
- <div class="after">
44
- <h3>With Plutonium</h3>
9
+ <HomeStopWriting />
45
10
 
46
- ```bash
47
- rails g pu:res:scaffold Post \
48
- title:string body:text
11
+ <HomePillars />
49
12
 
50
- rails g pu:res:conn Post \
51
- --dest=admin_portal
52
- ```
13
+ <HomeWalkthrough />
53
14
 
54
- <div class="stats success">
55
- <span>2 commands</span>
56
- <span>30 seconds</span>
57
- <span>Auth included</span>
58
- </div>
59
- </div>
60
- </div>
61
- </section>
15
+ <HomeAudienceSplit />
62
16
 
63
- <section class="ai-section">
64
- <h2>Built for the AI Era</h2>
65
- <p class="ai-intro">Plutonium is the first Rails framework designed from the ground up for AI-assisted development. Every pattern, every convention, every file structure is optimized for AI comprehension.</p>
17
+ <HomeInTheBox />
66
18
 
67
- <div class="ai-features">
68
- <div class="ai-feature">
69
- <div class="ai-icon">🧠</div>
70
- <h3>Claude Code Skills</h3>
71
- <p>20+ built-in skills teach AI assistants your app's patterns. Resources, policies, definitions, interactions - Claude understands them all.</p>
72
- </div>
73
- <div class="ai-feature">
74
- <div class="ai-icon">⚡</div>
75
- <h3>Predictable Patterns</h3>
76
- <p>Convention-heavy architecture means AI can accurately predict file locations, naming, and relationships. Less hallucination, more precision.</p>
77
- </div>
78
- <div class="ai-feature">
79
- <div class="ai-icon">🔄</div>
80
- <h3>Generate & Iterate</h3>
81
- <p>Tell Claude what you need. It generates the scaffold, policy, and definition. You refine. Ship in minutes what used to take hours.</p>
82
- </div>
83
- </div>
84
-
85
- <div class="ai-example">
86
- <div class="ai-prompt">
87
- <span class="prompt-label">You say:</span>
88
- <p>"Add a blog with posts and comments. Posts belong to users. Only authors can edit their posts. Add a publish action."</p>
89
- </div>
90
- <div class="ai-result">
91
- <span class="result-label">Claude generates:</span>
92
- <p>Model, migration, policy, definition, interaction, and connects it to your portal. Ready to customize.</p>
93
- </div>
94
- </div>
95
- </section>
96
-
97
- <section class="features-detailed">
98
- <h2>Everything You Need, Nothing You Don't</h2>
99
-
100
- <div class="feature-row">
101
- <div class="feature-text">
102
- <h3>Resources Are Your Foundation</h3>
103
- <p>Just ActiveRecord. Associations, scopes, validations you already know. No new ORM to learn.</p>
104
- </div>
105
- <div class="feature-code">
106
-
107
- ```ruby
108
- class Post < ApplicationRecord
109
- include Plutonium::Resource::Record
110
-
111
- belongs_to :author, class_name: "User"
112
- has_many :comments
113
-
114
- scope :published, -> { where.not(published_at: nil) }
115
- scope :drafts, -> { where(published_at: nil) }
116
- end
117
- ```
118
-
119
- </div>
120
- </div>
121
-
122
- <div class="feature-row reverse">
123
- <div class="feature-text">
124
- <h3>Definitions Control UI</h3>
125
- <p>Declare how fields render. Add search, filters, scopes. Custom actions. All in one place.</p>
126
- </div>
127
- <div class="feature-code">
128
-
129
- ```ruby
130
- class PostDefinition < ResourceDefinition
131
- input :body, as: :markdown
132
-
133
- search do |scope, query|
134
- scope.where("title ILIKE ?", "%#{query}%")
135
- end
136
-
137
- scope :published
138
- scope :drafts
139
-
140
- action :publish, interaction: PublishPost
141
- end
142
- ```
143
-
144
- </div>
145
- </div>
146
-
147
- <div class="feature-row">
148
- <div class="feature-text">
149
- <h3>Interactions Encapsulate Logic</h3>
150
- <p>Complex actions become simple classes. Validated inputs. Clear outcomes. Easy to test.</p>
151
- </div>
152
- <div class="feature-code">
153
-
154
- ```ruby
155
- class PublishPost < ResourceInteraction
156
- attribute :resource
157
- attribute :publish_at, :datetime
158
-
159
- def execute
160
- resource.published_at = publish_at
161
- if resource.save
162
- succeed(resource).with_message("Published!")
163
- else
164
- failed(resource.errors)
165
- end
166
- end
167
- end
168
- ```
169
-
170
- </div>
171
- </div>
172
-
173
- <div class="feature-row reverse">
174
- <div class="feature-text">
175
- <h3>Policies Control Access</h3>
176
- <p>Define who can do what. Attribute-level permissions. Automatic scoping. No more <code>if current_user.admin?</code> scattered everywhere.</p>
177
- </div>
178
- <div class="feature-code">
179
-
180
- ```ruby
181
- class PostPolicy < ResourcePolicy
182
- def update?
183
- record.author == user || user.admin?
184
- end
185
-
186
- def permitted_attributes_for_create
187
- %i[title body]
188
- end
189
- end
190
- ```
191
-
192
- </div>
193
- </div>
194
- </section>
195
-
196
- <section class="feature-grid">
197
- <div class="grid-item">
198
- <div class="icon">📦</div>
199
- <h3>Modular Packages</h3>
200
- <p>Split your app into Feature Packages and Portals. Each isolated, testable, and reusable.</p>
201
- </div>
202
- <div class="grid-item">
203
- <div class="icon">🔐</div>
204
- <h3>Auth Built In</h3>
205
- <p>Rodauth integration with login, registration, 2FA, and password reset. Ready in one command.</p>
206
- </div>
207
- <div class="grid-item">
208
- <div class="icon">🏢</div>
209
- <h3>Multi-Tenancy</h3>
210
- <p>Entity scoping works out of the box. Path-based or custom strategies. Data isolation guaranteed.</p>
211
- </div>
212
- <div class="grid-item">
213
- <div class="icon">🎨</div>
214
- <h3>Fully Customizable</h3>
215
- <p>Override any layer. Custom views with Phlex. Your CSS. No black boxes.</p>
216
- </div>
217
- </section>
218
-
219
- <section class="cta-section">
220
- <h2>Ready to Build Faster?</h2>
221
- <p>Get a complete admin interface running in under 5 minutes.</p>
222
- <div class="cta-buttons">
223
- <a href="./getting-started/" class="cta-primary">Get Started</a>
224
- <a href="./getting-started/tutorial/" class="cta-secondary">Follow the Tutorial</a>
225
- </div>
226
- </section>
227
-
228
- </div>
19
+ <HomeCta />