plutonium 0.42.0 โ†’ 0.43.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 (75) hide show
  1. checksums.yaml +4 -4
  2. data/.claude/skills/plutonium-controller/SKILL.md +38 -1
  3. data/.claude/skills/plutonium-definition/SKILL.md +14 -0
  4. data/.claude/skills/plutonium-forms/SKILL.md +16 -1
  5. data/.claude/skills/plutonium-profile/SKILL.md +276 -0
  6. data/.claude/skills/plutonium-views/SKILL.md +23 -1
  7. data/CHANGELOG.md +36 -0
  8. data/app/assets/plutonium.css +1 -1
  9. data/app/views/plutonium/_resource_header.html.erb +6 -27
  10. data/app/views/plutonium/_resource_sidebar.html.erb +1 -2
  11. data/app/views/resource/_resource_details.rabl +3 -2
  12. data/app/views/resource/index.rabl +3 -2
  13. data/app/views/resource/show.rabl +3 -2
  14. data/docs/guides/user-profile.md +322 -0
  15. data/docs/reference/controller/index.md +38 -1
  16. data/docs/reference/definition/index.md +16 -0
  17. data/docs/reference/views/forms.md +15 -0
  18. data/docs/reference/views/index.md +23 -1
  19. data/gemfiles/rails_7.gemfile.lock +1 -1
  20. data/gemfiles/rails_8.0.gemfile.lock +1 -1
  21. data/gemfiles/rails_8.1.gemfile.lock +1 -1
  22. data/lib/generators/pu/core/assets/assets_generator.rb +12 -0
  23. data/lib/generators/pu/core/install/templates/app/controllers/resource_controller.rb.tt +11 -0
  24. data/lib/generators/pu/core/typespec/templates/common.tsp.tt +95 -0
  25. data/lib/generators/pu/core/typespec/templates/main.tsp.tt +27 -0
  26. data/lib/generators/pu/core/typespec/templates/main_multi.tsp.tt +25 -0
  27. data/lib/generators/pu/core/typespec/templates/model.tsp.tt +226 -0
  28. data/lib/generators/pu/core/typespec/typespec_generator.rb +342 -0
  29. data/lib/generators/pu/invites/USAGE +0 -1
  30. data/lib/generators/pu/invites/install_generator.rb +62 -15
  31. data/lib/generators/pu/invites/templates/db/migrate/create_user_invites.rb.tt +2 -2
  32. data/lib/generators/pu/invites/templates/packages/invites/app/controllers/invites/user_invitations_controller.rb.tt +2 -0
  33. data/lib/generators/pu/invites/templates/packages/invites/app/controllers/invites/welcome_controller.rb.tt +1 -0
  34. data/lib/generators/pu/invites/templates/packages/invites/app/models/invites/user_invite.rb.tt +5 -5
  35. data/lib/generators/pu/invites/templates/packages/invites/app/views/invites/user_invitations/signup.html.erb.tt +4 -4
  36. data/lib/generators/pu/lib/plutonium_generators/concerns/actions.rb +1 -1
  37. data/lib/generators/pu/lib/plutonium_generators/generator.rb +29 -0
  38. data/lib/generators/pu/lib/plutonium_generators/model_generator_base.rb +6 -23
  39. data/lib/generators/pu/pkg/portal/portal_generator.rb +5 -1
  40. data/lib/generators/pu/profile/USAGE +59 -0
  41. data/lib/generators/pu/profile/concerns/profile_arguments.rb +27 -0
  42. data/lib/generators/pu/profile/conn/USAGE +33 -0
  43. data/lib/generators/pu/profile/conn_generator.rb +167 -0
  44. data/lib/generators/pu/profile/install_generator.rb +119 -0
  45. data/lib/generators/pu/profile/setup/USAGE +42 -0
  46. data/lib/generators/pu/profile/setup_generator.rb +73 -0
  47. data/lib/generators/pu/rodauth/account_generator.rb +2 -4
  48. data/lib/generators/pu/rodauth/install_generator.rb +2 -2
  49. data/lib/generators/pu/rodauth/templates/app/rodauth/account_rodauth_plugin.rb.tt +3 -0
  50. data/lib/generators/pu/saas/api_client_generator.rb +0 -2
  51. data/lib/generators/pu/saas/membership_generator.rb +68 -19
  52. data/lib/generators/pu/saas/setup_generator.rb +7 -2
  53. data/lib/generators/pu/saas/user_generator.rb +0 -2
  54. data/lib/plutonium/auth/rodauth.rb +8 -0
  55. data/lib/plutonium/core/controller.rb +7 -4
  56. data/lib/plutonium/core/controllers/authorizable.rb +5 -1
  57. data/lib/plutonium/definition/base.rb +7 -0
  58. data/lib/plutonium/helpers/display_helper.rb +6 -0
  59. data/lib/plutonium/profile/security_section.rb +118 -0
  60. data/lib/plutonium/resource/controller.rb +17 -7
  61. data/lib/plutonium/resource/controllers/interactive_actions.rb +11 -25
  62. data/lib/plutonium/resource/controllers/presentable.rb +46 -3
  63. data/lib/plutonium/resource/record/associated_with.rb +7 -1
  64. data/lib/plutonium/routing/mapper_extensions.rb +18 -18
  65. data/lib/plutonium/routing/route_set_extensions.rb +23 -2
  66. data/lib/plutonium/ui/breadcrumbs.rb +111 -131
  67. data/lib/plutonium/ui/dyna_frame/content.rb +12 -2
  68. data/lib/plutonium/ui/form/resource.rb +26 -19
  69. data/lib/plutonium/ui/page/base.rb +14 -14
  70. data/lib/plutonium/ui/table/components/selection_column.rb +6 -2
  71. data/lib/plutonium/ui/table/resource.rb +3 -2
  72. data/lib/plutonium/version.rb +1 -1
  73. data/package.json +1 -1
  74. metadata +17 -3
  75. data/lib/generators/pu/rodauth/concerns/gem_helpers.rb +0 -19
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cb19ed41e9f34ec8325aae0659a5db7533c163ed68c98ed0f8fcbd0868b21f93
4
- data.tar.gz: 6c4f9d70ac8840b61a3c41f9f7ff721f8be3ab1d5d57c3142525e15b72042009
3
+ metadata.gz: c6775aec4947b991b03ae8a90d29761106765bf7a960153b938298d366a5c5b0
4
+ data.tar.gz: bc7efa2d03feaa76610d747a182c9bf4e045c55a0af8ea21b743390b19061b5e
5
5
  SHA512:
