plutonium 0.50.0 → 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 (132) 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 +27 -2
  11. data/Rakefile +2 -1
  12. data/app/assets/plutonium.css +1 -11
  13. data/app/assets/plutonium.js +1009 -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 +37 -27
  18. data/docs/getting-started/index.md +22 -29
  19. data/docs/getting-started/installation.md +37 -80
  20. data/docs/getting-started/tutorial/index.md +4 -5
  21. data/docs/guides/adding-resources.md +66 -377
  22. data/docs/guides/authentication.md +94 -463
  23. data/docs/guides/authorization.md +124 -370
  24. data/docs/guides/creating-packages.md +94 -296
  25. data/docs/guides/custom-actions.md +121 -441
  26. data/docs/guides/index.md +22 -42
  27. data/docs/guides/multi-tenancy.md +116 -187
  28. data/docs/guides/nested-resources.md +103 -431
  29. data/docs/guides/search-filtering.md +123 -240
  30. data/docs/guides/testing.md +5 -4
  31. data/docs/guides/theming.md +157 -407
  32. data/docs/guides/troubleshooting.md +5 -3
  33. data/docs/guides/user-invites.md +106 -425
  34. data/docs/guides/user-profile.md +76 -243
  35. data/docs/index.md +1 -1
  36. data/docs/reference/app/generators.md +517 -0
  37. data/docs/reference/app/index.md +158 -0
  38. data/docs/reference/app/packages.md +146 -0
  39. data/docs/reference/app/portals.md +377 -0
  40. data/docs/reference/auth/accounts.md +230 -0
  41. data/docs/reference/auth/index.md +88 -0
  42. data/docs/reference/auth/profile.md +185 -0
  43. data/docs/reference/behavior/controllers.md +395 -0
  44. data/docs/reference/behavior/index.md +22 -0
  45. data/docs/reference/behavior/interactions.md +341 -0
  46. data/docs/reference/behavior/policies.md +417 -0
  47. data/docs/reference/index.md +56 -49
  48. data/docs/reference/resource/actions.md +423 -0
  49. data/docs/reference/resource/definition.md +508 -0
  50. data/docs/reference/resource/index.md +50 -0
  51. data/docs/reference/resource/model.md +348 -0
  52. data/docs/reference/resource/query.md +305 -0
  53. data/docs/reference/tenancy/entity-scoping.md +361 -0
  54. data/docs/reference/tenancy/index.md +36 -0
  55. data/docs/reference/tenancy/invites.md +393 -0
  56. data/docs/reference/tenancy/nested-resources.md +267 -0
  57. data/docs/reference/testing/index.md +287 -0
  58. data/docs/reference/ui/assets.md +400 -0
  59. data/docs/reference/ui/components.md +165 -0
  60. data/docs/reference/ui/displays.md +104 -0
  61. data/docs/reference/ui/forms.md +284 -0
  62. data/docs/reference/ui/index.md +30 -0
  63. data/docs/reference/ui/layouts.md +106 -0
  64. data/docs/reference/ui/pages.md +189 -0
  65. data/docs/reference/ui/tables.md +117 -0
  66. data/docs/superpowers/specs/2026-05-09-typeahead-endpoint-design.md +203 -0
  67. data/docs/superpowers/specs/2026-05-12-skill-compaction-design.md +99 -0
  68. data/docs/superpowers/specs/2026-05-13-docs-restructure-design.md +186 -0
  69. data/gemfiles/rails_7.gemfile.lock +1 -1
  70. data/gemfiles/rails_8.0.gemfile.lock +1 -1
  71. data/gemfiles/rails_8.1.gemfile.lock +1 -1
  72. data/lib/generators/pu/core/update/update_generator.rb +0 -20
  73. data/lib/generators/pu/invites/install_generator.rb +1 -0
  74. data/lib/plutonium/definition/base.rb +1 -1
  75. data/lib/plutonium/definition/{views.rb → index_views.rb} +21 -20
  76. data/lib/plutonium/helpers/turbo_helper.rb +11 -0
  77. data/lib/plutonium/helpers/turbo_stream_actions_helper.rb +14 -0
  78. data/lib/plutonium/resource/controller.rb +1 -0
  79. data/lib/plutonium/resource/controllers/crud_actions.rb +19 -1
  80. data/lib/plutonium/resource/controllers/typeahead.rb +180 -0
  81. data/lib/plutonium/resource/policy.rb +7 -0
  82. data/lib/plutonium/routing/mapper_extensions.rb +15 -0
  83. data/lib/plutonium/ui/component/methods.rb +4 -0
  84. data/lib/plutonium/ui/form/base.rb +6 -2
  85. data/lib/plutonium/ui/form/components/json.rb +58 -0
  86. data/lib/plutonium/ui/form/components/resource_select.rb +62 -8
  87. data/lib/plutonium/ui/form/components/secure_association.rb +98 -22
  88. data/lib/plutonium/ui/form/concerns/typeahead_attributes.rb +83 -0
  89. data/lib/plutonium/ui/form/resource.rb +0 -4
  90. data/lib/plutonium/ui/grid/resource.rb +1 -1
  91. data/lib/plutonium/ui/layout/base.rb +1 -0
  92. data/lib/plutonium/ui/page/base.rb +0 -7
  93. data/lib/plutonium/ui/page/index.rb +4 -4
  94. data/lib/plutonium/ui/table/resource.rb +1 -1
  95. data/lib/plutonium/version.rb +1 -1
  96. data/lib/plutonium.rb +8 -0
  97. data/lib/tasks/release.rake +15 -1
  98. data/package.json +10 -10
  99. data/src/css/slim_select.css +4 -0
  100. data/src/js/controllers/slim_select_controller.js +61 -0
  101. data/src/js/turbo/turbo_actions.js +33 -0
  102. data/yarn.lock +553 -543
  103. metadata +44 -33
  104. data/.claude/skills/plutonium-assets/SKILL.md +0 -512
  105. data/.claude/skills/plutonium-controller/SKILL.md +0 -396
  106. data/.claude/skills/plutonium-create-resource/SKILL.md +0 -303
  107. data/.claude/skills/plutonium-definition/SKILL.md +0 -1223
  108. data/.claude/skills/plutonium-entity-scoping/SKILL.md +0 -317
  109. data/.claude/skills/plutonium-forms/SKILL.md +0 -465
  110. data/.claude/skills/plutonium-installation/SKILL.md +0 -331
  111. data/.claude/skills/plutonium-interaction/SKILL.md +0 -413
  112. data/.claude/skills/plutonium-invites/SKILL.md +0 -408
  113. data/.claude/skills/plutonium-model/SKILL.md +0 -440
  114. data/.claude/skills/plutonium-nested-resources/SKILL.md +0 -360
  115. data/.claude/skills/plutonium-package/SKILL.md +0 -198
  116. data/.claude/skills/plutonium-policy/SKILL.md +0 -456
  117. data/.claude/skills/plutonium-portal/SKILL.md +0 -410
  118. data/.claude/skills/plutonium-views/SKILL.md +0 -651
  119. data/docs/reference/assets/index.md +0 -496
  120. data/docs/reference/controller/index.md +0 -412
  121. data/docs/reference/definition/actions.md +0 -462
  122. data/docs/reference/definition/fields.md +0 -383
  123. data/docs/reference/definition/index.md +0 -326
  124. data/docs/reference/definition/query.md +0 -351
  125. data/docs/reference/generators/index.md +0 -648
  126. data/docs/reference/interaction/index.md +0 -449
  127. data/docs/reference/model/features.md +0 -248
  128. data/docs/reference/model/index.md +0 -218
  129. data/docs/reference/policy/index.md +0 -456
  130. data/docs/reference/portal/index.md +0 -379
  131. data/docs/reference/views/forms.md +0 -411
  132. data/docs/reference/views/index.md +0 -544