6
- metadata.gz: 8079e42372a54b1bf3c1de34efc94bc6fe559d725571b5c40971faccb55df7b63ff9831c4e1830c95663c94317bf77a8b9b6fd3b4ea12724a16a52d80deb2066
7
- data.tar.gz: 794c3b42b783652814bea1a226e14ccf45fadb8c6e8bc8643e88606164d10324ebda15518d31e58d4c1d2084c07fd7a3c6d0c95cfc50261ccd76925b1a157fbf
6
+ metadata.gz: 3e5686e8560df32a8913be75ac08d7ce0df8543810982322843435c55dd1606b0412f619732613fa55c9252867b3a6f3553fd4d0ff10f08633b9c7bfb519cad2
7
+ data.tar.gz: 205ee9f10dbffd3e0437b48aa04b1c9f61d0ab6ee67ee208e084f318021b0816fbc01b1b143a455a02a907cbb5e246b0ac25ec5e7dd75c7e08ebfcce5dc4fc31
@@ -265,9 +265,46 @@ end
265
265
 
266
266
  Controllers automatically:
267
267
  - Scope all queries to the entity
268
- - Exclude entity field from forms
268
+ - Exclude entity field from forms (detected by association class)
269
+ - Inject entity value on create/update
269
270
  - Provide `current_scoped_entity` method
270
271
 
272
+ ### Association Detection
273
+
274
+ Plutonium auto-detects which `belongs_to` association points to the scoped entity class. This works even when `param_key` differs from the association name:
275
+
276
+ ```ruby
277
+ # Portal config
278
+ scope_to_entity Competition::Team, param_key: :team
279
+
280
+ # Model (association name differs from param_key)
281
+ class Match < ApplicationRecord
282
+ belongs_to :competition_team # Plutonium finds this by class
283
+ end
284
+ ```
285
+
286
+ ### Multiple Associations to Same Class
287
+
288
+ If a model has multiple associations to the scoped entity class, Plutonium raises an error:
289
+
290
+ ```
291
+ Match has multiple associations to Competition::Team: home_team, away_team.
292
+ Plutonium cannot auto-detect which one to use for entity scoping.
293
+ Override `scoped_entity_association` in your controller to specify the association.
294
+ ```
295
+
296
+ Resolve by overriding `scoped_entity_association`:
297
+
298
+ ```ruby
299
+ class MatchesController < ::ResourceController
300
+ private
301
+
302
+ def scoped_entity_association
303
+ :home_team # Return the association name as a symbol
304
+ end
305
+ end
306
+ ```
307
+
271
308
  ## Authorization Verification
272
309
 
273
310
  Controllers verify authorization was performed:
@@ -170,6 +170,20 @@ end
170
170
  | `input` blocks | Yes | Yes | Yes |
171
171
  | Page title procs | Yes | Yes (current_record!) | Yes |
172
172
 
173
+ ## Form Configuration
174
+
175
+ ```ruby
176
+ class PostDefinition < ResourceDefinition
177
+ # Controls "Save and add another" / "Update and continue editing" buttons
178
+ # nil (default) = auto-detect (hidden for singular resources, shown for plural)
179
+ # true = always show
180
+ # false = always hide
181
+ submit_and_continue false
182
+ end
183
+ ```
184
+
185
+ Singular resources (e.g., `resource :profile` routes or `has_one` nested) auto-hide this button since creating "another" doesn't make sense.
186
+
173
187
  ## Page Customization
174
188
 
175
189
  ```ruby
@@ -327,12 +327,27 @@ end
327
327
 
328
328
  ## Form Actions
329
329
 
330
+ ### Submit and Continue Button
331
+
332
+ Forms include a secondary button ("Create and add another" for new records, "Update and continue editing" for existing). Control this in your definition:
333
+
334
+ ```ruby
335
+ class PostDefinition < ResourceDefinition
336
+ # nil (default) = auto-detect (hidden for singular resources, shown for plural)
337
+ # true = always show
338
+ # false = always hide
339
+ submit_and_continue false
340
+ end
341
+ ```
342
+
343
+ Singular resources (e.g., `resource :profile` routes or `has_one` nested) auto-hide this button.
344
+
330
345
  ### Default Actions
331
346
 
332
347
  ```ruby
333
348
  def render_actions
334
349
  actions_wrapper {
335
- # "Create and add another" / "Update and continue editing" button
350
+ # "Create and add another" / "Update and continue editing" button (if enabled)
336
351
  # Primary submit button
337
352
  render submit_button
338
353
  }