@@ -1,322 +1,155 @@
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
18
-
19
- The easiest way to set this up is with the SaaS generator:
15
+ ## Quick path
20
16
 
21
17
  ```bash
22
- rails g pu:saas:user User
18
+ rails g pu:profile:setup date_of_birth:date bio:text \
19
+ --dest=competition \
20
+ --portal=competition_portal
23
21
  ```
24
22
 
25
- ## Installation
26
-
27
- ### Step 1: Install the Profile Resource
23
+ `pu:profile:setup` is a meta-generator — runs `pu:profile:install` + `pu:profile:conn` in one shot.
28
24
 
29
- ```bash
30
- rails generate pu:profile:install --dest=main_app
31
- ```
25
+ ## Step-by-step
32
26
 
33
- With custom fields:
27
+ ### 1. Install
34
28
 
35
29
  ```bash
36
- rails g pu:profile:install \
37
- bio:text \
38
- avatar:attachment \
39
- 'timezone:string?' \
40
- notifications_enabled:boolean \
41
- --dest=main_app
30
+ rails generate pu:profile:install bio:text avatar:attachment 'timezone:string?' \
31
+ --dest=customer
42
32
  ```
43
33
 
44
- ### Options
45
-
46
34
  | Option | Default | Description |
47
- |--------|---------|-------------|
48
- | `--dest` | (prompts) | Target destination package or main_app |
49
- | `--user-model` | User | Rodauth user model name |
35
+ |---|---|---|
36
+ | `--dest=DEST` | (prompts) | Target package or `main_app` |
37
+ | `--user-model=NAME` | `User` | Rodauth user model |
50
38
 