@@ -0,0 +1,276 @@
1
+ ---
2
+ name: plutonium-profile
3
+ description: Plutonium user profile - account settings page with Rodauth security links
4
+ ---
5
+
6
+ # Plutonium User Profile
7
+
8
+ Plutonium provides a Profile resource generator for managing Rodauth account settings. The profile resource allows users to:
9
+ - View and edit their profile information
10
+ - Access Rodauth security features (change password, 2FA, etc.)
11
+ - Manage their account settings in one place
12
+
13
+ ## Quick Setup
14
+
15
+ Use the setup generator to create and connect the profile in one command:
16
+
17
+ ```bash
18
+ rails g pu:profile:setup date_of_birth:date bio:text \
19
+ --dest=competition \
20
+ --portal=competition_portal
21
+ ```
22
+
23
+ ## Step-by-Step Installation
24
+
25
+ ### Install the Profile Resource
26
+
27
+ ```bash
28
+ rails generate pu:profile:install --dest=main_app
29
+ ```
30
+
31
+ **Options:**
32
+
33
+ | Option | Default | Description |
34
+ |--------|---------|-------------|
35
+ | `--dest=DESTINATION` | (prompts) | Target package or main_app |
36
+ | `--user-model=NAME` | User | Rodauth user model name |
37
+
38
+ **With custom fields:**
39
+
40
+ ```bash
41
+ rails g pu:profile:install \
42
+ bio:text \
43
+ avatar:attachment \
44
+ 'timezone:string?' \
45
+ --dest=customer
46
+ ```
47
+
48
+ **With custom name:**
49
+
50
+ ```bash
51
+ rails g pu:profile:install AccountSettings \
52
+ bio:text \
53
+ --dest=main_app
54
+ ```
55
+
56
+ ## What Gets Created
57
+
58
+ The generator creates a standard Plutonium resource:
59
+
60
+ ```
61
+ app/models/[package/]profile.rb # Profile model
62
+ db/migrate/xxx_create_profiles.rb # Migration
63
+ app/controllers/[package/]profiles_controller.rb
64
+ app/policies/[package/]profile_policy.rb
65
+ app/definitions/[package/]profile_definition.rb
66
+ ```
67
+
68
+ And modifies:
69
+ - **User model**: Adds `has_one :profile, dependent: :destroy`
70
+ - **Definition**: Injects custom ShowPage with SecuritySection
71
+
72
+ ## The SecuritySection Component
73
+
74
+ The generator injects a custom `ShowPage` that renders `Plutonium::Profile::SecuritySection`:
75
+
76
+ ```ruby
77
+ class ProfileDefinition < Plutonium::Resource::Definition
78
+ class ShowPage < ShowPage
79
+ private
80
+
81
+ def render_after_content
82
+ render Plutonium::Profile::SecuritySection.new
83
+ end
84
+ end
85
+ end
86
+ ```
87
+
88
+ The `SecuritySection` component dynamically checks which Rodauth features are enabled and displays links for:
89
+
90
+ | Feature | Label | Description |
91
+ |---------|-------|-------------|
92
+ | `change_password` | Change Password | Update account password |
93
+ | `change_login` | Change Email | Update email address |
94
+ | `otp` | Two-Factor Authentication | Set up TOTP |
95
+ | `recovery_codes` | Recovery Codes | View/regenerate backup codes |
96
+ | `webauthn` | Security Keys | Manage passkeys |
97
+ | `active_sessions` | Active Sessions | View/manage sessions |
98
+ | `close_account` | Close Account | Delete account |
99
+
100
+ Only enabled Rodauth features are displayed.
101
+
102
+ ## After Generation
103
+
104
+ ### 1. Run Migrations
105
+
106
+ ```bash
107
+ rails db:migrate
108
+ ```
109
+
110
+ ### 2. Connect to Portal
111
+
112
+ Use the profile connect generator to register as a singular resource and configure the `profile_url` helper:
113
+
114
+ ```bash
115
+ rails g pu:profile:conn --dest=customer_portal
116
+ ```
117
+
118
+ This:
119
+ - Registers the Profile as a singular resource (`/profile` instead of `/profiles/:id`)
120
+ - Adds `profile_url` helper to enable the "Profile" link in the user menu
121
+
122
+ ### 3. Create Profile Automatically
123
+
124
+ Users need a profile created. Add a callback or use `find_or_create`:
125
+
126
+ ```ruby
127
+ # Option A: Callback on User
128
+ class User < ApplicationRecord
129
+ after_create :create_profile!
130
+
131
+ private
132
+
133
+ def create_profile!
134
+ create_profile
135
+ end
136
+ end
137
+
138
+ # Option B: In controller or before_action
139
+ def current_profile
140
+ @current_profile ||= current_user.profile || current_user.create_profile
141
+ end
142
+ ```
143
+
144
+ ## Customization
145
+
146
+ ### Adding Profile Fields
147
+
148
+ Add fields during generation:
149
+
150
+ ```bash
151
+ rails g pu:profile:install \
152
+ bio:text \
153
+ avatar:attachment \
154
+ website:string \
155
+ 'company:string?' \
156
+ --dest=main_app
157
+ ```
158
+
159
+ Or add to the migration manually before running.
160
+
161
+ ### Customizing the Definition
162
+
163
+ Edit the generated definition:
164
+
165
+ ```ruby
166
+ # app/definitions/profile_definition.rb
167
+ class ProfileDefinition < Plutonium::Resource::Definition
168
+ # Form configuration
169
+ form do |f|
170
+ f.field :bio
171
+ f.field :avatar
172
+ f.field :website
173
+ end
174
+
175
+ # Display configuration
176
+ display do |d|
177
+ d.field :bio
178
+ d.field :avatar
179
+ d.field :website
180
+ end
181
+
182
+ class ShowPage < ShowPage
183
+ private
184
+
185
+ def render_after_content
186
+ render Plutonium::Profile::SecuritySection.new
187
+ end
188
+ end
189
+ end
190
+ ```
191
+
192
+ ### Custom Security Section
193
+
194
+ Override the SecuritySection or create your own:
195
+
196
+ ```ruby
197
+ class ProfileDefinition < Plutonium::Resource::Definition
198
+ class ShowPage < ShowPage
199
+ private
200
+
201
+ def render_after_content
202
+ render CustomSecuritySection.new
203
+ end
204
+ end
205
+ end
206
+ ```
207
+
208
+ ### Adding Custom Actions
209
+
210
+ Add profile-specific actions:
211
+
212
+ ```ruby
213
+ class ProfileDefinition < Plutonium::Resource::Definition
214
+ action :export_data,
215
+ interaction: Profile::ExportDataInteraction
216
+
217
+ action :verify_email,
218
+ interaction: Profile::VerifyEmailInteraction,
219
+ category: :secondary
220
+ end
221
+ ```
222
+
223
+ ## Profile Link in Header
224
+
225
+ To add a profile link to the resource header, the `profile_url` helper is available via `Plutonium::Auth::Rodauth`:
226
+
227
+ ```ruby
228
+ # In your controller or view
229
+ if respond_to?(:profile_url)
230
+ link_to "Profile", profile_url
231
+ end
232
+ ```
233
+
234
+ This helper is automatically available when Profile is connected to a portal.
235
+
236
+ ## Troubleshooting
237
+
238
+ ### "User model not found"
239
+
240
+ Ensure the User model exists at `app/models/user.rb` with the marker comment:
241
+
242
+ ```ruby
243
+ class User < ApplicationRecord
244
+ # add has_one associations above.
245
+ end
246
+ ```
247
+
248
+ ### "Definition path not found"
249
+
250
+ If using a package destination, ensure the package exists:
251
+
252
+ ```bash
253
+ # Check available packages
254
+ ls packages/
255
+ ```
256
+
257
+ ### Profile Not Loading
258
+
259
+ Ensure the Profile is connected to your portal:
260
+
261
+ ```bash
262
+ rails g pu:res:conn Profile --dest=my_portal --singular
263
+ ```
264
+
265
+ And the user has a profile:
266
+
267
+ ```ruby
268
+ current_user.profile || current_user.create_profile
269
+ ```
270
+
271
+ ## Related Skills
272
+
273
+ - `plutonium-rodauth` - Authentication configuration
274
+ - `plutonium-definition` - Customizing the profile definition
275
+ - `plutonium-views` - Custom pages and components
276
+ - `plutonium-connect-resource` - Connecting resources to portals
@@ -346,12 +346,34 @@ Available kit methods:
346
346
  - `TabList(items:)`