51
- ### Step 2: Run Migrations
39
+ Custom resource name (first positional argument):
52
40
 
53
41
  ```bash
54
- rails db:migrate
42
+ rails g pu:profile:install AccountSettings bio:text --dest=main_app
55
43
  ```
56
44
 
57
- ### Step 3: Connect to Portal
45
+ By default the model is `{UserModel}Profile` (`UserProfile`, `StaffUserProfile`, etc.).
58
46
 
59
- Connect the Profile to your portal using the profile connect generator:
47
+ ### 2. Migrate
60
48
 
61
49
  ```bash
62
- rails g pu:profile:conn --dest=customer_portal
50
+ rails db:migrate
63
51
  ```
64
52
 
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
53
+ ### 3. Connect to a portal
70
54
 
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
55
+ ```bash
56
+ rails g pu:profile:conn --dest=customer_portal
79
57
  ```
80
58
 
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
59
+ This registers the profile as a **singular** resource — exposes `/profile` (no `:id`) and the `profile_url` helper.
102
60
 
103
- Users need a profile created before they can access it. Choose one approach:
104
-
105
- ### Option A: Automatic Creation on User Signup
61
+ ### 4. Add the auto-create callback
106
62
 
107
63
  ```ruby
108
- # app/models/user.rb
64
+ # app/models/user.rb (modified by pu:profile:install)
109
65
  class User < ApplicationRecord
110
- has_one :profile, dependent: :destroy
66
+ has_one :profile, class_name: "UserProfile", dependent: :destroy
111
67
 
112
68
  after_create :create_profile!
113
69
 
114
70
  private
115
-
116
- def create_profile!
117
- create_profile
118
- end
119
-
120
- # add has_one associations above.
71
+ def create_profile! = create_profile
121
72
  end
122
73
  ```
123
74
 
124
- ### Option B: Create on First Access
125
-
126
- In your portal's application controller:
75
+ For existing users at migration time:
127
76
 
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
77
+ ```bash
78
+ rails runner "User.find_each(&:create_profile)"
139
79
  ```
140
80
 
141
- ### Option C: Find or Create in Profile Controller
81
+ ## What you get
142
82
 
143
- ```ruby
144
- # app/controllers/profiles_controller.rb
145
- class ProfilesController < ResourceController
146
- private
83
+ The generated definition injects a custom `ShowPage` that renders `SecuritySection` — dynamically lists Rodauth security links based on which features are enabled:
147
84
 
148
- def current_resource
149
- @current_resource ||= current_user.profile || current_user.create_profile
150
- end
151
- end
152
- ```
85
+ | Feature enabled | Link rendered |
86
+ |---|---|
87
+ | `change_password` | Change Password |
88
+ | `change_login` | Change Email |
89
+ | `otp` | Two-Factor Authentication |
90
+ | `recovery_codes` | Recovery Codes |
91
+ | `webauthn` | Security Keys |
92
+ | `active_sessions` | Active Sessions |
93
+ | `close_account` | Close Account |
153
94
 