347
347
  - `EmptyCard(message)`
348
348
  - `ActionButton(action, url:)`
349
- - `DynaFrameHost()` / `DynaFrameContent()`
349
+ - `DynaFrameHost(src:, loading:)` - Lazy-loading turbo frame
350
+ - `DynaFrameContent(content) { |frame| ... }` - Frame-aware content wrapper
350
351
  - `TableSearchBar()`
351
352
  - `TableScopesBar()`
352
353
  - `TableInfo(pagy)`
353
354
  - `TablePagination(pagy)`
354
355
 
356
+ ## DynaFrameContent Pattern
357
+
358
+ `DynaFrameContent` enables frame-aware rendering. For turbo-frame requests, only the content is rendered inside the frame. For regular requests, the full page renders with header/footer.
359
+
360
+ ```ruby
361
+ # How Page::Base uses DynaFrameContent
362
+ def view_template(&block)
363
+ DynaFrameContent(page_content(block)) do |frame|
364
+ render_header # Skipped for frame requests
365
+ frame.render_content # Always rendered (in turbo-frame for frame requests)
366
+ render_footer # Skipped for frame requests
367
+ end
368
+ end
369
+ ```
370
+
371
+ This pattern means:
372
+ - **Regular page load**: Full page with header, content, footer
373
+ - **Turbo-frame request**: Only `<turbo-frame id="...">` with content inside
374
+
375
+ All pages automatically inherit this behavior. No special handling needed for modals or frame navigation.
376
+
355
377
  ## Custom Components
356
378
 
357
379
  ### Creating a Phlex Component
data/CHANGELOG.md CHANGED
@@ -1,3 +1,39 @@
1
+ ## [0.43.0] - 2026-03-05
2
+
3
+ ### ๐Ÿš€ Features
4
+
5
+ - *(generators)* Add TypeSpec API specification generator
6
+ - *(forms)* Make submit_and_continue button configurable
7
+ - *(generators)* Add package destination and namespace handling to invites
8
+ - *(controller)* Auto-detect entity association by class
9
+ - *(generators)* Add profile generators for user settings pages
10
+ - *(auth)* Add profile_url helper method to rodauth module
11
+
12
+ ### ๐Ÿ› Bug Fixes
13
+
14
+ - *(ui)* Support custom primary keys and optional timestamps
15
+ - *(routing)* Handle singular resources and entity scoping correctly
16
+ - *(generators)* Improve robustness and package support
17
+ - *(resource)* Handle class reloading in associated_with scope
18
+ - *(ui)* Show profile link in user menu only when profile_url defined
19
+ - *(generators)* Fix invites templates and null attribute handling
20
+
21
+ ### ๐Ÿšœ Refactor
22
+
23
+ - *(ui)* Extract breadcrumb rendering methods and add tests
24
+
25
+ ### ๐Ÿ“š Documentation
26
+
27
+ - Add profile guide and update skill documentation
28
+
29
+ ### ๐Ÿงช Testing
30
+
31
+ - Add OrgPortal fixture for entity scoping tests
32
+ - Add tests for routing, authorization, and display helpers
33
+
34
+ ### โš™๏ธ Miscellaneous Tasks
35
+
36
+ - Add profile_url stub to controller template and test fixtures
1
37
  ## [0.42.0] - 2026-02-14
2
38
 
3
39
  ### ๐Ÿš€ Features