154
- ## Customization
95
+ If a feature isn't enabled, its link doesn't render — no configuration needed.
155
96
 
156
- ### Adding Custom Fields
157
-
158
- Edit the generated definition to configure how fields appear:
97
+ ## Linking to the profile
159
98
 
160
99
  ```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
100
+ link_to("Profile", profile_url) if respond_to?(:profile_url)
185
101
  ```
186
102
 
187
- ### Custom Security Section
103
+ The `respond_to?` guard is defensive — only portals that ran `pu:profile:conn` have the helper.
104
+
105
+ ## Customizing the definition
188
106
 
189
- Create your own security section component:
107
+ The generated profile is a normal Plutonium definition. Customize like any other:
190
108
 
191
109
  ```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
- ```
110
+ class UserProfileDefinition < Plutonium::Resource::Definition
111
+ field :bio, as: :markdown
112
+ input :avatar, as: :uppy
113
+ field :timezone, as: :select, choices: ActiveSupport::TimeZone.all.map(&:name)
208
114
 
209
- Use it in your definition:
115
+ metadata :created_at, :updated_at
210
116
 
211
- ```ruby
212
- class ProfileDefinition < Plutonium::Resource::Definition
213
117
  class ShowPage < ShowPage
214
118
  private
215
119
 
216
120
  def render_after_content
217
- render CustomSecuritySection.new
121
+ render Plutonium::Profile::SecuritySection.new
218
122
  end
219
123
  end
220
124
  end
221
125
  ```
222
126
 
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
127
+ See [Reference Resource › Definition](/reference/resource/definition) for the full definition surface.
280
128
 
281
- Ensure the user has a profile created. Use one of the creation strategies above.
129
+ ## Multiple account types
282
130
 
283
- ### Security Links Not Showing
131
+ If your app has both `User` and `StaffUser` accounts, run `pu:profile:install` once per:
284
132
 
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
133
+ ```bash
134
+ rails g pu:profile:install --user-model=User --dest=main_app
135
+ rails g pu:profile:install --user-model=StaffUser --dest=main_app
295
136
  ```
296
137
 
297
- ### Profile Routes Conflicting
298
-
299
- Ensure you use `--singular` when connecting:
138
+ Each gets its own `*Profile` model with `:profile` association on the respective user. Connect each to the appropriate portal:
300
139
 
301
140
  ```bash
302
- rails g pu:res:conn Profile --dest=my_portal --singular
141
+ rails g pu:profile:conn UserProfile --dest=customer_portal
142
+ rails g pu:profile:conn StaffUserProfile --dest=admin_portal
303
143
  ```
304
144
 
305
- This creates `/profile` (singular) instead of `/profiles/:id`.
145
+ ## Common issues
306
146
 
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
- ```
147
+ - **`current_user.profile` is nil** — every user needs a profile row. Add `after_create :create_profile!` to the user model.
148
+ - **`profile_url` is undefined** — the profile isn't connected to this portal. Run `pu:profile:conn --dest=<portal>`.
149
+ - **`SecuritySection` shows nothing** — none of the relevant Rodauth features are enabled. Enable `change_password`, `otp`, etc. on the Rodauth plugin.
316
150
 
317
- ## Next Steps
151
+ ## Related
318
152
 
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
153
+ - [Reference › Auth › Profile](/reference/auth/profile) full surface
154
+ - [Reference › Auth › Accounts](/reference/auth/accounts) Rodauth feature flags that gate SecuritySection
155
+ - [Authentication](./authentication) the underlying auth setup
data/docs/index.md CHANGED
@@ -68,7 +68,7 @@ rails g pu:res:conn Post \
68
68
  <div class="ai-feature">
69
69
  <div class="ai-icon">🧠</div>
70
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>
71
+ <p>Built-in skills teach AI assistants your app's patterns. Resources, policies, definitions, interactions Claude understands them all.</p>
72
72
  </div>
73
73
  <div class="ai-feature">
74
74
  <div class="ai-icon">⚡</div>